diff --git a/.github/workflows/iroh-relay-minter.yml b/.github/workflows/iroh-relay-minter.yml deleted file mode 100644 index 8a8f07e850ef..000000000000 --- a/.github/workflows/iroh-relay-minter.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Iroh relay minter - -on: - # CI pause: preserve explicit validation without automatic PR/main runs. - workflow_dispatch: - -concurrency: - group: iroh-relay-minter-${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -permissions: - contents: read - -jobs: - test: - runs-on: ${{ vars.LINUX_RUNNER || 'blacksmith-4vcpu-ubuntu-2404' }} - timeout-minutes: 40 - defaults: - run: - working-directory: services/iroh-relay-minter - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - name: Install pinned Rust toolchain - run: rustup toolchain install 1.91.0 --profile minimal --component clippy --component rustfmt - - - name: Check formatting - run: cargo fmt --check - - - name: Lint - run: cargo clippy --all-targets --locked -- -D warnings - - - name: Test - run: cargo test --locked - - - name: Build production function - run: cargo build --release --locked diff --git a/Packages/Shared/CmuxIrohTransport/Sources/CmuxIrohTransport/CmxIrohInboundStream.swift b/Packages/Shared/CmuxIrohTransport/Sources/CmuxIrohTransport/CmxIrohInboundStream.swift deleted file mode 100644 index 350f413dd7e5..000000000000 --- a/Packages/Shared/CmuxIrohTransport/Sources/CmuxIrohTransport/CmxIrohInboundStream.swift +++ /dev/null @@ -1,18 +0,0 @@ -/// A peer-created unidirectional stream after its lane header is removed. -public struct CmxIrohInboundStream: Sendable { - /// The declared server-event or artifact lane. - public let lane: CmxIrohLane - - /// The readable application payload after the consumed header. - public let receiveStream: any CmxIrohReceiveStream - - /// Creates a decoded inbound stream. - /// - /// - Parameters: - /// - lane: The peer-declared application lane. - /// - receiveStream: The stream with any over-read bytes preserved. - public init(lane: CmxIrohLane, receiveStream: any CmxIrohReceiveStream) { - self.lane = lane - self.receiveStream = receiveStream - } -} diff --git a/services/iroh-relay-minter/.env.example b/services/iroh-relay-minter/.env.example deleted file mode 100644 index 5f56d2143364..000000000000 --- a/services/iroh-relay-minter/.env.example +++ /dev/null @@ -1,5 +0,0 @@ -# Configure these only on the isolated relay-minter Vercel project. -IROH_SERVICES_API_SECRET= -CMUX_IROH_MINT_HMAC_SECRET_B64= -# Optional, minter-only overlap key during a bounded HMAC rotation. -CMUX_IROH_MINT_HMAC_PREVIOUS_SECRET_B64= diff --git a/services/iroh-relay-minter/.gitignore b/services/iroh-relay-minter/.gitignore deleted file mode 100644 index 226a93ca6de2..000000000000 --- a/services/iroh-relay-minter/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -/target/ -/.vercel/ -/.env* -!/.env.example diff --git a/services/iroh-relay-minter/Cargo.lock b/services/iroh-relay-minter/Cargo.lock deleted file mode 100644 index c31436a8045b..000000000000 --- a/services/iroh-relay-minter/Cargo.lock +++ /dev/null @@ -1,4590 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "aead" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "crypto-common 0.1.7", - "generic-array", -] - -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures 0.2.17", -] - -[[package]] -name = "aes-gcm" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" -dependencies = [ - "aead", - "aes", - "cipher", - "ctr", - "ghash", - "subtle", -] - -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anyhow" -version = "1.0.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" - -[[package]] -name = "arc-swap" -version = "1.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" -dependencies = [ - "rustversion", -] - -[[package]] -name = "arrayref" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" - -[[package]] -name = "arrayvec" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" - -[[package]] -name = "asn1-rs" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8" -dependencies = [ - "asn1-rs-derive", - "asn1-rs-impl", - "displaydoc", - "nom", - "num-traits", - "rusticata-macros", - "thiserror 2.0.18", - "time", -] - -[[package]] -name = "asn1-rs-derive" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "asn1-rs-impl" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "async-trait" -version = "0.1.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "async_io_stream" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" -dependencies = [ - "futures", - "pharos", - "rustc_version", -] - -[[package]] -name = "atomic-polyfill" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" -dependencies = [ - "critical-section", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "attohttpc" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e2cdb6d5ed835199484bb92bb8b3edd526effe995c61732580439c1a67e2e9" -dependencies = [ - "base64", - "http", - "log", - "url", -] - -[[package]] -name = "autocfg" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" - -[[package]] -name = "backon" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" -dependencies = [ - "fastrand", - "gloo-timers", - "tokio", -] - -[[package]] -name = "base16ct" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "base64ct" -version = "1.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" - -[[package]] -name = "bit-vec" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" -dependencies = [ - "serde", -] - -[[package]] -name = "bitflags" -version = "2.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" - -[[package]] -name = "blake3" -version = "1.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" -dependencies = [ - "arrayref", - "arrayvec", - "cc", - "cfg-if", - "constant_time_eq", - "cpufeatures 0.3.0", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "block-buffer" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" -dependencies = [ - "hybrid-array", -] - -[[package]] -name = "block2" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" -dependencies = [ - "objc2", -] - -[[package]] -name = "built" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c0e531d93d39c34eef561e929e8a7f86d77a5af08aac4f6d6e39976c51858e9" -dependencies = [ - "cargo-lock", -] - -[[package]] -name = "bumpalo" -version = "3.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo-lock" -version = "11.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63585cdf8572aa7adf0e30a253f988f2b77233bfac1973d52efb6dd53a75920e" -dependencies = [ - "semver", - "serde", - "toml", - "url", -] - -[[package]] -name = "cc" -version = "1.2.66" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d6cac793997bd970000024b2934968efe83b382de4fdcf4fcb46b6ee4ad996" -dependencies = [ - "find-msvc-tools", - "shlex", -] - -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "chacha20" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.0", - "rand_core", -] - -[[package]] -name = "chrono" -version = "0.4.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" -dependencies = [ - "iana-time-zone", - "num-traits", - "serde", - "windows-link", -] - -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common 0.1.7", - "inout", -] - -[[package]] -name = "cmov" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" - -[[package]] -name = "cmux-iroh-relay-minter" -version = "0.1.0" -dependencies = [ - "base64", - "data-encoding", - "futures-util", - "hex", - "hmac", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "iroh", - "iroh-services", - "rcan", - "serde", - "serde_json", - "sha2 0.10.9", - "time", - "tokio", - "vercel_runtime", - "zeroize", -] - -[[package]] -name = "cobs" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" -dependencies = [ - "thiserror 2.0.18", -] - -[[package]] -name = "combine" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "memchr", -] - -[[package]] -name = "const-oid" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" - -[[package]] -name = "constant_time_eq" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" - -[[package]] -name = "convert_case" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "cordyceps" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688d7fbb8092b8de775ef2536f36c8c31f2bc4006ece2e8d8ad2d17d00ce0a2a" -dependencies = [ - "loom", - "tracing", -] - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "cpufeatures" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" -dependencies = [ - "libc", -] - -[[package]] -name = "critical-section" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" - -[[package]] -name = "crossbeam-channel" -version = "0.5.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" - -[[package]] -name = "crypto-common" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "crypto-common" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" -dependencies = [ - "hybrid-array", -] - -[[package]] -name = "ctr" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" -dependencies = [ - "cipher", -] - -[[package]] -name = "ctutils" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" -dependencies = [ - "cmov", -] - -[[package]] -name = "curve25519-dalek" -version = "5.0.0-rc.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f359e08ca85e7bd759e1fd933ff2bccd81864c60a8fba0e259c7f822b0924bf" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.0", - "curve25519-dalek-derive", - "digest 0.11.3", - "fiat-crypto", - "rand_core", - "rustc_version", - "serde", - "subtle", - "zeroize", -] - -[[package]] -name = "curve25519-dalek-derive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "darling" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn", -] - -[[package]] -name = "darling_macro" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" -dependencies = [ - "darling_core", - "quote", - "syn", -] - -[[package]] -name = "data-encoding" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" - -[[package]] -name = "data-encoding-macro" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3259c913752a86488b501ed8680446a5ed2d5aeac6e596cb23ba3800768ea32c" -dependencies = [ - "data-encoding", - "data-encoding-macro-internal", -] - -[[package]] -name = "data-encoding-macro-internal" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccc2776f0c61eca1ca32528f85548abd1a4be8fb53d1b21c013e4f18da1e7090" -dependencies = [ - "data-encoding", - "syn", -] - -[[package]] -name = "der" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d" -dependencies = [ - "const-oid", - "pem-rfc7468", - "zeroize", -] - -[[package]] -name = "der-parser" -version = "10.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" -dependencies = [ - "asn1-rs", - "displaydoc", - "nom", - "num-bigint", - "num-traits", - "rusticata-macros", -] - -[[package]] -name = "deranged" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" - -[[package]] -name = "derive_builder" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" -dependencies = [ - "derive_builder_macro", -] - -[[package]] -name = "derive_builder_core" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "derive_builder_macro" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" -dependencies = [ - "derive_builder_core", - "syn", -] - -[[package]] -name = "derive_more" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn", - "unicode-xid", -] - -[[package]] -name = "diatomic-waker" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab03c107fafeb3ee9f5925686dbb7a73bc76e3932abb0d2b365cb64b169cf04c" - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer 0.10.4", - "crypto-common 0.1.7", - "subtle", -] - -[[package]] -name = "digest" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" -dependencies = [ - "block-buffer 0.12.1", - "crypto-common 0.2.2", -] - -[[package]] -name = "dispatch2" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" -dependencies = [ - "bitflags", - "block2", - "libc", - "objc2", -] - -[[package]] -name = "displaydoc" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "dlopen2" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" -dependencies = [ - "libc", - "once_cell", - "winapi", -] - -[[package]] -name = "ed25519" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a" -dependencies = [ - "pkcs8", - "serdect", - "signature", -] - -[[package]] -name = "ed25519-dalek" -version = "3.0.0-rc.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b011170fe4f04665565b4110afef66774fe9ffff278f3eb5b81cc73d26e27d60" -dependencies = [ - "curve25519-dalek", - "ed25519", - "rand_core", - "serde", - "sha2 0.11.0", - "signature", - "subtle", - "zeroize", -] - -[[package]] -name = "either" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" - -[[package]] -name = "embedded-io" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" - -[[package]] -name = "embedded-io" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" - -[[package]] -name = "enum-assoc" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed8956bd5c1f0415200516e78ff07ec9e16415ade83c056c230d7b7ea0d55b7" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "fastrand" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" - -[[package]] -name = "fiat-crypto" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" - -[[package]] -name = "find-msvc-tools" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foldhash" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" - -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futures" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-buffered" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4421cb78ee172b6b06080093479d3c50f058e7c81b7d577bbb8d118d551d4cd5" -dependencies = [ - "cordyceps", - "diatomic-waker", - "futures-core", - "pin-project-lite", - "spin 0.10.0", -] - -[[package]] -name = "futures-channel" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" - -[[package]] -name = "futures-executor" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" - -[[package]] -name = "futures-lite" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - -[[package]] -name = "futures-macro" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" - -[[package]] -name = "futures-task" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" - -[[package]] -name = "futures-util" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "slab", -] - -[[package]] -name = "generator" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3b854b0e584ead1a33f18b2fcad7cf7be18b3875c78816b753639aa501513ae" -dependencies = [ - "cc", - "cfg-if", - "libc", - "log", - "rustversion", - "windows-link", - "windows-result", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi 5.3.0", - "wasip2", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi 6.0.0", - "rand_core", - "wasm-bindgen", -] - -[[package]] -name = "ghash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" -dependencies = [ - "opaque-debug", - "polyval", -] - -[[package]] -name = "gloo-timers" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "h2" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hash32" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" -dependencies = [ - "byteorder", -] - -[[package]] -name = "hashbrown" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash", -] - -[[package]] -name = "heapless" -version = "0.7.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" -dependencies = [ - "atomic-polyfill", - "hash32", - "rustc_version", - "serde", - "spin 0.9.8", - "stable_deref_trait", -] - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hickory-net" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2295ed2f9c31e471e1428a8f88a3f0e1f4b27c15049592138d1eebe9c35b183" -dependencies = [ - "async-trait", - "bytes", - "cfg-if", - "data-encoding", - "futures-channel", - "futures-io", - "futures-util", - "h2", - "hickory-proto", - "http", - "idna", - "ipnet", - "jni 0.22.4", - "rand", - "rustls", - "thiserror 2.0.18", - "tinyvec", - "tokio", - "tokio-rustls", - "tracing", - "url", -] - -[[package]] -name = "hickory-proto" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bab31817bfb44672a252e97fe81cd0c18d1b2cf892108922f6818820df8c643" -dependencies = [ - "data-encoding", - "idna", - "ipnet", - "jni 0.22.4", - "once_cell", - "prefix-trie", - "rand", - "ring", - "thiserror 2.0.18", - "tinyvec", - "tracing", - "url", -] - -[[package]] -name = "hickory-resolver" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d58d28879ceecde6607729660c2667a081ccdc082e082675042793960f178c" -dependencies = [ - "cfg-if", - "futures-util", - "hickory-net", - "hickory-proto", - "ipconfig", - "ipnet", - "jni 0.22.4", - "moka", - "ndk-context", - "once_cell", - "parking_lot", - "rand", - "resolv-conf", - "rustls", - "smallvec", - "system-configuration", - "thiserror 2.0.18", - "tokio", - "tokio-rustls", - "tracing", -] - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest 0.10.7", -] - -[[package]] -name = "http" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" -dependencies = [ - "bytes", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hybrid-array" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" -dependencies = [ - "typenum", -] - -[[package]] -name = "hyper" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" -dependencies = [ - "http", - "hyper", - "hyper-util", - "rustls", - "tokio", - "tokio-rustls", - "tower-service", -] - -[[package]] -name = "hyper-util" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" -dependencies = [ - "base64", - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2", - "system-configuration", - "tokio", - "tower-layer", - "tower-service", - "tracing", - "windows-registry", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.65" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "icu_collections" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" -dependencies = [ - "displaydoc", - "potential_utf", - "utf8_iter", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" - -[[package]] -name = "icu_properties" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" -dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" - -[[package]] -name = "icu_provider" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "identity-hash" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfdd7caa900436d8f13b2346fe10257e0c05c1f1f9e351f4f5d57c03bd5f45da" - -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "igd-next" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de7238d487a9aff61f81b5ab41c0a841532a115a398b5fa92a2fadd0885e2581" -dependencies = [ - "attohttpc", - "bytes", - "futures", - "http", - "http-body-util", - "hyper", - "hyper-util", - "log", - "rand", - "tokio", - "url", - "xmltree", -] - -[[package]] -name = "indexmap" -version = "2.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "inout" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" -dependencies = [ - "generic-array", -] - -[[package]] -name = "ipconfig" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" -dependencies = [ - "socket2", - "widestring", - "windows-registry", - "windows-result", - "windows-sys 0.61.2", -] - -[[package]] -name = "ipnet" -version = "2.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" -dependencies = [ - "serde", -] - -[[package]] -name = "iroh" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6435544bb3a5c4e6ff7affaa0c0aa0d1bca45bd700226329d5059d3eb54f9dff" -dependencies = [ - "backon", - "blake3", - "bytes", - "cfg_aliases", - "ctutils", - "data-encoding", - "derive_more", - "ed25519-dalek", - "futures-util", - "getrandom 0.4.3", - "hickory-resolver", - "http", - "ipnet", - "iroh-base", - "iroh-dns", - "iroh-metrics", - "iroh-relay", - "n0-error", - "n0-future", - "n0-watcher", - "netwatch", - "noq", - "noq-proto", - "noq-udp", - "papaya", - "pin-project", - "portable-atomic", - "portmapper", - "rand", - "reqwest", - "rustc-hash", - "rustls", - "rustls-pki-types", - "serde", - "smallvec", - "strum", - "time", - "tokio", - "tokio-stream", - "tokio-util", - "tracing", - "url", - "wasm-bindgen-futures", -] - -[[package]] -name = "iroh-base" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830a582cd54410dc1aa71d4786a82c3297d7b0165accd8b6dbbb3b240b48140d" -dependencies = [ - "curve25519-dalek", - "data-encoding", - "data-encoding-macro", - "derive_more", - "ed25519-dalek", - "getrandom 0.4.3", - "n0-error", - "rand", - "serde", - "url", - "zeroize", -] - -[[package]] -name = "iroh-dns" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "516e4eedc38e33ab69a6bd325520332dc3d67b25454e2d590ebb84a25240dd9a" -dependencies = [ - "arc-swap", - "cfg_aliases", - "derive_more", - "hickory-resolver", - "iroh-base", - "n0-error", - "n0-future", - "ndk-context", - "portable-atomic", - "rand", - "rustls", - "simple-dns", - "strum", - "tokio", - "tracing", - "url", -] - -[[package]] -name = "iroh-metrics" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291065721ad7c477b972e581bbc528df031dc8eb5e39fe1ff3300ae5dfb157ef" -dependencies = [ - "iroh-metrics-derive", - "itoa", - "n0-error", - "portable-atomic", - "ryu", - "serde", - "tracing", -] - -[[package]] -name = "iroh-metrics-derive" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ae5f0c4405d1fbc9fb16ff422ca40620e93dc36c30ecaba0c2aee3992b7bd48" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "iroh-relay" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8149bb6a57126225a07d6928846d82dcedfd24ea0f863ef7b2eb475e1d726354" -dependencies = [ - "blake3", - "bytes", - "cfg_aliases", - "data-encoding", - "derive_more", - "getrandom 0.4.3", - "hickory-resolver", - "http", - "http-body-util", - "hyper", - "hyper-util", - "iroh-base", - "iroh-dns", - "iroh-metrics", - "lru", - "n0-error", - "n0-future", - "noq", - "noq-proto", - "num_enum", - "pin-project", - "postcard", - "rand", - "reqwest", - "rustls", - "rustls-pki-types", - "serde", - "serde_bytes", - "strum", - "tokio", - "tokio-rustls", - "tokio-util", - "tokio-websockets", - "tracing", - "url", - "vergen-gitcl", - "webpki-roots", - "ws_stream_wasm", -] - -[[package]] -name = "iroh-services" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a88cd95fbd20abd9eadc4df91c722915dc943412b964e915f35b0b0a46a1fd" -dependencies = [ - "anyhow", - "base64", - "built", - "bytes", - "data-encoding", - "derive_more", - "ed25519-dalek", - "futures-buffered", - "getrandom 0.4.3", - "iroh", - "iroh-metrics", - "iroh-tickets", - "irpc", - "irpc-iroh", - "n0-error", - "n0-future", - "portmapper", - "postcard", - "rand", - "rcan", - "serde", - "serde_json", - "strum", - "thiserror 2.0.18", - "tokio", - "tracing", - "tracing-subscriber", - "uuid", -] - -[[package]] -name = "iroh-tickets" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da53233419ca36bf521ed45683b7748366f9b233032891eefc2d70567a84ac54" -dependencies = [ - "data-encoding", - "derive_more", - "iroh-base", - "n0-error", - "postcard", - "serde", -] - -[[package]] -name = "irpc" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3623d6ff582b415904b29bbe6ebcb4a4f9a262ccdee05a45fdd003ef0950c386" -dependencies = [ - "futures-buffered", - "futures-util", - "irpc-derive", - "n0-error", - "n0-future", - "noq", - "postcard", - "rcgen", - "rustls", - "serde", - "smallvec", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "irpc-derive" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35c254013736de16472140d26904e6ac98e8f3887284dcf4af40f88c77411b56" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "irpc-iroh" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2342daed629b312f61e57e452b0750a59da162f261b97f260a6354de61d4fb0e" -dependencies = [ - "getrandom 0.3.4", - "iroh", - "iroh-base", - "irpc", - "n0-error", - "n0-future", - "postcard", - "serde", - "tokio", - "tracing", -] - -[[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys 0.3.1", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", -] - -[[package]] -name = "jni" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" -dependencies = [ - "cfg-if", - "combine", - "jni-macros", - "jni-sys 0.4.1", - "log", - "simd_cesu8", - "thiserror 2.0.18", - "walkdir", - "windows-link", -] - -[[package]] -name = "jni-macros" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" -dependencies = [ - "proc-macro2", - "quote", - "rustc_version", - "simd_cesu8", - "syn", -] - -[[package]] -name = "jni-sys" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" -dependencies = [ - "jni-sys 0.4.1", -] - -[[package]] -name = "jni-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" -dependencies = [ - "jni-sys-macros", -] - -[[package]] -name = "jni-sys-macros" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "js-sys" -version = "0.3.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" -dependencies = [ - "cfg-if", - "futures-util", - "wasm-bindgen", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libc" -version = "0.2.186" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" - -[[package]] -name = "litemap" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" - -[[package]] -name = "loom" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" -dependencies = [ - "cfg-if", - "generator", - "scoped-tls", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "lru" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6180140927ee907000b0aa540091f6ea512ead4447c92b8fc35bc72788a5a6" -dependencies = [ - "hashbrown", -] - -[[package]] -name = "lru-slab" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" - -[[package]] -name = "mac-addr" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3d25b0e0b648a86960ac23b7ad4abb9717601dec6f66c165f5b037f3f03065f" - -[[package]] -name = "matchers" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" -dependencies = [ - "regex-automata", -] - -[[package]] -name = "memchr" -version = "2.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "mio" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.61.2", -] - -[[package]] -name = "moka" -version = "0.12.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" -dependencies = [ - "crossbeam-channel", - "crossbeam-epoch", - "crossbeam-utils", - "equivalent", - "parking_lot", - "portable-atomic", - "smallvec", - "tagptr", - "uuid", -] - -[[package]] -name = "n0-error" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c37e81176a83a77d2514528b91bdafc70ef88aab428f0e1b91aebb8d99888895" -dependencies = [ - "n0-error-macros", - "spez", -] - -[[package]] -name = "n0-error-macros" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2acd8b070213b0299282f884b4beba4e7b52d624fdcd504a3ad3665390c11e1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "n0-future" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2ab99dfb861450e68853d34ae665243a88b8c493d01ba957321a1e9b2312bbe" -dependencies = [ - "cfg_aliases", - "derive_more", - "futures-buffered", - "futures-lite", - "futures-util", - "js-sys", - "pin-project", - "send_wrapper", - "tokio", - "tokio-util", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-time", -] - -[[package]] -name = "n0-watcher" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbc618745ad0b7414b149d0517ad8b5573b2fb4d4e2717add3d2446ce1fdd826" -dependencies = [ - "derive_more", - "n0-error", - "n0-future", -] - -[[package]] -name = "ndk-context" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" - -[[package]] -name = "netdev" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "569dfbdd2efd771b24ec9bb57f956e04d4fbfc72f62b2f11961723f9b3f4b020" -dependencies = [ - "block2", - "dispatch2", - "dlopen2", - "ipnet", - "jni 0.21.1", - "libc", - "mac-addr", - "ndk-context", - "netlink-packet-core", - "netlink-packet-route", - "netlink-sys", - "objc2", - "objc2-core-foundation", - "objc2-core-wlan", - "objc2-foundation", - "objc2-system-configuration", - "once_cell", - "plist", - "windows-sys 0.61.2", -] - -[[package]] -name = "netlink-packet-core" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3463cbb78394cb0141e2c926b93fc2197e473394b761986eca3b9da2c63ae0f4" -dependencies = [ - "paste", -] - -[[package]] -name = "netlink-packet-route" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2288fcb784eb3defd5fb16f4c4160d5f477de192eac730f43e1d11c24d9a007" -dependencies = [ - "bitflags", - "libc", - "log", - "netlink-packet-core", -] - -[[package]] -name = "netlink-proto" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b65d130ee111430e47eed7896ea43ca693c387f097dd97376bffafbf25812128" -dependencies = [ - "bytes", - "futures", - "log", - "netlink-packet-core", - "netlink-sys", - "thiserror 2.0.18", -] - -[[package]] -name = "netlink-sys" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd6c30ed10fa69cc491d491b85cc971f6bdeb8e7367b7cde2ee6cc878d583fae" -dependencies = [ - "bytes", - "futures-util", - "libc", - "log", - "tokio", -] - -[[package]] -name = "netwatch" -version = "0.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d9cbe01741347ef750d743d6690603f5eed8341e679fb51c8e629337aa11976" -dependencies = [ - "atomic-waker", - "bytes", - "cfg_aliases", - "derive_more", - "ipnet", - "js-sys", - "libc", - "n0-error", - "n0-future", - "n0-watcher", - "netdev", - "netlink-packet-core", - "netlink-packet-route", - "netlink-proto", - "netlink-sys", - "noq-udp", - "objc2-core-foundation", - "objc2-system-configuration", - "pin-project-lite", - "serde", - "socket2", - "time", - "tokio", - "tokio-util", - "tracing", - "web-sys", - "windows", - "windows-result", - "wmi", -] - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "noq" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bf95190af1bd4a00a10e8255ca0c8ddd9e9a9f5e79151d7a7eb6d56aff5dc89" -dependencies = [ - "bytes", - "cfg_aliases", - "derive_more", - "noq-proto", - "noq-udp", - "pin-project-lite", - "rustc-hash", - "rustls", - "socket2", - "thiserror 2.0.18", - "tokio", - "tokio-stream", - "tracing", - "web-time", -] - -[[package]] -name = "noq-proto" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa6c890013591e709a3e45dd53501351b7e27e7ff3c7e9fc3dce43e300e7e9d3" -dependencies = [ - "aes-gcm", - "bytes", - "derive_more", - "enum-assoc", - "getrandom 0.4.3", - "identity-hash", - "lru-slab", - "rand", - "rand_pcg", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "slab", - "sorted-index-buffer", - "thiserror 2.0.18", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "noq-udp" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3137a52df66c20090a889828d1c655f21f52294cba64e5c4fbb04fc83eee7c8e" -dependencies = [ - "cfg_aliases", - "libc", - "socket2", - "tracing", - "windows-sys 0.61.2", -] - -[[package]] -name = "nu-ansi-term" -version = "0.50.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "num-bigint" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-conv" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_enum" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" -dependencies = [ - "num_enum_derive", - "rustversion", -] - -[[package]] -name = "num_enum_derive" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "num_threads" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" -dependencies = [ - "libc", -] - -[[package]] -name = "objc2" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" -dependencies = [ - "objc2-encode", -] - -[[package]] -name = "objc2-core-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" -dependencies = [ - "bitflags", - "block2", - "dispatch2", - "libc", - "objc2", -] - -[[package]] -name = "objc2-core-wlan" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e34919aba0d701380d911702455038a8a3587467fe0141d6a71501e7ffe48" -dependencies = [ - "bitflags", - "objc2", - "objc2-core-foundation", - "objc2-foundation", - "objc2-security", - "objc2-security-foundation", -] - -[[package]] -name = "objc2-encode" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" - -[[package]] -name = "objc2-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" -dependencies = [ - "bitflags", - "block2", - "libc", - "objc2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-security" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709fe137109bd1e8b5a99390f77a7d8b2961dafc1a1c5db8f2e60329ad6d895a" -dependencies = [ - "bitflags", - "objc2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-security-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef76382e9cedd18123099f17638715cc3d81dba3637d4c0d39ab69df2ef345a5" -dependencies = [ - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-system-configuration" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7216bd11cbda54ccabcab84d523dc93b858ec75ecfb3a7d89513fa22464da396" -dependencies = [ - "bitflags", - "dispatch2", - "libc", - "objc2", - "objc2-core-foundation", - "objc2-security", -] - -[[package]] -name = "oid-registry" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" -dependencies = [ - "asn1-rs", -] - -[[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" -dependencies = [ - "critical-section", - "portable-atomic", -] - -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" - -[[package]] -name = "openssl-probe" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" - -[[package]] -name = "papaya" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "997ee03cd38c01469a7046643714f0ad28880bcb9e6679ff0666e24817ca19b7" -dependencies = [ - "equivalent", - "seize", -] - -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link", -] - -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - -[[package]] -name = "pem" -version = "3.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" -dependencies = [ - "base64", - "serde_core", -] - -[[package]] -name = "pem-rfc7468" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9" -dependencies = [ - "base64ct", -] - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "pharos" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" -dependencies = [ - "futures", - "rustc_version", -] - -[[package]] -name = "pin-project" -version = "1.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" - -[[package]] -name = "pkcs8" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" -dependencies = [ - "der", - "spki", -] - -[[package]] -name = "plist" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da1d65da6dd5d1e44199ac0f58712d241c0f439f80adea8924d832384087f85" -dependencies = [ - "base64", - "indexmap", - "quick-xml", - "serde", - "time", -] - -[[package]] -name = "polyval" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" -dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "opaque-debug", - "universal-hash", -] - -[[package]] -name = "portable-atomic" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" -dependencies = [ - "serde", -] - -[[package]] -name = "portmapper" -version = "0.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3713e4977408279158444a18c1a01ac9bf2e7eaf1fbfd1a19ac9cd18d90721" -dependencies = [ - "base64", - "bytes", - "derive_more", - "hyper-util", - "igd-next", - "iroh-metrics", - "libc", - "n0-error", - "n0-future", - "netwatch", - "num_enum", - "rand", - "serde", - "smallvec", - "socket2", - "time", - "tokio", - "tokio-util", - "tower-layer", - "tracing", - "url", -] - -[[package]] -name = "postcard" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" -dependencies = [ - "cobs", - "embedded-io 0.4.0", - "embedded-io 0.6.1", - "heapless", - "postcard-derive", - "serde", -] - -[[package]] -name = "postcard-derive" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0232bd009a197ceec9cc881ba46f727fcd8060a2d8d6a9dde7a69030a6fe2bb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "potential_utf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" -dependencies = [ - "zerovec", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "prefix-trie" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cf6e3177f0684016a5c209b00882e15f8bdd3f3bb48f0491df10cd102d0c6e7" -dependencies = [ - "either", - "ipnet", - "num-traits", -] - -[[package]] -name = "proc-macro-crate" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" -dependencies = [ - "toml_edit", -] - -[[package]] -name = "proc-macro2" -version = "1.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quick-xml" -version = "0.41.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" -dependencies = [ - "memchr", -] - -[[package]] -name = "quote" -version = "1.0.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "r-efi" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" - -[[package]] -name = "rand" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" -dependencies = [ - "chacha20", - "getrandom 0.4.3", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" - -[[package]] -name = "rand_pcg" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" -dependencies = [ - "rand_core", -] - -[[package]] -name = "rcan" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12a624a4a4742f8c6e58fba99712e606cea0491b76f5b2345f06af5802101027" -dependencies = [ - "anyhow", - "derive_more", - "ed25519-dalek", - "hex", - "n0-future", - "postcard", - "serde", - "serdect", -] - -[[package]] -name = "rcgen" -version = "0.14.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57f6d249aad744e274e682777a50283a225a32705394ee6d5fcc01efa25e4055" -dependencies = [ - "pem", - "ring", - "rustls-pki-types", - "time", - "x509-parser", - "yasna", -] - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags", -] - -[[package]] -name = "regex-automata" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f388202e4b80542a0921078cc23b6333bcf1409c1e3f86404cae4766a6131db" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" - -[[package]] -name = "reqwest" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" -dependencies = [ - "base64", - "bytes", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "js-sys", - "log", - "percent-encoding", - "pin-project-lite", - "rustls", - "rustls-pki-types", - "rustls-platform-verifier", - "sync_wrapper", - "tokio", - "tokio-rustls", - "tokio-util", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", -] - -[[package]] -name = "resolv-conf" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" - -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.17", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustc-hash" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "rusticata-macros" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" -dependencies = [ - "nom", -] - -[[package]] -name = "rustls" -version = "0.23.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f" -dependencies = [ - "log", - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-native-certs" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" -dependencies = [ - "openssl-probe", - "rustls-pki-types", - "schannel", - "security-framework", -] - -[[package]] -name = "rustls-pki-types" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" -dependencies = [ - "web-time", - "zeroize", -] - -[[package]] -name = "rustls-platform-verifier" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" -dependencies = [ - "core-foundation 0.10.1", - "core-foundation-sys", - "jni 0.22.4", - "log", - "once_cell", - "rustls", - "rustls-native-certs", - "rustls-platform-verifier-android", - "rustls-webpki", - "security-framework", - "security-framework-sys", - "webpki-root-certs", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls-platform-verifier-android" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" - -[[package]] -name = "rustls-webpki" -version = "0.103.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" - -[[package]] -name = "ryu" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "schannel" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "security-framework" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" -dependencies = [ - "bitflags", - "core-foundation 0.10.1", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "seize" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b55fb86dfd3a2f5f76ea78310a88f96c4ea21a3031f8d212443d56123fd0521" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "semver" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" -dependencies = [ - "serde", - "serde_core", -] - -[[package]] -name = "send_wrapper" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_bytes" -version = "0.11.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" -dependencies = [ - "serde", - "serde_core", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.150" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "serde_spanned" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" -dependencies = [ - "serde_core", -] - -[[package]] -name = "serdect" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e" -dependencies = [ - "base16ct", - "serde", -] - -[[package]] -name = "sha1_smol" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" - -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "digest 0.10.7", -] - -[[package]] -name = "sha2" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.0", - "digest 0.11.3", -] - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shlex" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" - -[[package]] -name = "signal-hook-registry" -version = "1.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" -dependencies = [ - "errno", - "libc", -] - -[[package]] -name = "signature" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" - -[[package]] -name = "simd_cesu8" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" -dependencies = [ - "rustc_version", - "simdutf8", -] - -[[package]] -name = "simdutf8" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" - -[[package]] -name = "simple-dns" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a75cbde1bf934313596a004973e462f9a82caa814dcf1a5f507bdf51597eeb4" -dependencies = [ - "bitflags", -] - -[[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - -[[package]] -name = "smallvec" -version = "1.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" - -[[package]] -name = "socket2" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "sorted-index-buffer" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea06cc588e43c632923a55450401b8f25e628131571d4e1baea1bdfdb2b5ed06" - -[[package]] -name = "spez" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c87e960f4dca2788eeb86bbdde8dd246be8948790b7618d656e68f9b720a86e8" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] - -[[package]] -name = "spin" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" - -[[package]] -name = "spki" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" -dependencies = [ - "base64ct", - "der", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "strum" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "syn" -version = "2.0.118" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "system-configuration" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" -dependencies = [ - "bitflags", - "core-foundation 0.9.4", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "tagptr" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" -dependencies = [ - "thiserror-impl 2.0.18", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "thread_local" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "time" -version = "0.3.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" -dependencies = [ - "deranged", - "js-sys", - "libc", - "num-conv", - "num_threads", - "powerfmt", - "serde_core", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" - -[[package]] -name = "time-macros" -version = "0.2.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tinystr" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tinyvec" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tokio" -version = "1.52.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" -dependencies = [ - "bytes", - "libc", - "mio", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.61.2", -] - -[[package]] -name = "tokio-macros" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-stream" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", - "tokio-util", -] - -[[package]] -name = "tokio-util" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "futures-util", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-websockets" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52efb639344a7c6adb8e62c6f3d2c19c001ff1b79a5041ba1c6ed42e19c6aa5" -dependencies = [ - "base64", - "bytes", - "futures-core", - "futures-sink", - "getrandom 0.4.3", - "http", - "httparse", - "rand", - "ring", - "rustls-pki-types", - "sha1_smol", - "simdutf8", - "tokio", - "tokio-rustls", - "tokio-util", -] - -[[package]] -name = "toml" -version = "0.9.12+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" -dependencies = [ - "indexmap", - "serde_core", - "serde_spanned", - "toml_datetime 0.7.5+spec-1.1.0", - "toml_parser", - "toml_writer", - "winnow 0.7.15", -] - -[[package]] -name = "toml_datetime" -version = "0.7.5+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_datetime" -version = "1.1.1+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_edit" -version = "0.25.12+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" -dependencies = [ - "indexmap", - "toml_datetime 1.1.1+spec-1.1.0", - "toml_parser", - "winnow 1.0.3", -] - -[[package]] -name = "toml_parser" -version = "1.1.2+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" -dependencies = [ - "winnow 1.0.3", -] - -[[package]] -name = "toml_writer" -version = "1.1.1+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" - -[[package]] -name = "tower" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-http" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" -dependencies = [ - "bitflags", - "bytes", - "futures-util", - "http", - "http-body", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", - "url", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "log", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-serde" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" -dependencies = [ - "serde", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex-automata", - "serde", - "serde_json", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", - "tracing-serde", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "typenum" -version = "1.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" - -[[package]] -name = "unicode-ident" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" - -[[package]] -name = "unicode-segmentation" -version = "1.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "universal-hash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" -dependencies = [ - "crypto-common 0.1.7", - "subtle", -] - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", - "serde_derive", -] - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "uuid" -version = "1.23.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53" -dependencies = [ - "getrandom 0.4.3", - "js-sys", - "serde_core", - "wasm-bindgen", -] - -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - -[[package]] -name = "vercel_runtime" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f5e7942f725023f1572b7fef91b0aeddc8749a3b9b0b191f59c208c42ed8e62" -dependencies = [ - "base64", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "lazy_static", - "libc", - "serde", - "serde_json", - "tokio", - "tokio-stream", - "tower", -] - -[[package]] -name = "vergen" -version = "9.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b849a1f6d8639e8de261e81ee0fc881e3e3620db1af9f2e0da015d4382ceaf75" -dependencies = [ - "anyhow", - "derive_builder", - "rustversion", - "vergen-lib", -] - -[[package]] -name = "vergen-gitcl" -version = "9.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ff3b5300a085d6bcd8fc96a507f706a28ae3814693236c9b409db71a1d15b9" -dependencies = [ - "anyhow", - "derive_builder", - "rustversion", - "time", - "vergen", - "vergen-lib", -] - -[[package]] -name = "vergen-lib" -version = "9.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b34a29ba7e9c59e62f229ae1932fb1b8fb8a6fdcc99215a641913f5f5a59a569" -dependencies = [ - "anyhow", - "derive_builder", - "rustversion", -] - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasip2" -version = "1.0.4+wasi-0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.126" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.76" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.126" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.126" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.126" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wasm-streams" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "web-sys" -version = "0.3.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki-root-certs" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d46a5a140e6f7afeccd8eae97eff335163939eac8b929834875168b29b3d267" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "webpki-roots" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf85cb06032201fa7c6f829d7db5a7e5aa45bcc0655327713065f6f0576731bf" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "widestring" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" -dependencies = [ - "windows-collections", - "windows-core", - "windows-future", - "windows-numerics", -] - -[[package]] -name = "windows-collections" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" -dependencies = [ - "windows-core", -] - -[[package]] -name = "windows-core" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-future" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" -dependencies = [ - "windows-core", - "windows-link", - "windows-threading", -] - -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-numerics" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" -dependencies = [ - "windows-core", - "windows-link", -] - -[[package]] -name = "windows-registry" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" -dependencies = [ - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-threading" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "winnow" -version = "0.7.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" - -[[package]] -name = "winnow" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" -dependencies = [ - "memchr", -] - -[[package]] -name = "wit-bindgen" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" - -[[package]] -name = "wmi" -version = "0.18.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c81b85c57a57500e56669586496bf2abd5cf082b9d32995251185d105208b64" -dependencies = [ - "chrono", - "futures", - "log", - "serde", - "thiserror 2.0.18", - "windows", - "windows-core", -] - -[[package]] -name = "writeable" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" - -[[package]] -name = "ws_stream_wasm" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c173014acad22e83f16403ee360115b38846fe754e735c5d9d3803fe70c6abc" -dependencies = [ - "async_io_stream", - "futures", - "js-sys", - "log", - "pharos", - "rustc_version", - "send_wrapper", - "thiserror 2.0.18", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "x509-parser" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" -dependencies = [ - "asn1-rs", - "data-encoding", - "der-parser", - "lazy_static", - "nom", - "oid-registry", - "ring", - "rusticata-macros", - "thiserror 2.0.18", - "time", -] - -[[package]] -name = "xml-rs" -version = "0.8.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" - -[[package]] -name = "xmltree" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7d8a75eaf6557bb84a65ace8609883db44a29951042ada9b393151532e41fcb" -dependencies = [ - "xml-rs", -] - -[[package]] -name = "yasna" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5f6765e852b9b4dc8e2a76843e4d64d1cea8e79bcde0b6901aea8e7c7f08282" -dependencies = [ - "bit-vec", - "time", -] - -[[package]] -name = "yoke" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zerofrom" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zerotrie" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zmij" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/services/iroh-relay-minter/Cargo.toml b/services/iroh-relay-minter/Cargo.toml deleted file mode 100644 index f50a71e4290d..000000000000 --- a/services/iroh-relay-minter/Cargo.toml +++ /dev/null @@ -1,53 +0,0 @@ -[package] -name = "cmux-iroh-relay-minter" -version = "0.1.0" -edition = "2024" -rust-version = "1.91" -publish = false -autobins = false - -[lib] -path = "src/lib.rs" - -[[bin]] -name = "relay-token" -path = "api/relay-token.rs" - -[dependencies] -base64 = "0.22.1" -data-encoding = "2.9.0" -hex = "0.4.3" -hmac = "0.12.1" -http-body = "1.0.1" -http-body-util = "0.1.3" -hyper = { version = "1.7.0", features = ["http1"] } -iroh = { version = "=1.0.0", default-features = false } -iroh-services = { version = "=1.0.0", default-features = false } -rcan = "=0.4.0" -serde = { version = "1.0.228", features = ["derive"] } -serde_json = "1.0.145" -sha2 = "0.10.9" -time = { version = "0.3.44", features = ["formatting", "parsing"] } -tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread"] } -vercel_runtime = "=2.0.0" -zeroize = "1.8.1" - -[dev-dependencies] -futures-util = "0.3.31" -hyper-util = { version = "0.1.17", features = ["server", "http1", "tokio"] } -tokio = { version = "1.47.1", features = ["net"] } - -[profile.release] -codegen-units = 1 -lto = "fat" -opt-level = 3 -panic = "abort" -strip = true - -[lints.rust] -unsafe_code = "forbid" - -[lints.clippy] -dbg_macro = "deny" -todo = "deny" -unimplemented = "deny" diff --git a/services/iroh-relay-minter/README.md b/services/iroh-relay-minter/README.md deleted file mode 100644 index 9d9858cf0684..000000000000 --- a/services/iroh-relay-minter/README.md +++ /dev/null @@ -1,98 +0,0 @@ -# Iroh relay-token minter - -This Vercel Rust project is the only cmux service allowed to hold the Iroh -Services project credential. It converts a short-lived request authenticated by -the cmux web trust broker into a 24-hour, endpoint-scoped RCAN containing only -`relay:use`. - -Deploy this directory as a separate Vercel project. Set its Root Directory to -`services/iroh-relay-minter`. Do not add `IROH_SERVICES_API_SECRET` to the cmux -web project because Vercel environment variables are project-wide. - -## Environment - -The minter project requires: - -- `IROH_SERVICES_API_SECRET`: the rotated Iroh Services project secret. It is - parsed by `iroh-services` 1.0.0 and is never returned or logged. -- `CMUX_IROH_MINT_HMAC_SECRET_B64`: 32 to 256 random bytes encoded as canonical - standard base64. Generate a new 32-byte value with `openssl rand -base64 32`. -- `CMUX_IROH_MINT_HMAC_PREVIOUS_SECRET_B64`: optional minter-only previous key - accepted during a bounded rotation overlap. It must differ from the current - key. The web project never receives this value. - -The web project requires the same `CMUX_IROH_MINT_HMAC_SECRET_B64` value and: - -- `CMUX_IROH_MINT_URL=https:///api/relay-token` - -Rotate any Iroh Services credential previously pasted into chat or logs before -putting it in Vercel. A Services credential rotation affects only the minter. - -Rotate the HMAC without an outage in this order: - -1. Deploy the minter with the new key in `CMUX_IROH_MINT_HMAC_SECRET_B64` and - the old key in `CMUX_IROH_MINT_HMAC_PREVIOUS_SECRET_B64`. -2. Change the web project's `CMUX_IROH_MINT_HMAC_SECRET_B64` to the new key. -3. Keep the previous key for at least five minutes, which covers the 30-second - request timestamp window and deployment propagation. -4. Remove `CMUX_IROH_MINT_HMAC_PREVIOUS_SECRET_B64` from the minter. - -The overlap changes only which HMAC key authenticates the existing fixed -method, path, timestamp, and body-hash transcript. It does not expand the -minter route or RCAN capabilities. - -## Wire contract - -The only accepted route is `POST /api/relay-token` with one `Content-Type` -header whose media type is `application/json`, optionally followed by parameters -such as `charset=utf-8`, no query string, and this body: - -```json -{"endpointId":"<64 lowercase hex characters>","lifetimeSeconds":86400} -``` - -The web service sends: - -- `x-cmux-iroh-timestamp`: canonical Unix seconds, within 30 seconds of the - minter clock. -- `x-cmux-iroh-signature`: unpadded base64url HMAC-SHA256 over the transcript - below. - -```text -POST -/api/relay-token - - -``` - -The response is bounded JSON: - -```json -{"token":"","expiresAt":""} -``` - -The RCAN issuer is the Iroh Services project key, the audience is the supplied -EndpointID, the sole capability is `relay:use`, and expiry is 86,400 seconds. -The trust broker stores only issuance audit state and refreshes the relay token -after 12 hours. - -## Local verification - -No production secrets are needed for tests. - -```sh -cargo fmt --check -cargo clippy --all-targets --locked -- -D warnings -cargo test --locked -``` - -For authenticated local dogfood, the example server binds only to loopback and -uses the same request handler as the Vercel function: - -```sh -CMUX_IROH_MINT_DEV_PORT=9460 cargo run --locked --example loopback -``` - -It still requires `IROH_SERVICES_API_SECRET` and -`CMUX_IROH_MINT_HMAC_SECRET_B64` in the process environment. Do not use a -credential copied through chat for a deployed environment; rotate it first. diff --git a/services/iroh-relay-minter/api/relay-token.rs b/services/iroh-relay-minter/api/relay-token.rs deleted file mode 100644 index 1c1da8d8e52a..000000000000 --- a/services/iroh-relay-minter/api/relay-token.rs +++ /dev/null @@ -1,18 +0,0 @@ -use std::time::SystemTime; - -use cmux_iroh_relay_minter::{MinterConfig, configuration_error_response, handle_request}; -use vercel_runtime::{Error, Request, Response, ResponseBody, run, service_fn}; - -#[tokio::main] -async fn main() -> Result<(), Error> { - run(service_fn(handler)).await -} - -async fn handler(request: Request) -> Result, Error> { - let config = match MinterConfig::from_env() { - Ok(config) => config, - Err(_) => return Ok(configuration_error_response()), - }; - - Ok(handle_request(request, &config, SystemTime::now()).await) -} diff --git a/services/iroh-relay-minter/examples/loopback.rs b/services/iroh-relay-minter/examples/loopback.rs deleted file mode 100644 index 2d501715eb9d..000000000000 --- a/services/iroh-relay-minter/examples/loopback.rs +++ /dev/null @@ -1,37 +0,0 @@ -use std::{convert::Infallible, env, sync::Arc, time::SystemTime}; - -use cmux_iroh_relay_minter::{MinterConfig, handle_request}; -use hyper::{Request, body::Incoming, server::conn::http1, service::service_fn}; -use hyper_util::rt::TokioIo; -use tokio::net::TcpListener; - -#[tokio::main] -async fn main() -> Result<(), Box> { - let port = env::var("CMUX_IROH_MINT_DEV_PORT") - .ok() - .map(|value| value.parse::()) - .transpose()? - .unwrap_or(9460); - let listener = TcpListener::bind(("127.0.0.1", port)).await?; - let config = Arc::new(MinterConfig::from_env()?); - eprintln!("Iroh relay minter listening on http://127.0.0.1:{port}"); - - loop { - let (stream, peer) = listener.accept().await?; - if !peer.ip().is_loopback() { - continue; - } - let config = Arc::clone(&config); - tokio::spawn(async move { - let service = service_fn(move |request: Request| { - let config = Arc::clone(&config); - async move { - Ok::<_, Infallible>(handle_request(request, &config, SystemTime::now()).await) - } - }); - let _ = http1::Builder::new() - .serve_connection(TokioIo::new(stream), service) - .await; - }); - } -} diff --git a/services/iroh-relay-minter/rust-toolchain.toml b/services/iroh-relay-minter/rust-toolchain.toml deleted file mode 100644 index 0f39414be778..000000000000 --- a/services/iroh-relay-minter/rust-toolchain.toml +++ /dev/null @@ -1,4 +0,0 @@ -[toolchain] -channel = "1.91.0" -profile = "minimal" -components = ["clippy", "rustfmt"] diff --git a/services/iroh-relay-minter/src/lib.rs b/services/iroh-relay-minter/src/lib.rs deleted file mode 100644 index 2672a171f1b0..000000000000 --- a/services/iroh-relay-minter/src/lib.rs +++ /dev/null @@ -1,929 +0,0 @@ -use std::{ - env, fmt, - str::FromStr, - time::{Duration, SystemTime, UNIX_EPOCH}, -}; - -use base64::{ - Engine as _, - engine::general_purpose::{STANDARD, STANDARD_NO_PAD, URL_SAFE_NO_PAD}, -}; -use data_encoding::BASE32_NOPAD; -use hmac::{Hmac, Mac}; -use http_body::Body; -use http_body_util::BodyExt as _; -use hyper::{ - Method, Request, Response, StatusCode, - body::Bytes, - header::{ALLOW, CACHE_CONTROL, CONTENT_LENGTH, CONTENT_TYPE, HeaderMap, HeaderName}, -}; -use iroh::{EndpointId, SecretKey}; -use iroh_services::{ - ApiSecret, - caps::{Cap, Caps, RelayCap, create_api_token_from_secret_key}, -}; -use rcan::Expires; -use serde::{Deserialize, Serialize}; -use sha2::{Digest, Sha256}; -use time::{OffsetDateTime, format_description::well_known::Rfc3339}; -use vercel_runtime::ResponseBody; -use zeroize::Zeroizing; - -pub const MINT_PATH: &str = "/api/relay-token"; -pub const RELAY_TOKEN_LIFETIME_SECONDS: u64 = 86_400; -pub const MAX_REQUEST_BYTES: usize = 4 * 1_024; - -const MAX_RESPONSE_BYTES: usize = 32 * 1_024; -const MAX_TOKEN_BYTES: usize = 16 * 1_024; -const CLOCK_SKEW_SECONDS: u64 = 30; -const SERVICES_SECRET_ENV: &str = "IROH_SERVICES_API_SECRET"; -const HMAC_SECRET_ENV: &str = "CMUX_IROH_MINT_HMAC_SECRET_B64"; -const HMAC_PREVIOUS_SECRET_ENV: &str = "CMUX_IROH_MINT_HMAC_PREVIOUS_SECRET_B64"; -const TIMESTAMP_HEADER: HeaderName = HeaderName::from_static("x-cmux-iroh-timestamp"); -const SIGNATURE_HEADER: HeaderName = HeaderName::from_static("x-cmux-iroh-signature"); - -type HmacSha256 = Hmac; - -pub struct MinterConfig { - issuer: SecretKey, - hmac_secret: Zeroizing>, - hmac_previous_secret: Option>>, -} - -impl MinterConfig { - pub fn from_env() -> Result { - let services_secret = Zeroizing::new(bounded_env(SERVICES_SECRET_ENV, 16_384)?); - let api_secret = - ApiSecret::from_str(services_secret.as_str()).map_err(|_| ConfigurationError)?; - let hmac_secret_text = Zeroizing::new(bounded_env(HMAC_SECRET_ENV, 512)?); - let hmac_secret = Zeroizing::new(decode_hmac_secret(hmac_secret_text.as_str())?); - let hmac_previous_secret = optional_bounded_env(HMAC_PREVIOUS_SECRET_ENV, 512)? - .map(Zeroizing::new) - .map(|value| decode_hmac_secret(value.as_str())) - .transpose()? - .map(Zeroizing::new); - if hmac_previous_secret - .as_ref() - .is_some_and(|previous| previous.as_slice() == hmac_secret.as_slice()) - { - return Err(ConfigurationError); - } - - Ok(Self { - issuer: api_secret.secret, - hmac_secret, - hmac_previous_secret, - }) - } -} - -#[derive(Clone, Copy, Debug)] -pub struct ConfigurationError; - -impl fmt::Display for ConfigurationError { - fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str("relay minter configuration is unavailable") - } -} - -impl std::error::Error for ConfigurationError {} - -pub async fn handle_request( - request: Request, - config: &MinterConfig, - now: SystemTime, -) -> Response -where - B: Body + Unpin, -{ - match process_request(request, config, now).await { - Ok(response) => json_response(StatusCode::OK, &response), - Err(error) => error_response(error.status(), error.code()), - } -} - -pub fn configuration_error_response() -> Response { - error_response(StatusCode::SERVICE_UNAVAILABLE, "configuration_unavailable") -} - -async fn process_request( - request: Request, - config: &MinterConfig, - now: SystemTime, -) -> Result -where - B: Body + Unpin, -{ - if request.method() != Method::POST { - return Err(RequestFailure::Method); - } - if request.uri().path() != MINT_PATH || request.uri().query().is_some() { - return Err(RequestFailure::Path); - } - require_json_content_type(request.headers())?; - validate_content_length(request.headers())?; - - let timestamp_text = single_header(request.headers(), &TIMESTAMP_HEADER) - .ok_or(RequestFailure::Authentication)? - .to_owned(); - let timestamp = parse_timestamp(×tamp_text).ok_or(RequestFailure::Authentication)?; - let now_seconds = unix_seconds(now).ok_or(RequestFailure::Internal)?; - if now_seconds.abs_diff(timestamp) > CLOCK_SKEW_SECONDS { - return Err(RequestFailure::Authentication); - } - - let signature_text = single_header(request.headers(), &SIGNATURE_HEADER) - .ok_or(RequestFailure::Authentication)?; - let signature = decode_signature(signature_text).ok_or(RequestFailure::Authentication)?; - - let body = read_bounded_body(request.into_body()).await?; - verify_configured_hmac(config, ×tamp_text, &body, &signature)?; - - let input: MintRequest = - serde_json::from_slice(&body).map_err(|_| RequestFailure::InvalidBody)?; - if input.lifetime_seconds != RELAY_TOKEN_LIFETIME_SECONDS { - return Err(RequestFailure::InvalidLifetime); - } - let endpoint_id = parse_endpoint_id(&input.endpoint_id)?; - - mint_token(config, endpoint_id, now_seconds) -} - -async fn read_bounded_body(mut body: B) -> Result, RequestFailure> -where - B: Body + Unpin, -{ - if body - .size_hint() - .upper() - .is_some_and(|upper| upper > MAX_REQUEST_BYTES as u64) - { - return Err(RequestFailure::BodyTooLarge); - } - - let mut bytes = Vec::with_capacity( - body.size_hint() - .upper() - .unwrap_or(0) - .min(MAX_REQUEST_BYTES as u64) as usize, - ); - while let Some(frame) = body.frame().await { - let frame = frame.map_err(|_| RequestFailure::InvalidBody)?; - let Ok(data) = frame.into_data() else { - continue; - }; - let Some(next_len) = bytes.len().checked_add(data.len()) else { - return Err(RequestFailure::BodyTooLarge); - }; - if next_len > MAX_REQUEST_BYTES { - return Err(RequestFailure::BodyTooLarge); - } - bytes.extend_from_slice(&data); - } - Ok(bytes) -} - -fn mint_token( - config: &MinterConfig, - endpoint_id: EndpointId, - authenticated_at: u64, -) -> Result { - let capability = Caps::new([Cap::Relay(RelayCap::Use)]); - let rcan = create_api_token_from_secret_key( - config.issuer.clone(), - endpoint_id, - Duration::from_secs(RELAY_TOKEN_LIFETIME_SECONDS), - capability, - ) - .map_err(|_| RequestFailure::Internal)?; - - let Expires::At(expires_at) = rcan.expires() else { - return Err(RequestFailure::Internal); - }; - let expected_expiry = authenticated_at - .checked_add(RELAY_TOKEN_LIFETIME_SECONDS) - .ok_or(RequestFailure::Internal)?; - if expected_expiry.abs_diff(*expires_at) > 2 { - return Err(RequestFailure::Internal); - } - - let mut token = BASE32_NOPAD.encode(&rcan.encode()); - token.make_ascii_lowercase(); - if token.is_empty() - || token.len() > MAX_TOKEN_BYTES - || token.contains('=') - || !token - .bytes() - .all(|byte| byte.is_ascii_lowercase() || (b'2'..=b'7').contains(&byte)) - { - return Err(RequestFailure::Internal); - } - - let expires_at_i64 = i64::try_from(*expires_at).map_err(|_| RequestFailure::Internal)?; - let expires_at = OffsetDateTime::from_unix_timestamp(expires_at_i64) - .map_err(|_| RequestFailure::Internal)? - .format(&Rfc3339) - .map_err(|_| RequestFailure::Internal)?; - if expires_at.len() > 64 { - return Err(RequestFailure::Internal); - } - - Ok(MintResponse { token, expires_at }) -} - -fn verify_hmac( - secret: &[u8], - timestamp: &str, - body: &[u8], - signature: &[u8; 32], -) -> Result<(), RequestFailure> { - let body_hash = hex::encode(Sha256::digest(body)); - let transcript = format!("POST\n{MINT_PATH}\n{timestamp}\n{body_hash}"); - let mut mac = HmacSha256::new_from_slice(secret).map_err(|_| RequestFailure::Internal)?; - mac.update(transcript.as_bytes()); - mac.verify_slice(signature) - .map_err(|_| RequestFailure::Authentication) -} - -fn verify_configured_hmac( - config: &MinterConfig, - timestamp: &str, - body: &[u8], - signature: &[u8; 32], -) -> Result<(), RequestFailure> { - let current_matches = - verify_hmac(config.hmac_secret.as_slice(), timestamp, body, signature).is_ok(); - let previous_matches = config - .hmac_previous_secret - .as_ref() - .is_some_and(|secret| verify_hmac(secret.as_slice(), timestamp, body, signature).is_ok()); - if current_matches || previous_matches { - Ok(()) - } else { - Err(RequestFailure::Authentication) - } -} - -fn parse_endpoint_id(value: &str) -> Result { - if value.len() != 64 - || !value - .bytes() - .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte)) - { - return Err(RequestFailure::InvalidEndpoint); - } - let mut bytes = [0_u8; 32]; - hex::decode_to_slice(value, &mut bytes).map_err(|_| RequestFailure::InvalidEndpoint)?; - EndpointId::from_bytes(&bytes).map_err(|_| RequestFailure::InvalidEndpoint) -} - -fn require_json_content_type(headers: &HeaderMap) -> Result<(), RequestFailure> { - let content_type = single_header(headers, &CONTENT_TYPE).ok_or(RequestFailure::ContentType)?; - let media_type = content_type - .split(';') - .next() - .map(str::trim) - .unwrap_or_default(); - if media_type.eq_ignore_ascii_case("application/json") { - Ok(()) - } else { - Err(RequestFailure::ContentType) - } -} - -fn validate_content_length(headers: &HeaderMap) -> Result<(), RequestFailure> { - let values = headers.get_all(&CONTENT_LENGTH); - let mut values = values.iter(); - let Some(value) = values.next() else { - return Ok(()); - }; - if values.next().is_some() { - return Err(RequestFailure::InvalidBody); - } - let length = value - .to_str() - .ok() - .and_then(|value| value.parse::().ok()) - .ok_or(RequestFailure::InvalidBody)?; - if length > MAX_REQUEST_BYTES { - Err(RequestFailure::BodyTooLarge) - } else { - Ok(()) - } -} - -fn single_header<'a>(headers: &'a HeaderMap, name: &HeaderName) -> Option<&'a str> { - let values = headers.get_all(name); - let mut values = values.iter(); - let value = values.next()?.to_str().ok()?; - if values.next().is_some() { - return None; - } - Some(value) -} - -fn parse_timestamp(value: &str) -> Option { - if value.is_empty() || value.len() > 20 || !value.bytes().all(|byte| byte.is_ascii_digit()) { - return None; - } - let parsed: u64 = value.parse().ok()?; - (parsed.to_string() == value).then_some(parsed) -} - -fn decode_signature(value: &str) -> Option<[u8; 32]> { - if value.len() != 43 || value.contains('=') { - return None; - } - let decoded = URL_SAFE_NO_PAD.decode(value.as_bytes()).ok()?; - let signature: [u8; 32] = decoded.try_into().ok()?; - (URL_SAFE_NO_PAD.encode(signature) == value).then_some(signature) -} - -fn decode_hmac_secret(value: &str) -> Result, ConfigurationError> { - let decoded = STANDARD - .decode(value.as_bytes()) - .or_else(|_| STANDARD_NO_PAD.decode(value.as_bytes())) - .map_err(|_| ConfigurationError)?; - if decoded.len() < 32 || decoded.len() > 256 { - return Err(ConfigurationError); - } - let canonical_padded = STANDARD.encode(&decoded); - let canonical_unpadded = STANDARD_NO_PAD.encode(&decoded); - if value != canonical_padded && value != canonical_unpadded { - return Err(ConfigurationError); - } - Ok(decoded) -} - -fn bounded_env(name: &str, max_bytes: usize) -> Result { - let value = env::var(name).map_err(|_| ConfigurationError)?; - if value.is_empty() || value.len() > max_bytes { - return Err(ConfigurationError); - } - Ok(value) -} - -fn optional_bounded_env( - name: &str, - max_bytes: usize, -) -> Result, ConfigurationError> { - match env::var(name) { - Ok(value) if !value.is_empty() && value.len() <= max_bytes => Ok(Some(value)), - Ok(_) => Err(ConfigurationError), - Err(env::VarError::NotPresent) => Ok(None), - Err(env::VarError::NotUnicode(_)) => Err(ConfigurationError), - } -} - -fn unix_seconds(time: SystemTime) -> Option { - time.duration_since(UNIX_EPOCH) - .ok() - .map(|value| value.as_secs()) -} - -fn json_response(status: StatusCode, value: &impl Serialize) -> Response { - let body = serde_json::to_string(value) - .unwrap_or_else(|_| "{\"error\":\"internal_error\"}".to_owned()); - if body.len() > MAX_RESPONSE_BYTES { - return error_response(StatusCode::INTERNAL_SERVER_ERROR, "internal_error"); - } - response(status, body) -} - -fn error_response(status: StatusCode, code: &'static str) -> Response { - let body = serde_json::to_string(&ErrorResponse { error: code }) - .unwrap_or_else(|_| "{\"error\":\"internal_error\"}".to_owned()); - let mut response = response(status, body); - if status == StatusCode::METHOD_NOT_ALLOWED { - response - .headers_mut() - .insert(ALLOW, "POST".parse().expect("static header value")); - } - response -} - -fn response(status: StatusCode, body: String) -> Response { - Response::builder() - .status(status) - .header(CONTENT_TYPE, "application/json") - .header(CACHE_CONTROL, "no-store") - .header("x-content-type-options", "nosniff") - .body(ResponseBody::from(body)) - .expect("static response metadata is valid") -} - -#[derive(Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -struct MintRequest { - endpoint_id: String, - lifetime_seconds: u64, -} - -#[derive(Serialize, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -struct MintResponse { - token: String, - expires_at: String, -} - -#[derive(Serialize)] -struct ErrorResponse { - error: &'static str, -} - -#[derive(Clone, Copy, Debug)] -enum RequestFailure { - Method, - Path, - ContentType, - Authentication, - BodyTooLarge, - InvalidBody, - InvalidEndpoint, - InvalidLifetime, - Internal, -} - -impl RequestFailure { - const fn status(self) -> StatusCode { - match self { - Self::Method => StatusCode::METHOD_NOT_ALLOWED, - Self::Path => StatusCode::NOT_FOUND, - Self::ContentType => StatusCode::UNSUPPORTED_MEDIA_TYPE, - Self::Authentication => StatusCode::UNAUTHORIZED, - Self::BodyTooLarge => StatusCode::PAYLOAD_TOO_LARGE, - Self::InvalidBody | Self::InvalidEndpoint | Self::InvalidLifetime => { - StatusCode::BAD_REQUEST - } - Self::Internal => StatusCode::INTERNAL_SERVER_ERROR, - } - } - - const fn code(self) -> &'static str { - match self { - Self::Method => "method_not_allowed", - Self::Path => "not_found", - Self::ContentType => "unsupported_media_type", - Self::Authentication => "unauthorized", - Self::BodyTooLarge => "body_too_large", - Self::InvalidBody => "invalid_body", - Self::InvalidEndpoint => "invalid_endpoint_id", - Self::InvalidLifetime => "invalid_lifetime", - Self::Internal => "internal_error", - } - } -} - -#[cfg(test)] -mod tests { - use std::{convert::Infallible, time::SystemTime}; - - use futures_util::stream; - use http_body::Frame; - use http_body_util::{BodyExt as _, Full, StreamBody}; - use hyper::{ - Method, Request, StatusCode, - body::Bytes, - header::{ALLOW, HeaderValue}, - }; - use iroh::SecretKey; - use rcan::{CapabilityOrigin, Expires, Rcan}; - use time::OffsetDateTime; - - use super::*; - - const TEST_HMAC_SECRET: [u8; 32] = [0x42; 32]; - const PREVIOUS_HMAC_SECRET: [u8; 32] = [0x41; 32]; - - #[tokio::test] - async fn mints_lowercase_relay_only_rcan_for_the_exact_audience() { - let config = test_config(); - let endpoint = test_endpoint(); - let now = SystemTime::now(); - let now_seconds = unix_seconds(now).expect("test clock is after the Unix epoch"); - let body = valid_body(&endpoint.to_string()); - let request = signed_request(Method::POST, MINT_PATH, now_seconds, body); - - let response = handle_request(request, &config, now).await; - assert_eq!(response.status(), StatusCode::OK); - assert_eq!(response.headers()[CACHE_CONTROL], "no-store"); - let response: MintResponse = response_json(response).await; - - assert!(!response.token.contains('=')); - assert!(response.token.len() <= MAX_TOKEN_BYTES); - assert!( - response - .token - .bytes() - .all(|byte| byte.is_ascii_lowercase() || (b'2'..=b'7').contains(&byte)) - ); - - let token_bytes = BASE32_NOPAD - .decode(response.token.to_ascii_uppercase().as_bytes()) - .expect("response is unpadded base32"); - let rcan = Rcan::::decode(&token_bytes).expect("response is a signed RCAN"); - assert_eq!(rcan.audience(), &endpoint.as_verifying_key()); - assert_eq!(rcan.issuer(), &config.issuer.public().as_verifying_key()); - assert_eq!(rcan.capability_origin(), &CapabilityOrigin::Issuer); - assert_eq!(rcan.capability().to_strings(), ["relay:use"]); - - let Expires::At(token_expiry) = rcan.expires() else { - panic!("relay token must expire"); - }; - assert!( - now_seconds - .checked_add(RELAY_TOKEN_LIFETIME_SECONDS) - .expect("test expiry is representable") - .abs_diff(*token_expiry) - <= 2 - ); - let response_expiry = OffsetDateTime::parse(&response.expires_at, &Rfc3339) - .expect("response expiry is RFC 3339"); - assert_eq!(response_expiry.unix_timestamp(), *token_expiry as i64); - } - - #[tokio::test] - async fn rejects_every_method_and_path_except_the_single_post_route() { - let config = test_config(); - let now = SystemTime::now(); - let timestamp = unix_seconds(now).expect("test clock is valid"); - let body = valid_body(&test_endpoint().to_string()); - - let response = handle_request( - signed_request(Method::GET, MINT_PATH, timestamp, body.clone()), - &config, - now, - ) - .await; - assert_eq!(response.status(), StatusCode::METHOD_NOT_ALLOWED); - assert_eq!(response.headers()[ALLOW], "POST"); - - for path in ["/", "/api/relay-token/", "/api/relay-token?debug=1"] { - let response = handle_request( - signed_request(Method::POST, path, timestamp, body.clone()), - &config, - now, - ) - .await; - assert_eq!(response.status(), StatusCode::NOT_FOUND, "path {path}"); - } - } - - #[tokio::test] - async fn rejects_missing_wrong_or_body_substituted_hmac() { - let config = test_config(); - let now = SystemTime::now(); - let timestamp = unix_seconds(now).expect("test clock is valid"); - let body = valid_body(&test_endpoint().to_string()); - - let mut missing = signed_request(Method::POST, MINT_PATH, timestamp, body.clone()); - missing.headers_mut().remove(&SIGNATURE_HEADER); - assert_eq!( - handle_request(missing, &config, now).await.status(), - StatusCode::UNAUTHORIZED - ); - - let mut wrong = signed_request(Method::POST, MINT_PATH, timestamp, body.clone()); - wrong.headers_mut().insert( - &SIGNATURE_HEADER, - URL_SAFE_NO_PAD - .encode([0_u8; 32]) - .parse() - .expect("test header is valid"), - ); - assert_eq!( - handle_request(wrong, &config, now).await.status(), - StatusCode::UNAUTHORIZED - ); - - let mut substituted = signed_request(Method::POST, MINT_PATH, timestamp, body); - *substituted.body_mut() = Full::new(Bytes::from(valid_body( - &SecretKey::from_bytes(&[0x33; 32]).public().to_string(), - ))); - assert_eq!( - handle_request(substituted, &config, now).await.status(), - StatusCode::UNAUTHORIZED - ); - } - - #[tokio::test] - async fn accepts_the_previous_hmac_secret_only_during_rotation_overlap() { - let mut config = test_config(); - config.hmac_previous_secret = Some(Zeroizing::new(PREVIOUS_HMAC_SECRET.to_vec())); - let now = SystemTime::now(); - let timestamp = unix_seconds(now).expect("test clock is valid"); - let body = valid_body(&test_endpoint().to_string()); - let request = signed_request_with_secret( - Method::POST, - MINT_PATH, - timestamp, - body.clone(), - &PREVIOUS_HMAC_SECRET, - ); - - assert_eq!( - handle_request(request, &config, now).await.status(), - StatusCode::OK - ); - let previous_after_overlap = signed_request_with_secret( - Method::POST, - MINT_PATH, - timestamp, - body, - &PREVIOUS_HMAC_SECRET, - ); - assert_eq!( - handle_request(previous_after_overlap, &test_config(), now) - .await - .status(), - StatusCode::UNAUTHORIZED - ); - } - - #[tokio::test] - async fn enforces_the_thirty_second_timestamp_window() { - let config = test_config(); - let now = SystemTime::now(); - let now_seconds = unix_seconds(now).expect("test clock is valid"); - let body = valid_body(&test_endpoint().to_string()); - - for timestamp in [now_seconds - 31, now_seconds + 31] { - let response = handle_request( - signed_request(Method::POST, MINT_PATH, timestamp, body.clone()), - &config, - now, - ) - .await; - assert_eq!(response.status(), StatusCode::UNAUTHORIZED); - } - - for timestamp in [now_seconds - 30, now_seconds + 30] { - let response = handle_request( - signed_request(Method::POST, MINT_PATH, timestamp, body.clone()), - &config, - now, - ) - .await; - assert_eq!(response.status(), StatusCode::OK); - } - - let mut malformed = signed_request(Method::POST, MINT_PATH, now_seconds, body); - malformed.headers_mut().insert( - &TIMESTAMP_HEADER, - "+123".parse().expect("test header is valid"), - ); - assert_eq!( - handle_request(malformed, &config, now).await.status(), - StatusCode::UNAUTHORIZED - ); - assert_eq!(parse_timestamp("01"), None); - } - - #[tokio::test] - async fn bounds_declared_and_streamed_request_bodies() { - let config = test_config(); - let now = SystemTime::now(); - let timestamp = unix_seconds(now).expect("test clock is valid"); - let body = valid_body(&test_endpoint().to_string()); - - let mut declared = signed_request(Method::POST, MINT_PATH, timestamp, body); - declared.headers_mut().insert( - CONTENT_LENGTH, - (MAX_REQUEST_BYTES + 1) - .to_string() - .parse() - .expect("test header is valid"), - ); - assert_eq!( - handle_request(declared, &config, now).await.status(), - StatusCode::PAYLOAD_TOO_LARGE - ); - - let chunk = Bytes::from(vec![b'x'; MAX_REQUEST_BYTES / 2 + 1]); - let streamed_body = [chunk.clone(), chunk]; - let joined = streamed_body.concat(); - let signature = sign_request(timestamp, &joined); - let body_stream = StreamBody::new(stream::iter( - streamed_body - .into_iter() - .map(|chunk| Ok::, Infallible>(Frame::data(chunk))), - )); - let streamed = Request::builder() - .method(Method::POST) - .uri(MINT_PATH) - .header(CONTENT_TYPE, "application/json") - .header(&TIMESTAMP_HEADER, timestamp.to_string()) - .header(&SIGNATURE_HEADER, signature) - .body(body_stream) - .expect("test request is valid"); - assert_eq!( - handle_request(streamed, &config, now).await.status(), - StatusCode::PAYLOAD_TOO_LARGE - ); - } - - #[tokio::test] - async fn rejects_invalid_endpoint_lifetime_and_json_shape() { - let config = test_config(); - let now = SystemTime::now(); - let timestamp = unix_seconds(now).expect("test clock is valid"); - let endpoint = test_endpoint().to_string(); - let invalid_bodies = [ - format!( - "{{\"endpointId\":\"{}\",\"lifetimeSeconds\":86400}}", - endpoint.to_ascii_uppercase() - ), - format!("{{\"endpointId\":\"{endpoint}\",\"lifetimeSeconds\":86401}}"), - format!( - "{{\"endpointId\":\"{endpoint}\",\"lifetimeSeconds\":86400,\"capability\":\"all\"}}" - ), - "{}".to_owned(), - ]; - - for body in invalid_bodies { - let response = handle_request( - signed_request(Method::POST, MINT_PATH, timestamp, body), - &config, - now, - ) - .await; - assert_eq!(response.status(), StatusCode::BAD_REQUEST); - } - } - - #[tokio::test] - async fn accepts_json_content_type_parameters() { - let config = test_config(); - let now = SystemTime::now(); - let timestamp = unix_seconds(now).expect("test clock is valid"); - let body = valid_body(&test_endpoint().to_string()); - let mut request = signed_request(Method::POST, MINT_PATH, timestamp, body); - request.headers_mut().insert( - CONTENT_TYPE, - "Application/JSON; charset=utf-8" - .parse() - .expect("valid test header"), - ); - - assert_eq!( - handle_request(request, &config, now).await.status(), - StatusCode::OK - ); - } - - #[tokio::test] - async fn rejects_non_json_duplicate_and_malformed_content_type_headers() { - let config = test_config(); - let now = SystemTime::now(); - let timestamp = unix_seconds(now).expect("test clock is valid"); - let body = valid_body(&test_endpoint().to_string()); - let mut non_json = signed_request(Method::POST, MINT_PATH, timestamp, body.clone()); - non_json.headers_mut().insert( - CONTENT_TYPE, - "text/plain".parse().expect("valid test header"), - ); - let mut duplicate = signed_request(Method::POST, MINT_PATH, timestamp, body.clone()); - duplicate.headers_mut().append( - CONTENT_TYPE, - "application/json; charset=utf-8" - .parse() - .expect("valid test header"), - ); - let mut malformed = signed_request(Method::POST, MINT_PATH, timestamp, body); - malformed.headers_mut().insert( - CONTENT_TYPE, - HeaderValue::from_bytes(&[0xff]).expect("opaque header bytes are representable"), - ); - - for request in [non_json, duplicate, malformed] { - assert_eq!( - handle_request(request, &config, now).await.status(), - StatusCode::UNSUPPORTED_MEDIA_TYPE - ); - } - } - - #[test] - fn matches_the_typescript_hmac_wire_fixture() { - #[derive(Deserialize)] - struct Fixture { - path: String, - timestamp: String, - body: String, - signature: String, - } - - let fixture: Fixture = serde_json::from_str(include_str!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/../../tests/fixtures/iroh/relay-minter-request-v1.json" - ))) - .expect("shared relay-minter fixture is valid"); - assert_eq!(fixture.path, MINT_PATH); - let timestamp = parse_timestamp(&fixture.timestamp).expect("fixture timestamp is valid"); - assert_eq!( - sign_request(timestamp, fixture.body.as_bytes()), - fixture.signature - ); - let signature = decode_signature(&fixture.signature).expect("fixture signature is valid"); - verify_hmac( - &TEST_HMAC_SECRET, - &fixture.timestamp, - fixture.body.as_bytes(), - &signature, - ) - .expect("fixture authenticates"); - let request: MintRequest = - serde_json::from_str(&fixture.body).expect("fixture body matches the contract"); - assert_eq!(request.lifetime_seconds, RELAY_TOKEN_LIFETIME_SECONDS); - parse_endpoint_id(&request.endpoint_id).expect("fixture endpoint is valid"); - } - - #[test] - fn accepts_only_canonical_hmac_secret_encodings_of_at_least_32_bytes() { - let padded = STANDARD.encode(TEST_HMAC_SECRET); - let unpadded = STANDARD_NO_PAD.encode(TEST_HMAC_SECRET); - assert_eq!( - decode_hmac_secret(&padded).expect("padded secret"), - TEST_HMAC_SECRET - ); - assert_eq!( - decode_hmac_secret(&unpadded).expect("unpadded secret"), - TEST_HMAC_SECRET - ); - assert!(decode_hmac_secret(&STANDARD.encode([1_u8; 31])).is_err()); - assert!(decode_hmac_secret(&format!(" {padded}")).is_err()); - assert!(decode_hmac_secret(&URL_SAFE_NO_PAD.encode([0xff_u8; 32])).is_err()); - } - - fn test_config() -> MinterConfig { - MinterConfig { - issuer: SecretKey::from_bytes(&[0x11; 32]), - hmac_secret: Zeroizing::new(TEST_HMAC_SECRET.to_vec()), - hmac_previous_secret: None, - } - } - - fn test_endpoint() -> EndpointId { - SecretKey::from_bytes(&[0x22; 32]).public() - } - - fn valid_body(endpoint_id: &str) -> String { - format!( - "{{\"endpointId\":\"{endpoint_id}\",\"lifetimeSeconds\":{RELAY_TOKEN_LIFETIME_SECONDS}}}" - ) - } - - fn signed_request( - method: Method, - path: &str, - timestamp: u64, - body: String, - ) -> Request> { - signed_request_with_secret(method, path, timestamp, body, &TEST_HMAC_SECRET) - } - - fn signed_request_with_secret( - method: Method, - path: &str, - timestamp: u64, - body: String, - secret: &[u8], - ) -> Request> { - Request::builder() - .method(method) - .uri(path) - .header(CONTENT_TYPE, "application/json") - .header(&TIMESTAMP_HEADER, timestamp.to_string()) - .header( - &SIGNATURE_HEADER, - sign_request_with_secret(secret, timestamp, body.as_bytes()), - ) - .body(Full::new(Bytes::from(body))) - .expect("test request is valid") - } - - fn sign_request(timestamp: u64, body: &[u8]) -> String { - sign_request_with_secret(&TEST_HMAC_SECRET, timestamp, body) - } - - fn sign_request_with_secret(secret: &[u8], timestamp: u64, body: &[u8]) -> String { - let body_hash = hex::encode(Sha256::digest(body)); - let transcript = format!("POST\n{MINT_PATH}\n{timestamp}\n{body_hash}"); - let mut mac = HmacSha256::new_from_slice(secret).expect("test HMAC key length is valid"); - mac.update(transcript.as_bytes()); - URL_SAFE_NO_PAD.encode(mac.finalize().into_bytes()) - } - - async fn response_json(response: Response) -> T - where - T: for<'de> Deserialize<'de>, - { - let bytes = response - .into_body() - .collect() - .await - .expect("test response body is readable") - .to_bytes(); - serde_json::from_slice(&bytes).expect("test response is JSON") - } -} diff --git a/services/iroh-relay-minter/vercel.json b/services/iroh-relay-minter/vercel.json deleted file mode 100644 index 20fc77e7e7cf..000000000000 --- a/services/iroh-relay-minter/vercel.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "$schema": "https://openapi.vercel.sh/vercel.json" -} diff --git a/tests/fixtures/iroh/relay-minter-request-v1.json b/tests/fixtures/iroh/relay-minter-request-v1.json deleted file mode 100644 index 978434ba6659..000000000000 --- a/tests/fixtures/iroh/relay-minter-request-v1.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "path": "/api/relay-token", - "timestamp": "1783641600", - "body": "{\"endpointId\":\"a09aa5f47a6759802ff955f8dc2d2a14a5c99d23be97f864127ff9383455a4f0\",\"lifetimeSeconds\":86400}", - "signature": "U7P9cSbpQMrtmshYTTuBALTsDhGwxWqTG4mIdlqgX4c" -} diff --git a/web/.env.example b/web/.env.example index 7a73d45a897d..7c05e7f8ba4e 100644 --- a/web/.env.example +++ b/web/.env.example @@ -78,7 +78,7 @@ NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY= STACK_SECRET_SERVER_KEY= # Personal-account Iroh trust broker. The Iroh Services project API key is not -# accepted by this process; it belongs only in the isolated Rust relay minter. +# accepted by this process. # Grant verification JSON maps the current and previous KIDs to Ed25519 SPKI # PEM strings. The developer binding override remains off unless all three # override settings explicitly match the authenticated user and deployment. @@ -93,10 +93,6 @@ CMUX_IROH_GRANT_SIGNING_KID= # Versioned public Ed25519 key set. Each key is canonical SPKI DER base64. # {"version":1,"current_kid":"current","keys":[{"kid":"current","alg":"EdDSA","spki_der_base64":"..."}]} CMUX_IROH_GRANT_VERIFICATION_KEYS_JSON= -CMUX_IROH_MINT_URL= -# Current request-authentication key. The optional previous overlap key belongs -# only in the isolated minter project. -CMUX_IROH_MINT_HMAC_SECRET_B64= CMUX_IROH_RATE_LIMIT_ID= # Must exactly match the presence Worker's CONNECTIVITY_INVALIDATION_SECRET. # Generate an independent value with `openssl rand -hex 32`. diff --git a/web/app/api/devices/iroh/relay-token/route.ts b/web/app/api/devices/iroh/relay-token/route.ts deleted file mode 100644 index c7dd0ed2899f..000000000000 --- a/web/app/api/devices/iroh/relay-token/route.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { handleIrohRoute } from "../../../../../services/iroh/routeHandler"; - - -export async function POST(request: Request): Promise { - return handleIrohRoute(request, "relay_token"); -} diff --git a/web/app/env.ts b/web/app/env.ts index 7bc0eba4598f..501e897ab0cf 100644 --- a/web/app/env.ts +++ b/web/app/env.ts @@ -1,10 +1,5 @@ import { createEnv } from "@t3-oss/env-nextjs"; import { z } from "zod"; -import { - insecureLoopbackMinterAllowed, - parseIrohMinterUrl, - type IrohMinterUrlPolicy, -} from "../services/iroh/minterUrlPolicy"; // Trim at the runtimeEnv source so every consumer — including paths that // run when validation is skipped (VERCEL_ENV === "preview") — sees clean @@ -31,13 +26,6 @@ const isVercelProductionDeployment = process.env.VERCEL === "1" && process.env.VERCEL_ENV === "production" && !isDocsZone; -const irohMinterUrlPolicy: IrohMinterUrlPolicy = { - allowInsecureLoopback: - trimEnv(process.env.CMUX_IROH_DEV_ALLOW_INSECURE_LOOPBACK_MINTER) === "1", - deploymentEnvironment: - process.env.VERCEL_ENV ?? process.env.NODE_ENV ?? "development", - isVercelDeployment: process.env.VERCEL === "1", -}; const requireVercelNonPreviewValue = ( name: string, schema: z.ZodType = z.string().min(1), @@ -116,32 +104,6 @@ const publicEnvValidationIssues = (issues: readonly unknown[]): readonly unknown } return publicIssues; }; -const localDevelopmentOptIn = (name: string) => - z.enum(["0", "1"]).optional().superRefine((value, context) => { - if ( - value === "1" && - !insecureLoopbackMinterAllowed({ - ...irohMinterUrlPolicy, - allowInsecureLoopback: true, - }) - ) { - context.addIssue({ - code: z.ZodIssueCode.custom, - message: `${name} is only allowed in local development`, - }); - } - }); -const irohMinterUrl = z.string().url().superRefine((value, context) => { - try { - parseIrohMinterUrl(value, irohMinterUrlPolicy); - } catch { - context.addIssue({ - code: z.ZodIssueCode.custom, - message: - "CMUX_IROH_MINT_URL must use HTTPS, except for an opted-in local loopback development minter", - }); - } -}); const irohBindingLimit = z.string().regex(/^[1-9][0-9]{0,3}$/).superRefine((value, context) => { if (Number(value) > 4_096) { context.addIssue({ @@ -258,8 +220,8 @@ export const env = createEnv({ }) .optional(), // Iroh trust broker. The Services API key deliberately has no TypeScript - // env entry: only the isolated Rust relay minter may hold it. These values - // are server-only and routes fail closed when an operation's key is absent. + // env entry. These values are server-only and routes fail closed when an + // operation's key is absent. CMUX_IROH_LAN_DISCOVERY_SECRET_B64: requireVercelNonPreviewValue( "CMUX_IROH_LAN_DISCOVERY_SECRET_B64", z.string().max(512).regex(/^[A-Za-z0-9+/]{43,}={0,2}$/), @@ -280,11 +242,6 @@ export const env = createEnv({ "CMUX_IROH_GRANT_VERIFICATION_KEYS_JSON", z.string().min(2).max(32_768), ), - // Optional compatibility path for n0-hosted relay credentials. The - // self-hosted fleet mints endpoint-bound JWTs through /api/relay/token. - CMUX_IROH_MINT_URL: irohMinterUrl.optional(), - CMUX_IROH_MINT_HMAC_SECRET_B64: - z.string().max(512).regex(/^[A-Za-z0-9+/]{43,}={0,2}$/).optional(), // Optional: leave unset to disable iroh rate limiting entirely. When unset, // the firewall gate in routeHandler.ts is skipped. Matches the other // optional rate-limit IDs (for example @@ -297,9 +254,6 @@ export const env = createEnv({ // Server-to-worker authentication for revision publication. Native clients // hold only their Stack access token and can never mint invalidations. CMUX_CONNECTIVITY_INVALIDATION_SECRET: z.string().min(32).max(512).optional(), - CMUX_IROH_DEV_ALLOW_INSECURE_LOOPBACK_MINTER: localDevelopmentOptIn( - "CMUX_IROH_DEV_ALLOW_INSECURE_LOOPBACK_MINTER", - ), CMUX_IROH_DEV_BINDING_OVERRIDE_ENABLED: z.enum(["0", "1"]).optional(), CMUX_IROH_DEV_BINDING_OVERRIDE_USER_IDS: z.string().max(8_192).optional(), CMUX_IROH_DEV_BINDING_OVERRIDE_ENVIRONMENTS: z.string().max(256).optional(), @@ -326,8 +280,8 @@ export const env = createEnv({ CMUX_RELAY_PREFERENCES_RATE_LIMIT_ID: z.string().min(1).optional(), // Shared secret for the relay fleet's per-connection access-control hook // (POST /api/relay/allow). Optional: when unset the route answers 503 and - // the fleet fails closed for new endpoint admissions. Same base64 shape as - // CMUX_IROH_MINT_HMAC_SECRET_B64. + // the fleet fails closed for new endpoint admissions. 32-byte random + // secret in standard base64. CMUX_RELAY_ALLOW_HMAC_SECRET_B64: z.string().max(512).regex(/^[A-Za-z0-9+/]{43,}={0,2}$/).optional(), }, @@ -405,16 +359,11 @@ export const env = createEnv({ CMUX_IROH_GRANT_SIGNING_KEY_P8: trimEnv(process.env.CMUX_IROH_GRANT_SIGNING_KEY_P8), CMUX_IROH_GRANT_SIGNING_KID: trimEnv(process.env.CMUX_IROH_GRANT_SIGNING_KID), CMUX_IROH_GRANT_VERIFICATION_KEYS_JSON: trimEnv(process.env.CMUX_IROH_GRANT_VERIFICATION_KEYS_JSON), - CMUX_IROH_MINT_URL: trimEnv(process.env.CMUX_IROH_MINT_URL), - CMUX_IROH_MINT_HMAC_SECRET_B64: trimEnv(process.env.CMUX_IROH_MINT_HMAC_SECRET_B64), CMUX_IROH_RATE_LIMIT_ID: trimEnv(process.env.CMUX_IROH_RATE_LIMIT_ID), CMUX_PRESENCE_BASE_URL: trimEnv(process.env.CMUX_PRESENCE_BASE_URL), CMUX_CONNECTIVITY_INVALIDATION_SECRET: trimEnv( process.env.CMUX_CONNECTIVITY_INVALIDATION_SECRET, ), - CMUX_IROH_DEV_ALLOW_INSECURE_LOOPBACK_MINTER: trimEnv( - process.env.CMUX_IROH_DEV_ALLOW_INSECURE_LOOPBACK_MINTER, - ), CMUX_IROH_DEV_BINDING_OVERRIDE_ENABLED: trimEnv(process.env.CMUX_IROH_DEV_BINDING_OVERRIDE_ENABLED), CMUX_IROH_DEV_BINDING_OVERRIDE_USER_IDS: trimEnv(process.env.CMUX_IROH_DEV_BINDING_OVERRIDE_USER_IDS), CMUX_IROH_DEV_BINDING_OVERRIDE_ENVIRONMENTS: trimEnv(process.env.CMUX_IROH_DEV_BINDING_OVERRIDE_ENVIRONMENTS), diff --git a/web/services/connectivity/routeHandler.ts b/web/services/connectivity/routeHandler.ts index ea472e1164de..a62bd68680e7 100644 --- a/web/services/connectivity/routeHandler.ts +++ b/web/services/connectivity/routeHandler.ts @@ -148,21 +148,8 @@ function connectivityExpectedErrorResponse( return connectivityJsonResponse({ error: `${error.resource}_not_found` }, 404); case "IrohConflictError": return connectivityJsonResponse({ error: error.code }, 409); - case "IrohQuotaExceededError": - return new Response(JSON.stringify({ - error: error.code, - retry_after_seconds: error.retryAfterSeconds, - }), { - status: 429, - headers: { - "content-type": "application/json", - "cache-control": "no-store", - "retry-after": String(error.retryAfterSeconds), - }, - }); case "IrohConfigurationError": case "IrohDatabaseError": - case "IrohRelayMintError": return connectivityJsonResponse({ error: "connectivity_service_unavailable" }, 503); } } diff --git a/web/services/iroh/README.md b/web/services/iroh/README.md index 06f728d81b8b..e99968c44fc1 100644 --- a/web/services/iroh/README.md +++ b/web/services/iroh/README.md @@ -45,33 +45,24 @@ the Stack credential. There is no total active-binding limit per account or device. Postgres advisory locks keep request-rate limits concurrency-safe: six challenges per device per -ten minutes, 32 outstanding challenges per account, 60 pair grants per account -per hour, three relay mints per endpoint per ten minutes, 12 relay mints per -endpoint per day, and 100 relay mints per account per day. A relay reservation -remains active for 60 seconds, then the next account-scoped reservation marks it -expired before applying those quotas. The optional Vercel Firewall rule is -defense in depth. A tagged-build override widens challenge issuance only after -an exact authenticated user-id and deployment-environment allowlist match. +ten minutes, 32 outstanding challenges per account, and 60 pair grants per +account per hour. The optional Vercel Firewall rule is defense in depth. A +tagged-build override widens challenge issuance only after an exact +authenticated user-id and deployment-environment allowlist match. -Registration bootstraps a relay credential only when it creates a binding. -Signed refreshes of the same binding return `relay.status = "not_requested"`; -clients retain their existing credential or use the dedicated relay-token route -when its refresh window arrives. Platform is part of the immutable binding -identity and requires explicit revocation before it can change. - -The n0-hosted relay minter is an optional compatibility path. When -`CMUX_IROH_MINT_URL` and `CMUX_IROH_MINT_HMAC_SECRET_B64` are absent, initial -registration returns `relay.status = "unavailable"` without rolling back the -binding. Current clients obtain endpoint-bound credentials for the self-hosted -fleet from `/api/relay/token`. +Registration never mints a relay credential. A newly created binding receives +`relay.status = "unavailable"` and signed refreshes of the same binding return +`relay.status = "not_requested"`; clients obtain endpoint-bound credentials for +the self-hosted fleet from `/api/relay/token`, which admits callers by their +active binding. Platform is part of the immutable binding identity and requires +explicit revocation before it can change. Every user-scoped mutation acquires the account-deletion advisory fence before any Iroh lock. If the deletion tombstone wins, no challenge, binding, grant, or relay audit state can be created. If an Iroh mutation wins, account deletion waits for that transaction and then removes its rows. Pair grants re-read and lock both exact signed peers at audit insertion, requiring an iOS initiator and -a pairable Mac acceptor. Relay credentials are returned only after a second -locked active-binding check following the external mint. +a pairable Mac acceptor. Registration stores the earliest managed-relay expiry in `path_hints_next_expiry`. The hourly cleanup uses that indexed scalar and diff --git a/web/services/iroh/config.ts b/web/services/iroh/config.ts index d015a12897d4..9b0bd91a1609 100644 --- a/web/services/iroh/config.ts +++ b/web/services/iroh/config.ts @@ -8,11 +8,6 @@ export type IrohTrustBrokerConfigShape = { readonly grantSigningPrivateKeyPem?: string; readonly grantSigningKid?: string; readonly grantVerificationKeysJson?: string; - readonly relayMinterUrl?: string; - readonly relayMinterHmacSecretBase64?: string; - readonly relayMinterInsecureLoopbackOptIn: boolean; - readonly deploymentEnvironment: string; - readonly isVercelDeployment: boolean; }; export class IrohTrustBrokerConfig extends Context.Tag("cmux/IrohTrustBrokerConfig")< @@ -27,12 +22,6 @@ export function irohTrustBrokerConfigFromEnv(): IrohTrustBrokerConfigShape { grantSigningPrivateKeyPem: env.CMUX_IROH_GRANT_SIGNING_KEY_P8, grantSigningKid: env.CMUX_IROH_GRANT_SIGNING_KID, grantVerificationKeysJson: env.CMUX_IROH_GRANT_VERIFICATION_KEYS_JSON, - relayMinterUrl: env.CMUX_IROH_MINT_URL, - relayMinterHmacSecretBase64: env.CMUX_IROH_MINT_HMAC_SECRET_B64, - relayMinterInsecureLoopbackOptIn: - env.CMUX_IROH_DEV_ALLOW_INSECURE_LOOPBACK_MINTER === "1", - deploymentEnvironment: process.env.VERCEL_ENV ?? process.env.NODE_ENV ?? "development", - isVercelDeployment: process.env.VERCEL === "1", }; } diff --git a/web/services/iroh/crypto.ts b/web/services/iroh/crypto.ts index 3b818ce76592..7ebbd427ad8c 100644 --- a/web/services/iroh/crypto.ts +++ b/web/services/iroh/crypto.ts @@ -12,8 +12,6 @@ import { IROH_ENDPOINT_ATTESTATION_SCOPE, IROH_ENDPOINT_ATTESTATION_TYP, IROH_ENDPOINT_ATTESTATION_VERSION, - IROH_OFFLINE_PAIR_SESSION_LIFETIME_SECONDS, - IROH_OFFLINE_PAIR_SESSION_VERSION, IROH_ALPN, IROH_PAIR_GRANT_LIFETIME_SECONDS, IROH_PAIR_GRANT_TYP, @@ -94,32 +92,6 @@ export type EndpointAttestationExpectation = { readonly nowSeconds: number; }; -export type OfflinePairVerificationExpectation = { - readonly initiator: Omit & { - readonly platform: "ios"; - }; - readonly acceptor: Omit & { - readonly platform: "mac"; - }; - readonly nowSeconds: number; -}; - -export type OfflinePairSessionRecord = { - readonly version: typeof IROH_OFFLINE_PAIR_SESSION_VERSION; - readonly sessionId: string; - readonly acceptor: OfflinePairVerificationExpectation["acceptor"]; - readonly proofHash: string; - readonly createdAtSeconds: number; - readonly expiresAtSeconds: number; - consumedAtSeconds: number | null; -}; - -export type OfflinePairInvitationProof = { - readonly version: typeof IROH_OFFLINE_PAIR_SESSION_VERSION; - readonly sessionId: string; - readonly proof: string; -}; - export function registrationTranscript(input: { readonly challengeId: string; readonly nonce: string; @@ -350,109 +322,6 @@ export function verifyEndpointAttestation( return claims; } -function verifyOfflineSameAccountPair(input: { - readonly initiatorAttestation: string; - readonly acceptorAttestation: string; - readonly publicKeys: ReadonlyMap; - readonly expected: OfflinePairVerificationExpectation; -}): { - readonly initiator: EndpointAttestationClaims; - readonly acceptor: EndpointAttestationClaims; -} { - if ( - input.expected.initiator.platform !== "ios" || - input.expected.acceptor.platform !== "mac" - ) { - throw new IrohForbiddenError({ code: "invalid_offline_pair_platforms" }); - } - const initiator = verifyEndpointAttestation(input.initiatorAttestation, input.publicKeys, { - ...input.expected.initiator, - nowSeconds: input.expected.nowSeconds, - }); - const acceptor = verifyEndpointAttestation(input.acceptorAttestation, input.publicKeys, { - ...input.expected.acceptor, - nowSeconds: input.expected.nowSeconds, - }); - if ( - initiator.bindingId === acceptor.bindingId || - initiator.deviceId === acceptor.deviceId || - initiator.endpointId === acceptor.endpointId || - !canonicalSubjectsEqual(initiator.sub, acceptor.sub) - ) { - throw new IrohForbiddenError({ code: "offline_pair_same_account_proof_required" }); - } - return { initiator, acceptor }; -} - -export function createOfflinePairSessionRecord(input: { - readonly sessionId: string; - readonly proof: string; - readonly acceptor: OfflinePairVerificationExpectation["acceptor"]; - readonly nowSeconds: number; - readonly expiresAtSeconds: number; -}): OfflinePairSessionRecord { - validateOfflinePairSessionWindow(input.nowSeconds, input.expiresAtSeconds); - validateEndpointExpectation(input.acceptor, "mac"); - if (!UUID_PATTERN.test(input.sessionId) || input.sessionId !== input.sessionId.toLowerCase()) { - throw new IrohInvalidInputError({ code: "invalid_offline_pair_session" }); - } - const proof = decodeCanonicalBase64url(input.proof, 32, "invalid_offline_pair_proof"); - return { - version: IROH_OFFLINE_PAIR_SESSION_VERSION, - sessionId: input.sessionId.toLowerCase(), - acceptor: { ...input.acceptor }, - proofHash: offlinePairProofHash(input.sessionId.toLowerCase(), input.acceptor, proof), - createdAtSeconds: input.nowSeconds, - expiresAtSeconds: input.expiresAtSeconds, - consumedAtSeconds: null, - }; -} - -export function verifyAndConsumeOfflineSameAccountPair(input: { - readonly initiatorAttestation: string; - readonly acceptorAttestation: string; - readonly publicKeys: ReadonlyMap; - readonly expected: OfflinePairVerificationExpectation; - readonly session: OfflinePairSessionRecord; - readonly invitation: OfflinePairInvitationProof; -}): { - readonly initiator: EndpointAttestationClaims; - readonly acceptor: EndpointAttestationClaims; - readonly sessionId: string; -} { - const { session, invitation } = input; - if ( - typeof invitation.sessionId !== "string" || - !UUID_PATTERN.test(invitation.sessionId) || - invitation.sessionId !== invitation.sessionId.toLowerCase() - ) { - throw new IrohInvalidInputError({ code: "invalid_offline_pair_session" }); - } - if ( - session.version !== IROH_OFFLINE_PAIR_SESSION_VERSION || - invitation.version !== IROH_OFFLINE_PAIR_SESSION_VERSION || - session.consumedAtSeconds !== null || - session.sessionId !== invitation.sessionId || - !sameEndpointExpectation(session.acceptor, input.expected.acceptor) || - session.createdAtSeconds > input.expected.nowSeconds + 30 || - session.expiresAtSeconds <= input.expected.nowSeconds - ) { - throw new IrohForbiddenError({ code: "offline_pair_session_unavailable" }); - } - validateOfflinePairSessionWindow( - session.createdAtSeconds, - session.expiresAtSeconds, - ); - const proof = decodeCanonicalBase64url(invitation.proof, 32, "invalid_offline_pair_proof"); - const actualHash = offlinePairProofHash(session.sessionId, session.acceptor, proof); - if (!hashesEqual(session.proofHash, actualHash)) { - throw new IrohForbiddenError({ code: "invalid_offline_pair_proof" }); - } - const verified = verifyOfflineSameAccountPair(input); - session.consumedAtSeconds = input.expected.nowSeconds; - return { ...verified, sessionId: session.sessionId }; -} - export function parseVerificationKeys( value: string | undefined, ): ParsedPairGrantVerificationKeys { @@ -827,65 +696,6 @@ function hasExactKeys(value: Record, allowed: readonly string[] return keys.length === allowed.length && keys.every((key) => allowed.includes(key)); } -function canonicalSubjectsEqual(left: string, right: string): boolean { - const leftBytes = decodeCanonicalBase64url(left, 32, "invalid_endpoint_attestation"); - const rightBytes = decodeCanonicalBase64url(right, 32, "invalid_endpoint_attestation"); - return timingSafeEqual(leftBytes, rightBytes); -} - -function validateOfflinePairSessionWindow(nowSeconds: number, expiresAtSeconds: number): void { - if ( - !Number.isSafeInteger(nowSeconds) || - !Number.isSafeInteger(expiresAtSeconds) || - expiresAtSeconds <= nowSeconds || - expiresAtSeconds - nowSeconds > IROH_OFFLINE_PAIR_SESSION_LIFETIME_SECONDS - ) { - throw new IrohInvalidInputError({ code: "invalid_offline_pair_session" }); - } -} - -function validateEndpointExpectation( - value: OfflinePairVerificationExpectation["acceptor"], - platform: "mac" | "ios", -): void { - if ( - !UUID_PATTERN.test(value.bindingId) || - !UUID_PATTERN.test(value.deviceId) || - value.platform !== platform || - !Number.isSafeInteger(value.identityGeneration) || - value.identityGeneration < 1 || - value.identityGeneration > POSTGRES_INT32_MAX - ) { - throw new IrohInvalidInputError({ code: "invalid_offline_pair_session" }); - } - endpointId(value.endpointId); -} - -function sameEndpointExpectation( - left: OfflinePairVerificationExpectation["acceptor"], - right: OfflinePairVerificationExpectation["acceptor"], -): boolean { - return left.bindingId === right.bindingId && - left.deviceId === right.deviceId && - left.endpointId === right.endpointId && - left.identityGeneration === right.identityGeneration && - left.platform === right.platform; -} - -function offlinePairProofHash( - sessionId: string, - acceptor: OfflinePairVerificationExpectation["acceptor"], - proof: Uint8Array, -): string { - return sha256(Buffer.concat([ - Buffer.from( - `cmux/iroh/offline-pair-session/v1\n${sessionId}\n${acceptor.bindingId}\n${acceptor.deviceId}\n${acceptor.endpointId}\n${acceptor.identityGeneration}\n${acceptor.platform}\n`, - "utf8", - ), - Buffer.from(proof), - ])); -} - const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; function assertExactKeys( diff --git a/web/services/iroh/discoveryScope.ts b/web/services/iroh/discoveryScope.ts index b5e4603b9dd0..59c2340c4091 100644 --- a/web/services/iroh/discoveryScope.ts +++ b/web/services/iroh/discoveryScope.ts @@ -86,37 +86,6 @@ export function discoveryScopeMatchesRegistration( && scope.localBinding.platform === registration.platform; } -export function bindingMatchesDiscoveryScope( - binding: { - readonly deviceUuid: string; - readonly appInstanceId: string; - readonly tag: string; - readonly platform: string; - readonly pairingEnabled: boolean; - }, - scope: IrohDiscoveryScope, -): boolean { - const local = scope.localBinding; - if ( - binding.deviceUuid === local.deviceId - && binding.appInstanceId === local.appInstanceId - && binding.tag === local.tag - && binding.platform === local.platform - ) { - return true; - } - const peers = scope.peerBindings; - return binding.platform === peers.platform - && ( - peers.tags === undefined - || peers.tags.includes(binding.tag.toLowerCase()) - ) - && ( - peers.pairingEnabled === undefined - || binding.pairingEnabled === peers.pairingEnabled - ); -} - function peerTags(value: unknown): readonly string[] { if ( !Array.isArray(value) diff --git a/web/services/iroh/errors.ts b/web/services/iroh/errors.ts index 67d5214f3490..16c727160460 100644 --- a/web/services/iroh/errors.ts +++ b/web/services/iroh/errors.ts @@ -17,18 +17,12 @@ export class IrohConflictError extends Data.TaggedError("IrohConflictError")<{ readonly code: string; }> {} -export class IrohQuotaExceededError extends Data.TaggedError("IrohQuotaExceededError")<{ - readonly code: string; - readonly retryAfterSeconds: number; -}> {} - export class IrohConfigurationError extends Data.TaggedError("IrohConfigurationError")<{ readonly component: | "grant_signing" | "grant_verification" | "account_subject" - | "lan_discovery" - | "relay_minter"; + | "lan_discovery"; }> {} export class IrohDatabaseError extends Data.TaggedError("IrohDatabaseError")<{ @@ -36,20 +30,13 @@ export class IrohDatabaseError extends Data.TaggedError("IrohDatabaseError")<{ readonly cause: unknown; }> {} -export class IrohRelayMintError extends Data.TaggedError("IrohRelayMintError")<{ - readonly code: string; - readonly cause?: unknown; -}> {} - export type IrohExpectedError = | IrohInvalidInputError | IrohNotFoundError | IrohForbiddenError | IrohConflictError - | IrohQuotaExceededError | IrohConfigurationError - | IrohDatabaseError - | IrohRelayMintError; + | IrohDatabaseError; export function irohExpectedError(error: unknown): IrohExpectedError | null { if (!error || typeof error !== "object") return null; @@ -85,8 +72,6 @@ const IROH_ERROR_TAGS = new Set([ "IrohNotFoundError", "IrohForbiddenError", "IrohConflictError", - "IrohQuotaExceededError", "IrohConfigurationError", "IrohDatabaseError", - "IrohRelayMintError", ]); diff --git a/web/services/iroh/minterUrlPolicy.ts b/web/services/iroh/minterUrlPolicy.ts deleted file mode 100644 index 7aac5faac229..000000000000 --- a/web/services/iroh/minterUrlPolicy.ts +++ /dev/null @@ -1,40 +0,0 @@ -export const IROH_RELAY_MINTER_PATH = "/api/relay-token"; - -export type IrohMinterUrlPolicy = { - readonly allowInsecureLoopback: boolean; - readonly deploymentEnvironment: string; - readonly isVercelDeployment: boolean; -}; - -export function insecureLoopbackMinterAllowed(policy: IrohMinterUrlPolicy): boolean { - return policy.allowInsecureLoopback && - !policy.isVercelDeployment && - policy.deploymentEnvironment === "development"; -} - -export function parseIrohMinterUrl(value: string, policy: IrohMinterUrlPolicy): URL { - const url = new URL(value); - const secureTransport = url.protocol === "https:"; - const allowedDevelopmentTransport = - url.protocol === "http:" && - insecureLoopbackMinterAllowed(policy) && - isCanonicalLoopbackHost(url.hostname); - - if ( - (!secureTransport && !allowedDevelopmentTransport) || - url.username || - url.password || - url.pathname !== IROH_RELAY_MINTER_PATH || - url.search || - url.hash - ) { - throw new Error("invalid Iroh relay minter URL"); - } - return url; -} - -function isCanonicalLoopbackHost(hostname: string): boolean { - return hostname === "localhost" || - hostname === "127.0.0.1" || - hostname === "[::1]"; -} diff --git a/web/services/iroh/model.ts b/web/services/iroh/model.ts index 7e474031775e..1e054add274a 100644 --- a/web/services/iroh/model.ts +++ b/web/services/iroh/model.ts @@ -16,10 +16,6 @@ export const IROH_ENDPOINT_ATTESTATION_SCOPE = "cmux.offline-pair.same-account"; export const IROH_CHALLENGE_LIFETIME_MS = 5 * 60 * 1_000; export const IROH_PAIR_GRANT_LIFETIME_SECONDS = 7 * 24 * 60 * 60; export const IROH_ENDPOINT_ATTESTATION_LIFETIME_SECONDS = 24 * 60 * 60; -export const IROH_OFFLINE_PAIR_SESSION_LIFETIME_SECONDS = 5 * 60; -export const IROH_OFFLINE_PAIR_SESSION_VERSION = 1; -export const IROH_RELAY_TOKEN_LIFETIME_SECONDS = 24 * 60 * 60; -export const IROH_RELAY_TOKEN_REFRESH_SECONDS = 12 * 60 * 60; export const IROH_ROUTE_CONTRACT_VERSION = 1; export const POSTGRES_INT32_MAX = 2_147_483_647; diff --git a/web/services/iroh/publicationPolicy.ts b/web/services/iroh/publicationPolicy.ts index e43abc27f3e4..f34d6b67407a 100644 --- a/web/services/iroh/publicationPolicy.ts +++ b/web/services/iroh/publicationPolicy.ts @@ -26,14 +26,6 @@ type PathHintLike = { readonly privacy_scope?: string; }; -/** Keep only endpoint-reported managed relay URLs for server persistence. */ -export function serverPublishedIrohPathHints( - hints: readonly T[], -): T[] { - return hints.filter((hint) => - hint.kind === "relay_url" && MANAGED_RELAY_URL_SET.has(hint.value)); -} - /** * Keep only routes safe for the authenticated same-account broker. * diff --git a/web/services/iroh/relayMinter.ts b/web/services/iroh/relayMinter.ts deleted file mode 100644 index 8bffeed91bf5..000000000000 --- a/web/services/iroh/relayMinter.ts +++ /dev/null @@ -1,207 +0,0 @@ -import { createHash, createHmac } from "node:crypto"; -import * as Context from "effect/Context"; -import * as Effect from "effect/Effect"; -import * as Layer from "effect/Layer"; -import { - IrohConfigurationError, - type IrohInvalidInputError, - IrohRelayMintError, -} from "./errors"; -import { IrohTrustBrokerConfig } from "./config"; -import { - IROH_RELAY_MINTER_PATH, - parseIrohMinterUrl, - type IrohMinterUrlPolicy, -} from "./minterUrlPolicy"; -import { IROH_RELAY_TOKEN_LIFETIME_SECONDS, endpointId } from "./model"; - -const MAX_MINTER_RESPONSE_BYTES = 32 * 1_024; -export { IROH_RELAY_MINTER_PATH }; - -export type IrohRelayMintResult = { - readonly token: string; - readonly expiresAt: Date; -}; - -export type IrohRelayMinterShape = { - readonly mint: (input: { - readonly endpointId: string; - readonly lifetimeSeconds: typeof IROH_RELAY_TOKEN_LIFETIME_SECONDS; - readonly now: Date; - }) => Effect.Effect< - IrohRelayMintResult, - IrohConfigurationError | IrohInvalidInputError | IrohRelayMintError - >; -}; - -export class IrohRelayMinter extends Context.Tag("cmux/IrohRelayMinter")< - IrohRelayMinter, - IrohRelayMinterShape ->() {} - -export const IrohRelayMinterLive = Layer.effect( - IrohRelayMinter, - Effect.gen(function* () { - const config = yield* IrohTrustBrokerConfig; - return { - mint: (input) => mintWithIsolatedService(config, input), - } satisfies IrohRelayMinterShape; - }), -); - -function mintWithIsolatedService( - config: typeof IrohTrustBrokerConfig.Service, - input: Parameters[0], -): Effect.Effect< - IrohRelayMintResult, - IrohConfigurationError | IrohInvalidInputError | IrohRelayMintError -> { - return Effect.tryPromise({ - try: async () => { - endpointId(input.endpointId); - const url = parseMinterUrl(config.relayMinterUrl, { - allowInsecureLoopback: config.relayMinterInsecureLoopbackOptIn, - deploymentEnvironment: config.deploymentEnvironment, - isVercelDeployment: config.isVercelDeployment, - }); - const secret = parseMinterHmacSecret(config.relayMinterHmacSecretBase64); - const body = JSON.stringify({ - endpointId: input.endpointId, - lifetimeSeconds: IROH_RELAY_TOKEN_LIFETIME_SECONDS, - }); - const timestamp = String(Math.floor(input.now.getTime() / 1_000)); - const bodyHash = createHash("sha256").update(body).digest("hex"); - const signature = createHmac("sha256", secret) - .update(`POST\n${url.pathname}\n${timestamp}\n${bodyHash}`, "utf8") - .digest("base64url"); - const response = await fetch(url, { - method: "POST", - redirect: "error", - signal: AbortSignal.timeout(10_000), - headers: { - "content-type": "application/json", - "x-cmux-iroh-timestamp": timestamp, - "x-cmux-iroh-signature": signature, - }, - body, - }); - if (!response.ok) throw new IrohRelayMintError({ code: "minter_rejected" }); - const raw = await readBoundedMinterJson(response); - if ( - typeof raw.token !== "string" || - raw.token.length < 16 || - raw.token.length > 16_384 || - !/^[a-z2-7]+$/.test(raw.token) - ) { - throw new IrohRelayMintError({ code: "invalid_minter_response" }); - } - if (typeof raw.expiresAt !== "string") throw new IrohRelayMintError({ code: "invalid_minter_response" }); - const expiresAt = new Date(raw.expiresAt); - const contractExpiry = input.now.getTime() + IROH_RELAY_TOKEN_LIFETIME_SECONDS * 1_000; - if ( - !Number.isFinite(expiresAt.getTime()) || - expiresAt <= input.now || - expiresAt.getTime() > contractExpiry + 60_000 || - expiresAt.getTime() < contractExpiry - 5 * 60_000 - ) { - throw new IrohRelayMintError({ code: "invalid_minter_expiry" }); - } - return { token: raw.token, expiresAt }; - }, - catch: (cause) => { - if ((cause as { _tag?: unknown } | null)?._tag === "IrohConfigurationError") { - return cause as IrohConfigurationError; - } - if ((cause as { _tag?: unknown } | null)?._tag === "IrohInvalidInputError") { - return cause as IrohInvalidInputError; - } - if ((cause as { _tag?: unknown } | null)?._tag === "IrohRelayMintError") { - return cause as IrohRelayMintError; - } - return new IrohRelayMintError({ code: "minter_unavailable", cause: safeCause(cause) }); - }, - }); -} - -export async function readBoundedMinterJson( - response: Response, -): Promise<{ token?: unknown; expiresAt?: unknown }> { - if ( - response.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase() !== - "application/json" - ) { - throw new IrohRelayMintError({ code: "invalid_minter_response" }); - } - const contentLength = response.headers.get("content-length"); - if (contentLength) { - const parsed = Number(contentLength); - if (!Number.isSafeInteger(parsed) || parsed < 0 || parsed > MAX_MINTER_RESPONSE_BYTES) { - throw new IrohRelayMintError({ code: "minter_response_too_large" }); - } - } - const reader = response.body?.getReader(); - if (!reader) throw new IrohRelayMintError({ code: "invalid_minter_response" }); - const chunks: Uint8Array[] = []; - let total = 0; - while (true) { - const next = await reader.read(); - if (next.done) break; - total += next.value.byteLength; - if (total > MAX_MINTER_RESPONSE_BYTES) { - await reader.cancel(); - throw new IrohRelayMintError({ code: "minter_response_too_large" }); - } - chunks.push(next.value); - } - const bytes = Buffer.concat(chunks.map((chunk) => Buffer.from(chunk)), total); - let parsed: unknown; - try { - parsed = JSON.parse(bytes.toString("utf8")); - } catch { - throw new IrohRelayMintError({ code: "invalid_minter_response" }); - } - if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { - throw new IrohRelayMintError({ code: "invalid_minter_response" }); - } - const object = parsed as Record; - const keys = Object.keys(object); - if (keys.length !== 2 || !keys.includes("token") || !keys.includes("expiresAt")) { - throw new IrohRelayMintError({ code: "invalid_minter_response" }); - } - return object; -} - -export function parseMinterUrl( - value: string | undefined, - policy: IrohMinterUrlPolicy = { - allowInsecureLoopback: false, - deploymentEnvironment: "production", - isVercelDeployment: true, - }, -): URL { - if (!value) throw new IrohConfigurationError({ component: "relay_minter" }); - try { - return parseIrohMinterUrl(value, policy); - } catch { - throw new IrohConfigurationError({ component: "relay_minter" }); - } -} - -export function parseMinterHmacSecret(value: string | undefined): Buffer { - if (!value || value.length > 512) throw new IrohConfigurationError({ component: "relay_minter" }); - const decoded = Buffer.from(value, "base64"); - const canonicalPadded = decoded.toString("base64"); - const canonicalUnpadded = canonicalPadded.replace(/=+$/, ""); - if ( - decoded.byteLength < 32 || - decoded.byteLength > 256 || - (value !== canonicalPadded && value !== canonicalUnpadded) - ) { - throw new IrohConfigurationError({ component: "relay_minter" }); - } - return decoded; -} - -function safeCause(cause: unknown): unknown { - return cause instanceof Error ? { name: cause.name } : { type: typeof cause }; -} diff --git a/web/services/iroh/repository.ts b/web/services/iroh/repository.ts index 4ad49a8ba9c6..873af7845a76 100644 --- a/web/services/iroh/repository.ts +++ b/web/services/iroh/repository.ts @@ -19,14 +19,12 @@ import { IrohDatabaseError, IrohForbiddenError, IrohNotFoundError, - IrohQuotaExceededError, } from "./errors"; import type { PairGrantPeer } from "./crypto"; import type { IrohDiscoveryCursor } from "./discoveryPagination"; import { nextPathHintExpiry, parseIrohPathHint, - sha256, type IrohPathHint, type IrohRegistrationPayload, } from "./model"; @@ -40,7 +38,6 @@ import type { IrohDiscoveryScope } from "./discoveryScope"; export const IROH_RETENTION_BATCH_SIZE = 500; export const IROH_RETENTION_MAX_ROWS = 10_000; export const IROH_RETENTION_MAX_DURATION_MS = 8_000; -export const IROH_RELAY_RESERVATION_LEASE_MS = 60 * 1_000; export type IrohRetentionCategory = | "revokedHints" @@ -76,8 +73,7 @@ type RepositoryError = | IrohDatabaseError | IrohForbiddenError | IrohNotFoundError - | IrohConflictError - | IrohQuotaExceededError; + | IrohConflictError; export type IrohRepositoryShape = { readonly issueChallenge: (input: { @@ -181,30 +177,6 @@ export type IrohRepositoryShape = { readonly notBefore: Date; readonly expiresAt: Date; }) => Effect.Effect; - readonly reserveRelayIssuance: (input: { - readonly userId: string; - readonly bindingId: string; - readonly clientNamespace?: string; - readonly now: Date; - }) => Effect.Effect<{ - readonly issuanceId: string; - readonly binding: IrohBindingRecord; - }, RepositoryError>; - readonly completeRelayIssuance: (input: { - readonly userId: string; - readonly issuanceId: string; - readonly bindingId: string; - readonly endpointId: string; - readonly tokenHash: string; - readonly completedAt: Date; - readonly expiresAt: Date; - }) => Effect.Effect; - readonly failRelayIssuance: (input: { - readonly userId: string; - readonly issuanceId: string; - readonly completedAt: Date; - readonly failureCode: string; - }) => Effect.Effect; }; export class IrohRepository extends Context.Tag("cmux/IrohRepository")< @@ -1145,134 +1117,6 @@ function makeLiveRepository(): IrohRepositoryShape { }); }), - reserveRelayIssuance: (input) => repositoryEffect("reserve_relay_issuance", async () => { - return await cloudDb().transaction(async (tx) => { - await assertIrohUserMutationAllowed(tx, input.userId); - await tx.execute(sql`select pg_advisory_xact_lock(hashtextextended(${`iroh:binding:${input.userId}`}, 0))`); - await tx.execute(sql`select pg_advisory_xact_lock(hashtextextended(${`iroh:relay:${input.userId}`}, 0))`); - const [binding] = await tx - .select() - .from(irohEndpointBindings) - .where(and( - eq(irohEndpointBindings.id, input.bindingId), - eq(irohEndpointBindings.userId, input.userId), - isNull(irohEndpointBindings.revokedAt), - )) - .for("update") - .limit(1); - if (!binding) throw new IrohNotFoundError({ resource: "binding" }); - if (binding.clientNamespace !== (input.clientNamespace ?? "legacy")) { - throw new IrohNotFoundError({ resource: "binding" }); - } - - await tx - .update(irohEndpointBindings) - .set({ lastSeenAt: input.now, updatedAt: input.now }) - .where(eq(irohEndpointBindings.id, binding.id)); - - const reservationCutoff = new Date( - input.now.getTime() - IROH_RELAY_RESERVATION_LEASE_MS, - ); - await tx - .update(irohRelayTokenIssuances) - .set({ - status: "expired", - completedAt: input.now, - failureCode: "reservation_expired", - }) - .where(and( - eq(irohRelayTokenIssuances.userId, input.userId), - eq(irohRelayTokenIssuances.status, "pending"), - lte(irohRelayTokenIssuances.requestedAt, reservationCutoff), - )); - - const [issuance] = await tx - .insert(irohRelayTokenIssuances) - .values({ - userId: input.userId, - bindingId: binding.id, - endpointIdHash: sha256(binding.endpointId), - status: "pending", - requestedAt: input.now, - }) - .returning({ id: irohRelayTokenIssuances.id }); - if (!issuance) throw new Error("relay issuance insert returned no row"); - return { issuanceId: issuance.id, binding }; - }); - }), - - completeRelayIssuance: (input) => repositoryEffect("complete_relay_issuance", async () => { - return await cloudDb().transaction(async (tx) => { - await assertIrohUserMutationAllowed(tx, input.userId); - await tx.execute(sql`select pg_advisory_xact_lock(hashtextextended(${`iroh:binding:${input.userId}`}, 0))`); - const [issuance] = await tx - .select() - .from(irohRelayTokenIssuances) - .where(and( - eq(irohRelayTokenIssuances.id, input.issuanceId), - eq(irohRelayTokenIssuances.userId, input.userId), - eq(irohRelayTokenIssuances.bindingId, input.bindingId), - eq(irohRelayTokenIssuances.status, "pending"), - )) - .for("update") - .limit(1); - if (!issuance) return false; - const [binding] = await tx - .select({ endpointId: irohEndpointBindings.endpointId }) - .from(irohEndpointBindings) - .where(and( - eq(irohEndpointBindings.id, input.bindingId), - eq(irohEndpointBindings.userId, input.userId), - isNull(irohEndpointBindings.revokedAt), - )) - .for("update") - .limit(1); - if ( - !binding || - binding.endpointId !== input.endpointId || - issuance.endpointIdHash !== sha256(input.endpointId) - ) { - await tx - .update(irohRelayTokenIssuances) - .set({ - status: "failed", - completedAt: input.completedAt, - failureCode: "binding_inactive_after_mint", - }) - .where(eq(irohRelayTokenIssuances.id, input.issuanceId)); - return false; - } - const completed = await tx - .update(irohRelayTokenIssuances) - .set({ - status: "succeeded", - tokenHash: input.tokenHash, - completedAt: input.completedAt, - expiresAt: input.expiresAt, - failureCode: null, - }) - .where(and( - eq(irohRelayTokenIssuances.id, input.issuanceId), - eq(irohRelayTokenIssuances.status, "pending"), - )) - .returning({ id: irohRelayTokenIssuances.id }); - return completed.length === 1; - }); - }), - - failRelayIssuance: (input) => repositoryEffect("fail_relay_issuance", async () => { - await cloudDb().transaction(async (tx) => { - await assertIrohUserMutationAllowed(tx, input.userId); - await tx - .update(irohRelayTokenIssuances) - .set({ status: "failed", completedAt: input.completedAt, failureCode: input.failureCode.slice(0, 64) }) - .where(and( - eq(irohRelayTokenIssuances.id, input.issuanceId), - eq(irohRelayTokenIssuances.userId, input.userId), - eq(irohRelayTokenIssuances.status, "pending"), - )); - }); - }), }; } @@ -1735,11 +1579,10 @@ function repositoryEffect( function isDomainError(error: unknown): error is | IrohForbiddenError | IrohNotFoundError - | IrohConflictError - | IrohQuotaExceededError { + | IrohConflictError { const tag = (error as { _tag?: unknown } | null)?._tag; return tag === "IrohForbiddenError" || tag === "IrohNotFoundError" || - tag === "IrohConflictError" || tag === "IrohQuotaExceededError"; + tag === "IrohConflictError"; } function sanitizedDatabaseCause(cause: unknown): unknown { diff --git a/web/services/iroh/routeHandler.ts b/web/services/iroh/routeHandler.ts index be8a16491398..d540a090c8dc 100644 --- a/web/services/iroh/routeHandler.ts +++ b/web/services/iroh/routeHandler.ts @@ -23,8 +23,7 @@ export type IrohRouteOperation = | "discover" | "endpoint_attestation" | "revoke" - | "pair_grant" - | "relay_token"; + | "pair_grant"; type RouteDependencies = { readonly verify?: typeof verifyRequest; @@ -221,8 +220,6 @@ function invoke( return broker.revoke(userId, body, undefined, clientNamespace, bindingProof); case "pair_grant": return broker.issuePairGrant(userId, body, undefined, clientNamespace, bindingProof); - case "relay_token": - return broker.issueRelayToken(userId, body, undefined, clientNamespace, bindingProof); } } @@ -353,17 +350,6 @@ function expectedErrorResponse(error: ReturnType & obj if (tag === "IrohConflictError") { return jsonResponse({ error: (error as { code: string }).code }, 409); } - if (tag === "IrohQuotaExceededError") { - const quota = error as { code: string; retryAfterSeconds: number }; - return irohJsonResponse( - { error: quota.code, retry_after_seconds: quota.retryAfterSeconds }, - 429, - { "retry-after": String(quota.retryAfterSeconds) }, - ); - } - if (tag === "IrohConfigurationError" || tag === "IrohRelayMintError") { - return jsonResponse({ error: "iroh_service_unavailable" }, 503); - } return jsonResponse({ error: "iroh_service_unavailable" }, 503); } diff --git a/web/services/iroh/trustBroker.ts b/web/services/iroh/trustBroker.ts index 13cdc74166fc..5d1f038be7d6 100644 --- a/web/services/iroh/trustBroker.ts +++ b/web/services/iroh/trustBroker.ts @@ -42,8 +42,6 @@ import { IROH_ENDPOINT_ATTESTATION_VERSION, IROH_PAIR_GRANT_LIFETIME_SECONDS, IROH_PAIR_SCOPE, - IROH_RELAY_TOKEN_LIFETIME_SECONDS, - IROH_RELAY_TOKEN_REFRESH_SECONDS, assertChallengeMatchesPayload, decodeRegistrationPayload, parseBindingIdBody, @@ -52,7 +50,6 @@ import { parseIrohPathHint, parsePairGrantRequest, parseRegisterRequest, - sha256, type IrohPathHint, } from "./model"; import { canIOSBindingUseMac } from "./buildCompatibility"; @@ -67,11 +64,6 @@ import { legacyIrohDiscoveryRequest, parseIrohDiscoveryRequest, } from "./discoveryPagination"; -import { - IrohRelayMinter, - IrohRelayMinterLive, - type IrohRelayMinterShape, -} from "./relayMinter"; import { defaultRelayPreference, type RelayPreference, @@ -145,13 +137,6 @@ export type IrohTrustBrokerShape = { clientNamespace?: string, bindingProof?: IrohBindingRequestProof, ) => Effect.Effect; - readonly issueRelayToken: ( - userId: string, - raw: unknown, - now?: Date, - clientNamespace?: string, - bindingProof?: IrohBindingRequestProof, - ) => Effect.Effect; }; export class IrohTrustBroker extends Context.Tag("cmux/IrohTrustBroker")< @@ -161,7 +146,6 @@ export class IrohTrustBroker extends Context.Tag("cmux/IrohTrustBroker")< export function makeIrohTrustBroker( repository: IrohRepositoryShape, - relayMinter: IrohRelayMinterShape, config: IrohTrustBrokerConfigShape, relayPreferences: Pick = { getPreference: () => Effect.succeed({ @@ -210,73 +194,6 @@ export function makeIrohTrustBroker( return binding; }); - const issueRelayTokenForBinding = ( - userId: string, - binding: IrohBindingRecord, - now: Date, - ): Effect.Effect => Effect.gen(function* () { - const reservation = yield* repository.reserveRelayIssuance({ - userId, - bindingId: binding.id, - clientNamespace: binding.clientNamespace, - now, - }); - const minted = yield* relayMinter.mint({ - endpointId: reservation.binding.endpointId, - lifetimeSeconds: IROH_RELAY_TOKEN_LIFETIME_SECONDS, - now, - }).pipe( - Effect.matchEffect({ - onFailure: (error) => repository.failRelayIssuance({ - userId, - issuanceId: reservation.issuanceId, - completedAt: new Date(), - failureCode: error._tag === "IrohRelayMintError" ? error.code : "not_configured", - }).pipe( - Effect.catchAll(() => Effect.void), - Effect.flatMap(() => Effect.fail(error)), - ), - onSuccess: Effect.succeed, - }), - ); - const completedAt = new Date(); - const completed = yield* repository.completeRelayIssuance({ - userId, - issuanceId: reservation.issuanceId, - bindingId: reservation.binding.id, - endpointId: reservation.binding.endpointId, - tokenHash: sha256(minted.token), - completedAt, - expiresAt: minted.expiresAt, - }); - if (!completed) return yield* Effect.fail(new IrohNotFoundError({ resource: "binding" })); - return { - token: minted.token, - expires_at: minted.expiresAt.toISOString(), - refresh_after: new Date(now.getTime() + IROH_RELAY_TOKEN_REFRESH_SECONDS * 1_000).toISOString(), - relay_fleet: MANAGED_RELAY_URLS, - }; - }); - - const issueRelayToken = ( - userId: string, - raw: unknown, - now = new Date(), - clientNamespace = "legacy", - bindingProof?: IrohBindingRequestProof, - ): Effect.Effect => Effect.gen(function* () { - const { bindingId } = yield* parseEffect(() => parseBindingIdBody(raw)); - const caller = yield* authorizeBinding(userId, bindingProof, clientNamespace, now); - if (caller && caller.id !== bindingId) { - return yield* Effect.fail(new IrohNotFoundError({ resource: "binding" })); - } - const binding = caller ?? (yield* repository.findActiveBindings(userId, [bindingId]))[0]; - if (!binding || (!caller && binding.clientNamespace !== "legacy")) { - return yield* Effect.fail(new IrohNotFoundError({ resource: "binding" })); - } - return yield* issueRelayTokenForBinding(userId, binding, now); - }); - const discover = ( userId: string, now = new Date(), @@ -496,18 +413,13 @@ export function makeIrohTrustBroker( now, }); - // New registration is already committed before relay minting starts. - // Refreshes keep their existing credential and use the dedicated relay - // route when it expires, so path-hint churn cannot consume mint quotas. + // Registration never mints a relay credential: clients obtain + // endpoint-bound credentials for the self-hosted fleet from + // /api/relay/token after registering. "unavailable" preserves the + // response shape the pre-registry bootstrap produced when the removed + // n0-hosted minter was unconfigured, which production always was. const relay = registration.created - ? yield* issueRelayTokenForBinding( - userId, - registration.binding, - now, - ).pipe( - Effect.map((value) => ({ status: "issued" as const, ...value as object })), - Effect.catchAll(() => Effect.succeed({ status: "unavailable" as const })), - ) + ? { status: "unavailable" as const } : { status: "not_requested" as const }; const discovery = request.discoveryScope ? (yield* discoverScoped( @@ -722,8 +634,6 @@ export function makeIrohTrustBroker( grant_verification_keys: verificationKeys.keySet, }; }), - - issueRelayToken, }; } @@ -732,23 +642,17 @@ export const IrohTrustBrokerLive = Layer.effect( Effect.gen(function* () { return makeIrohTrustBroker( yield* IrohRepository, - yield* IrohRelayMinter, yield* IrohTrustBrokerConfig, yield* RelayRepository, ); }), ); -const IrohRelayMinterWithConfig = IrohRelayMinterLive.pipe( - Layer.provide(IrohTrustBrokerConfigLive), -); - export const IrohTrustBrokerRuntime = IrohTrustBrokerLive.pipe( Layer.provide(Layer.mergeAll( IrohRepositoryLive, RelayRepositoryLive, IrohTrustBrokerConfigLive, - IrohRelayMinterWithConfig, )), ); @@ -834,4 +738,3 @@ function bindingPlatform(binding: IrohBindingRecord): "mac" | "ios" { // Stack bearer authentication alone is never sufficient to mutate path hints. // Until the dedicated endpoint-signed monotonic update route lands, clients // refresh watch_addr output only through a new signed registration challenge. -export const IROH_SIGNED_PATH_HINT_UPDATE_FOLLOWUP = "endpoint-signed-monotonic-watch-addr-update-v1"; diff --git a/web/tests/client-config-env.test.ts b/web/tests/client-config-env.test.ts index 12e243610d42..dac37eb7fb50 100644 --- a/web/tests/client-config-env.test.ts +++ b/web/tests/client-config-env.test.ts @@ -21,8 +21,6 @@ const requiredIrohProductionEnv = { CMUX_IROH_GRANT_SIGNING_KEY_P8: `-----BEGIN PRIVATE KEY-----\n${"A".repeat(64)}\n-----END PRIVATE KEY-----`, CMUX_IROH_GRANT_SIGNING_KID: "current", CMUX_IROH_GRANT_VERIFICATION_KEYS_JSON: "{}", - CMUX_IROH_MINT_URL: "https://iroh-minter.example.com/api/relay-token", - CMUX_IROH_MINT_HMAC_SECRET_B64: Buffer.alloc(32, 0x33).toString("base64"), }; const requiredRelayProductionEnv = { @@ -174,25 +172,6 @@ describe("client config env validation", () => { expect(result.exitCode).toBe(0); }); - test("accepts the self-hosted relay path without the legacy hosted minter", () => { - const result = importEnv({ - ...requiredEnv, - VERCEL: "1", - VERCEL_ENV: "production", - CMUX_CLIENT_CONFIG_RATE_LIMIT_ID: "client-config-rule", - CMUX_ANALYTICS_RATE_LIMIT_ID: "analytics-rule", - CMUX_IROH_LAN_DISCOVERY_SECRET_B64: requiredIrohProductionEnv.CMUX_IROH_LAN_DISCOVERY_SECRET_B64, - CMUX_IROH_ACCOUNT_SUBJECT_SECRET_B64: requiredIrohProductionEnv.CMUX_IROH_ACCOUNT_SUBJECT_SECRET_B64, - CMUX_IROH_GRANT_SIGNING_KEY_P8: requiredIrohProductionEnv.CMUX_IROH_GRANT_SIGNING_KEY_P8, - CMUX_IROH_GRANT_SIGNING_KID: requiredIrohProductionEnv.CMUX_IROH_GRANT_SIGNING_KID, - CMUX_IROH_GRANT_VERIFICATION_KEYS_JSON: - requiredIrohProductionEnv.CMUX_IROH_GRANT_VERIFICATION_KEYS_JSON, - ...requiredSubrouterDeploymentEnv, - ...requiredRelayProductionEnv, - }); - - expect(result.exitCode).toBe(0); - }); test("allows explicit Vercel production without the optional Iroh limiter id", () => { const result = importEnv({ @@ -223,7 +202,6 @@ describe("client config env validation", () => { expect(result.exitCode).not.toBe(0); expect(result.stderr).toContain("CMUX_IROH_GRANT_SIGNING_KEY_P8 is required"); - expect(result.stderr).not.toContain("CMUX_IROH_MINT_HMAC_SECRET_B64 is required"); }); test("requires the self-hosted relay signing and rate-limit configuration in production", () => { @@ -254,59 +232,6 @@ describe("client config env validation", () => { expect(result.exitCode).toBe(0); }); - - test("allows an explicitly opted-in loopback HTTP relay minter only in local development", () => { - const result = inspectIrohMinterUrl({ - ...requiredEnv, - NODE_ENV: "development", - CMUX_IROH_DEV_ALLOW_INSECURE_LOOPBACK_MINTER: "1", - CMUX_IROH_MINT_URL: "http://localhost:49152/api/relay-token", - }); - - expect(result.exitCode).toBe(0); - expect(result.stdout).toBe("http://localhost:49152/api/relay-token"); - }); - - test("rejects a plaintext non-loopback relay minter in local development", () => { - const result = inspectIrohMinterUrl({ - ...requiredEnv, - NODE_ENV: "development", - CMUX_IROH_DEV_ALLOW_INSECURE_LOOPBACK_MINTER: "1", - CMUX_IROH_MINT_URL: "http://192.168.1.10:49152/api/relay-token", - }); - - expect(result.exitCode).not.toBe(0); - }); - - test("rejects the insecure loopback opt-in in Vercel preview and production", () => { - const preview = inspectIrohMinterUrl({ - ...requiredEnv, - NODE_ENV: "production", - VERCEL: "1", - VERCEL_ENV: "preview", - CMUX_IROH_DEV_ALLOW_INSECURE_LOOPBACK_MINTER: "1", - CMUX_IROH_MINT_URL: "http://localhost:49152/api/relay-token", - }); - expect(preview.exitCode).not.toBe(0); - - const production = inspectIrohMinterUrl({ - ...requiredEnv, - ...requiredIrohProductionEnv, - NODE_ENV: "production", - VERCEL: "1", - VERCEL_ENV: "production", - CMUX_CLIENT_CONFIG_RATE_LIMIT_ID: "client-config-rule", - CMUX_ANALYTICS_RATE_LIMIT_ID: "analytics-rule", - ...requiredSubrouterDeploymentEnv, - ...requiredRelayProductionEnv, - CMUX_IROH_DEV_ALLOW_INSECURE_LOOPBACK_MINTER: "1", - CMUX_IROH_MINT_URL: "http://localhost:49152/api/relay-token", - }); - expect(production.exitCode).not.toBe(0); - expect(production.stderr).toContain( - "CMUX_IROH_DEV_ALLOW_INSECURE_LOOPBACK_MINTER is only allowed in local development", - ); - }); }); function importEnv(env: Record): { exitCode: number; stderr: string } { @@ -323,35 +248,3 @@ function importEnv(env: Record): { exitCode: number; stderr: str stderr: result.stderr, }; } - -function inspectIrohMinterUrl( - env: Record, -): { exitCode: number; stdout: string; stderr: string } { - const result = spawnSync( - process.execPath, - [ - "--no-env-file", - "-e", - ` - const { irohTrustBrokerConfigFromEnv } = await import('./services/iroh/config'); - const { parseMinterUrl } = await import('./services/iroh/relayMinter'); - const config = irohTrustBrokerConfigFromEnv(); - const url = parseMinterUrl(config.relayMinterUrl, { - allowInsecureLoopback: config.relayMinterInsecureLoopbackOptIn, - deploymentEnvironment: config.deploymentEnvironment, - isVercelDeployment: config.isVercelDeployment, - }); - console.log(url.href); - `, - ], - { - env: env as NodeJS.ProcessEnv, - encoding: "utf8", - }, - ); - return { - exitCode: result.status ?? 1, - stdout: result.stdout.trim(), - stderr: result.stderr, - }; -} diff --git a/web/tests/iroh-db-behavior.test.ts b/web/tests/iroh-db-behavior.test.ts index 3dc55195279b..0d7d721d58e8 100644 --- a/web/tests/iroh-db-behavior.test.ts +++ b/web/tests/iroh-db-behavior.test.ts @@ -284,13 +284,6 @@ describe("Iroh trust broker database behavior", () => { }); const ios = await pairPeer(iosId); const mac = await pairPeer(macId); - const [issuance] = await requiredSql()>` - insert into iroh_relay_token_issuances ( - user_id, binding_id, endpoint_id_hash, status, requested_at - ) values (${userId}, ${macId}, ${"0f".repeat(32)}, 'pending', ${NOW}) - returning id::text - `; - if (!issuance) throw new Error("issuance insert failed"); await requiredSql()` insert into account_deletion_tombstones (user_id_hash, user_id, status, updated_at) values (${accountDeletionUserHash(userId)}, ${userId}, 'pending', now()) @@ -322,22 +315,6 @@ describe("Iroh trust broker database behavior", () => { notBefore: NOW, expiresAt: new Date(NOW.getTime() + 7 * 24 * 60 * 60 * 1_000), }), - repository.reserveRelayIssuance({ userId, bindingId: macId, now: NOW }), - repository.completeRelayIssuance({ - userId, - issuanceId: issuance.id, - bindingId: macId, - endpointId: mac.endpointId, - tokenHash: "10".repeat(32), - completedAt: NOW, - expiresAt: new Date(NOW.getTime() + 24 * 60 * 60 * 1_000), - }), - repository.failRelayIssuance({ - userId, - issuanceId: issuance.id, - completedAt: NOW, - failureCode: "test_failure", - }), ]; for (const operation of operations) { const exit = await Effect.runPromiseExit(operation); @@ -347,19 +324,16 @@ describe("Iroh trust broker database behavior", () => { const [state] = await requiredSql()>` select exists(select 1 from iroh_endpoint_bindings where id = ${macId} and revoked_at is not null) as revoked, (select count(*)::text from iroh_pair_grant_issuances where user_id = ${userId}) as grants, - (select status from iroh_relay_token_issuances where id = ${issuance.id}) as "issuanceStatus", (select count(*)::text from iroh_account_security_states where user_id = ${userId}) as "securityStates" `; expect(state).toEqual({ revoked: false, grants: "0", - issuanceStatus: "pending", securityStates: "0", }); }); @@ -1878,112 +1852,6 @@ describe("Iroh trust broker database behavior", () => { expect(String(exit)).toContain("IrohNotFoundError"); }); - dbTest("expires abandoned relay reservations before enforcing endpoint and account quotas", async () => { - const repo = requiredRepository(); - const endpointUserId = "user-relay-abandoned-endpoint"; - const endpointBindingId = await insertBinding({ - userId: endpointUserId, - endpointId: "63".repeat(32), - }); - for (let index = 0; index < 3; index += 1) { - await requiredSql()` - insert into iroh_relay_token_issuances ( - user_id, binding_id, endpoint_id_hash, status, requested_at - ) values ( - ${endpointUserId}, ${endpointBindingId}, ${"64".repeat(32)}, 'pending', - ${new Date(NOW.getTime() - 5 * 60 * 1_000 - index * 1_000)} - ) - `; - } - - await Effect.runPromise(repo.reserveRelayIssuance({ - userId: endpointUserId, - bindingId: endpointBindingId, - now: NOW, - })); - const endpointStatuses = await requiredSql()>` - select status, count(*)::text as total - from iroh_relay_token_issuances - where user_id = ${endpointUserId} - group by status - order by status - `; - expect(endpointStatuses).toEqual([ - { status: "expired", total: "3" }, - { status: "pending", total: "1" }, - ]); - - const accountUserId = "user-relay-abandoned-account"; - const accountBindingIds: string[] = []; - for (let index = 0; index < 10; index += 1) { - const bindingId = await insertBinding({ - userId: accountUserId, - endpointId: (0xa0 + index).toString(16).repeat(32), - }); - accountBindingIds.push(bindingId); - await requiredSql()` - insert into iroh_relay_token_issuances ( - user_id, binding_id, endpoint_id_hash, status, requested_at - ) - select - ${accountUserId}, ${bindingId}, ${"65".repeat(32)}, 'pending', - ${new Date(NOW.getTime() - 15 * 60 * 1_000)} - make_interval(secs => value) - from generate_series(1, 10) as values(value) - `; - } - - await Effect.runPromise(repo.reserveRelayIssuance({ - userId: accountUserId, - bindingId: accountBindingIds[0]!, - now: NOW, - })); - const accountStatuses = await requiredSql()>` - select status, count(*)::text as total - from iroh_relay_token_issuances - where user_id = ${accountUserId} - group by status - order by status - `; - expect(accountStatuses).toEqual([ - { status: "expired", total: "100" }, - { status: "pending", total: "1" }, - ]); - }); - - dbTest("fails relay finalization when revocation commits during provider mint", async () => { - const repo = requiredRepository(); - const endpointId = "61".repeat(32); - const bindingId = await insertBinding({ userId: "user-relay-race", endpointId }); - const reservation = await Effect.runPromise(repo.reserveRelayIssuance({ - userId: "user-relay-race", - bindingId, - now: NOW, - })); - expect(await Effect.runPromise(repo.revokeBinding({ - userId: "user-relay-race", - bindingId, - now: new Date(NOW.getTime() + 1_000), - }))).toEqual({ revoked: true, accountRevision: 1 }); - expect(await Effect.runPromise(repo.completeRelayIssuance({ - userId: "user-relay-race", - issuanceId: reservation.issuanceId, - bindingId, - endpointId, - tokenHash: "62".repeat(32), - completedAt: new Date(NOW.getTime() + 2_000), - expiresAt: new Date(NOW.getTime() + 24 * 60 * 60 * 1_000), - }))).toBe(false); - const [issuance] = await requiredSql()>` - select status, failure_code as "failureCode" - from iroh_relay_token_issuances - where id = ${reservation.issuanceId} - `; - expect(issuance).toEqual({ - status: "failed", - failureCode: "binding_inactive_after_mint", - }); - }); - dbTest("global retention clears revoked hints and expired private data from Aurora", async () => { const repo = requiredRepository(); const activeId = await insertBinding({ diff --git a/web/tests/iroh-model-crypto.test.ts b/web/tests/iroh-model-crypto.test.ts index fc1b70d9114b..e5ebae42628d 100644 --- a/web/tests/iroh-model-crypto.test.ts +++ b/web/tests/iroh-model-crypto.test.ts @@ -7,7 +7,6 @@ import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; import { assertCurrentSigningKey, - createOfflinePairSessionRecord, deriveAccountSubject, deriveLanRendezvousKey, parseVerificationKeys, @@ -16,7 +15,6 @@ import { signPairGrant, verifyEndpointAttestation, verifyEndpointRegistrationSignature, - verifyAndConsumeOfflineSameAccountPair, verifyPairGrant, type EndpointAttestationClaims, type PairGrantClaims, @@ -35,13 +33,6 @@ import { MANAGED_RELAY_URLS, parseRegistrationPayload, } from "../services/iroh/model"; -import { - parseMinterHmacSecret, - parseMinterUrl, - readBoundedMinterJson, - IrohRelayMinter, - IrohRelayMinterLive, -} from "../services/iroh/relayMinter"; const NOW = new Date("2026-07-09T20:00:00.000Z"); @@ -482,111 +473,6 @@ describe("Iroh grant verification keys and offline endpoint attestations", () => )).toThrow(); }); - test("requires two fresh endpoint-bound attestations with the same opaque account subject", () => { - const initiatorToken = signEndpointAttestation({ - privateKeyPem: currentPrivate, - kid: "current", - claims: initiator, - }); - const acceptorToken = signEndpointAttestation({ - privateKeyPem: currentPrivate, - kid: "current", - claims: acceptor, - }); - const expected = { - initiator: { ...endpointExpectation(initiator), platform: "ios" as const }, - acceptor: { ...endpointExpectation(acceptor), platform: "mac" as const }, - nowSeconds, - } as const; - const proof = Buffer.alloc(32, 0x61).toString("base64url"); - const session = createOfflinePairSessionRecord({ - sessionId: "70000000-0000-4000-8000-000000000001", - proof, - acceptor: expected.acceptor, - nowSeconds, - expiresAtSeconds: nowSeconds + 300, - }); - const invitation = { version: 1 as const, sessionId: session.sessionId, proof }; - - expect(verifyAndConsumeOfflineSameAccountPair({ - initiatorAttestation: initiatorToken, - acceptorAttestation: acceptorToken, - publicKeys: parsedKeys.publicKeys, - expected, - session, - invitation, - }).acceptor.endpointId).toBe(acceptor.endpointId); - expect(session.consumedAtSeconds).toBe(nowSeconds); - expect(() => verifyAndConsumeOfflineSameAccountPair({ - initiatorAttestation: initiatorToken, - acceptorAttestation: acceptorToken, - publicKeys: parsedKeys.publicKeys, - expected, - session, - invitation, - })).toThrow(); - - const missingSession = createOfflinePairSessionRecord({ - sessionId: "70000000-0000-4000-8000-000000000002", - proof, - acceptor: expected.acceptor, - nowSeconds, - expiresAtSeconds: nowSeconds + 300, - }); - expect(() => verifyAndConsumeOfflineSameAccountPair({ - initiatorAttestation: "", - acceptorAttestation: acceptorToken, - publicKeys: parsedKeys.publicKeys, - expected, - session: missingSession, - invitation: { ...invitation, sessionId: missingSession.sessionId }, - })).toThrow(); - expect(missingSession.consumedAtSeconds).toBeNull(); - - const wrongProofSession = createOfflinePairSessionRecord({ - sessionId: "70000000-0000-4000-8000-000000000004", - proof, - acceptor: expected.acceptor, - nowSeconds, - expiresAtSeconds: nowSeconds + 300, - }); - expect(() => verifyAndConsumeOfflineSameAccountPair({ - initiatorAttestation: initiatorToken, - acceptorAttestation: acceptorToken, - publicKeys: parsedKeys.publicKeys, - expected, - session: wrongProofSession, - invitation: { - version: 1, - sessionId: wrongProofSession.sessionId, - proof: Buffer.alloc(32, 0x62).toString("base64url"), - }, - })).toThrow(); - expect(wrongProofSession.consumedAtSeconds).toBeNull(); - - const otherAccountToken = signEndpointAttestation({ - privateKeyPem: currentPrivate, - kid: "current", - claims: { ...acceptor, sub: Buffer.alloc(32, 0x52).toString("base64url") }, - }); - const mismatchSession = createOfflinePairSessionRecord({ - sessionId: "70000000-0000-4000-8000-000000000003", - proof, - acceptor: expected.acceptor, - nowSeconds, - expiresAtSeconds: nowSeconds + 300, - }); - expect(() => verifyAndConsumeOfflineSameAccountPair({ - initiatorAttestation: initiatorToken, - acceptorAttestation: otherAccountToken, - publicKeys: parsedKeys.publicKeys, - expected, - session: mismatchSession, - invitation: { ...invitation, sessionId: mismatchSession.sessionId }, - })).toThrow(); - expect(mismatchSession.consumedAtSeconds).toBeNull(); - }); - test("rejects endpoint substitution, expiry, extra identity claims, and noncanonical signatures", () => { const token = signEndpointAttestation({ privateKeyPem: currentPrivate, @@ -637,188 +523,6 @@ describe("Iroh grant verification keys and offline endpoint attestations", () => }); }); -describe("Iroh relay minter response bounds", () => { - test("the production Live layer sends the canonical fetch body and HMAC", async () => { - const secret = Buffer.alloc(32, 0x63); - const originalFetch = globalThis.fetch; - let captured: { url: string; init: RequestInit } | undefined; - globalThis.fetch = (async (input: string | URL | Request, init?: RequestInit) => { - captured = { url: String(input), init: init ?? {} }; - return new Response(JSON.stringify({ - token: "a".repeat(64), - expiresAt: "2026-07-10T20:00:00.000Z", - }), { - status: 200, - headers: { "content-type": "application/json" }, - }); - }) as typeof fetch; - try { - const config: IrohTrustBrokerConfigShape = { - relayMinterUrl: "https://minter.cmux.test/api/relay-token", - relayMinterHmacSecretBase64: secret.toString("base64"), - relayMinterInsecureLoopbackOptIn: false, - deploymentEnvironment: "test", - isVercelDeployment: false, - }; - const layer = IrohRelayMinterLive.pipe( - Layer.provide(Layer.succeed(IrohTrustBrokerConfig, config)), - ); - const result = await Effect.runPromise( - Effect.gen(function* () { - const minter = yield* IrohRelayMinter; - return yield* minter.mint({ - endpointId: "ab".repeat(32), - lifetimeSeconds: 86_400, - now: NOW, - }); - }).pipe(Effect.provide(layer)), - ); - expect(result.token).toBe("a".repeat(64)); - } finally { - globalThis.fetch = originalFetch; - } - - const request = captured; - expect(request?.url).toBe("https://minter.cmux.test/api/relay-token"); - expect(request?.init.method).toBe("POST"); - expect(request?.init.redirect).toBe("error"); - const body = JSON.stringify({ endpointId: "ab".repeat(32), lifetimeSeconds: 86_400 }); - expect(request?.init.body).toBe(body); - const timestamp = String(Math.floor(NOW.getTime() / 1_000)); - const expectedSignature = createHmac("sha256", secret) - .update(`POST\n/api/relay-token\n${timestamp}\n${createHash("sha256").update(body).digest("hex")}`) - .digest("base64url"); - expect(new Headers(request?.init.headers).get("x-cmux-iroh-timestamp")).toBe(timestamp); - expect(new Headers(request?.init.headers).get("x-cmux-iroh-signature")).toBe(expectedSignature); - expect(new Headers(request?.init.headers).get("content-type")).toBe("application/json"); - }); - - test("preserves an invalid EndpointID as an input error", async () => { - const config: IrohTrustBrokerConfigShape = { - relayMinterUrl: "https://minter.cmux.test/api/relay-token", - relayMinterHmacSecretBase64: Buffer.alloc(32, 0x63).toString("base64"), - relayMinterInsecureLoopbackOptIn: false, - deploymentEnvironment: "test", - isVercelDeployment: false, - }; - const layer = IrohRelayMinterLive.pipe( - Layer.provide(Layer.succeed(IrohTrustBrokerConfig, config)), - ); - const exit = await Effect.runPromiseExit( - Effect.gen(function* () { - const minter = yield* IrohRelayMinter; - return yield* minter.mint({ - endpointId: "not-an-endpoint-id", - lifetimeSeconds: 86_400, - now: NOW, - }); - }).pipe(Effect.provide(layer)), - ); - - expect(exit._tag).toBe("Failure"); - const failure = exit._tag === "Failure" - ? Option.getOrUndefined(Cause.failureOption(exit.cause)) - : undefined; - expect((failure as { _tag?: string } | undefined)?._tag).toBe("IrohInvalidInputError"); - }); - - test("matches the Rust minter HMAC wire fixture", () => { - const fixture = JSON.parse(readFileSync( - new URL("../../tests/fixtures/iroh/relay-minter-request-v1.json", import.meta.url), - "utf8", - )) as { path: string; timestamp: string; body: string; signature: string }; - const bodyHash = createHash("sha256").update(fixture.body).digest("hex"); - const signature = createHmac("sha256", Buffer.alloc(32, 0x42)) - .update(`POST\n${fixture.path}\n${fixture.timestamp}\n${bodyHash}`, "utf8") - .digest("base64url"); - expect(fixture.path).toBe("/api/relay-token"); - expect(signature).toBe(fixture.signature); - }); - - test("requires a canonical 32-byte-or-longer HMAC secret", () => { - const valid = Buffer.alloc(32, 9).toString("base64"); - expect(parseMinterHmacSecret(valid)).toEqual(Buffer.alloc(32, 9)); - expect(() => parseMinterHmacSecret("%%%%" + valid)).toThrow(); - expect(() => parseMinterHmacSecret(Buffer.alloc(16, 9).toString("base64"))).toThrow(); - expect(() => parseMinterHmacSecret(Buffer.alloc(257, 9).toString("base64"))).toThrow(); - expect(() => parseMinterHmacSecret(Buffer.alloc(32, 0xff).toString("base64url"))).toThrow(); - }); - - test("allows plaintext only for opted-in local loopback minters", () => { - const localDevelopment = { - allowInsecureLoopback: true, - deploymentEnvironment: "development", - isVercelDeployment: false, - }; - expect(parseMinterUrl("https://minter.cmux.test/api/relay-token").pathname).toBe("/api/relay-token"); - for (const value of [ - "http://localhost:49152/api/relay-token", - "http://127.0.0.1:49152/api/relay-token", - "http://[::1]:49152/api/relay-token", - ]) { - expect(parseMinterUrl(value, localDevelopment).protocol).toBe("http:"); - } - for (const value of [ - "http://minter.cmux.test/api/relay-token", - "http://192.168.1.10:49152/api/relay-token", - "https://minter.cmux.test/api/relay-token/", - "https://minter.cmux.test/other", - "https://minter.cmux.test/api/relay-token?debug=1", - ]) { - expect(() => parseMinterUrl(value, localDevelopment)).toThrow(); - } - expect(() => parseMinterUrl("http://localhost:49152/api/relay-token", { - ...localDevelopment, - allowInsecureLoopback: false, - })).toThrow(); - expect(() => parseMinterUrl("http://localhost:49152/api/relay-token", { - ...localDevelopment, - deploymentEnvironment: "production", - })).toThrow(); - expect(() => parseMinterUrl("http://localhost:49152/api/relay-token", { - ...localDevelopment, - deploymentEnvironment: "preview", - isVercelDeployment: true, - })).toThrow(); - }); - - test("parses a bounded response", async () => { - const body = { token: "a".repeat(32), expiresAt: "2026-07-10T20:00:00.000Z" }; - expect(await readBoundedMinterJson(new Response(JSON.stringify(body), { - headers: { "content-type": "application/json" }, - }))).toEqual(body); - }); - - test("rejects a non-JSON or expanded minter response contract", async () => { - await expect(readBoundedMinterJson(new Response("{}"))).rejects.toThrow(); - await expect(readBoundedMinterJson(new Response(JSON.stringify({ - token: "a".repeat(32), - expiresAt: "2026-07-10T20:00:00.000Z", - servicesSecret: "must-not-appear", - }), { - headers: { "content-type": "application/json" }, - }))).rejects.toThrow(); - }); - - test("rejects oversized fixed-length and chunked responses", async () => { - await expect(readBoundedMinterJson(new Response("{}", { - headers: { "content-length": "999999", "content-type": "application/json" }, - }))).rejects.toThrow(); - - const chunk = new Uint8Array(20_000); - const stream = new ReadableStream({ - start(controller) { - controller.enqueue(chunk); - controller.enqueue(chunk); - controller.close(); - }, - }); - await expect(readBoundedMinterJson(new Response(stream, { - headers: { "content-type": "application/json" }, - }))).rejects.toThrow(); - }); -}); - function manuallySignedJws(header: unknown, claims: unknown, privateKey: CryptoKey | import("node:crypto").KeyObject): string { const encodedHeader = Buffer.from(JSON.stringify(header)).toString("base64url"); const encodedClaims = Buffer.from(JSON.stringify(claims)).toString("base64url"); diff --git a/web/tests/iroh-route-handler.test.ts b/web/tests/iroh-route-handler.test.ts index 4bb17b0a48ca..568f26d2b23d 100644 --- a/web/tests/iroh-route-handler.test.ts +++ b/web/tests/iroh-route-handler.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test"; import * as Effect from "effect/Effect"; -import { IrohDatabaseError, IrohQuotaExceededError } from "../services/iroh/errors"; +import { IrohDatabaseError } from "../services/iroh/errors"; import { buildConnectivityInvalidationRequest, handleIrohRoute, @@ -328,13 +328,11 @@ describe("Iroh route boundary", () => { issueEndpointAttestation: namespaced, revoke: namespaced, issuePairGrant: namespaced, - issueRelayToken: namespaced, }); const operations = [ "endpoint_attestation", "revoke", "pair_grant", - "relay_token", ] as const; for (const operation of operations) { const base = authedPost("/api/devices/iroh", {}); @@ -353,32 +351,12 @@ describe("Iroh route boundary", () => { ); expect(response.status).toBe(operation === "revoke" ? 200 : 201); } - expect(received).toEqual(Array(4).fill("dev.cmux.app.demo")); + expect(received).toEqual(Array(3).fill("dev.cmux.app.demo")); expect(receivedBindingIDs).toEqual( - Array(4).fill("123e4567-e89b-42d3-a456-426614174000"), + Array(3).fill("123e4567-e89b-42d3-a456-426614174000"), ); }); - test("maps DB-authoritative quota failures to typed 429 with Retry-After", async () => { - const response = await handleIrohRoute(authedPost("/api/devices/iroh/relay-token", { - bindingId: "30000000-0000-4000-8000-000000000001", - }), "relay_token", { - verify: async () => USER, - broker: broker({ - issueRelayToken: () => Effect.fail(new IrohQuotaExceededError({ - code: "relay_endpoint_10m_quota", - retryAfterSeconds: 417, - })), - }), - }); - expect(response.status).toBe(429); - expect(response.headers.get("retry-after")).toBe("417"); - expect(await response.json()).toEqual({ - error: "relay_endpoint_10m_quota", - retry_after_seconds: 417, - }); - }); - test("does not expose database implementation details in service failures", async () => { const response = await handleIrohRoute(authedPost("/api/devices/iroh/challenge", {}), "challenge", { verify: async () => USER, @@ -435,7 +413,6 @@ function broker(overrides: Partial = {}): IrohTrustBrokerS issueEndpointAttestation: unavailable, revoke: unavailable, issuePairGrant: unavailable, - issueRelayToken: unavailable, ...overrides, }; } diff --git a/web/tests/iroh-trust-broker.test.ts b/web/tests/iroh-trust-broker.test.ts index 8938329fbcb2..a4fe8dba85fb 100644 --- a/web/tests/iroh-trust-broker.test.ts +++ b/web/tests/iroh-trust-broker.test.ts @@ -15,10 +15,8 @@ import { IrohConflictError, IrohForbiddenError, IrohNotFoundError, - IrohRelayMintError, } from "../services/iroh/errors"; import { - IROH_RELAY_TOKEN_LIFETIME_SECONDS, MANAGED_RELAY_URLS, sha256, type IrohRegistrationPayload, @@ -33,9 +31,8 @@ import { type IrohChallengeRecord, type IrohRepositoryShape, } from "../services/iroh/repository"; -import type { IrohRelayMinterShape } from "../services/iroh/relayMinter"; import { makeIrohTrustBroker } from "../services/iroh/trustBroker"; -import { bindingMatchesDiscoveryScope } from "../services/iroh/discoveryScope"; +import type { IrohDiscoveryScope } from "../services/iroh/discoveryScope"; import type { RelayPreference } from "../services/relay/model"; const NOW = new Date("2026-07-09T20:00:00.000Z"); @@ -206,13 +203,13 @@ describe("Iroh build compatibility", () => { }); describe("Iroh trust broker registration", () => { - test("registers a valid endpoint proof and mints relay credentials after commit", async () => { + test("registers a valid endpoint proof and reports the relay credential unavailable", async () => { const fixture = makeFixture(); const request = await fixture.signedRegistration(); const result = await Effect.runPromise(fixture.broker.register(USER_A, request, NOW)) as { revision: number; binding: { endpoint_id: string }; - relay: { status: string; token: string }; + relay: { status: string }; discovery_complete: boolean; discovery: { revision: number; @@ -220,7 +217,7 @@ describe("Iroh trust broker registration", () => { }; }; expect(result.binding.endpoint_id).toBe(fixture.endpointId); - expect(result.relay.status).toBe("issued"); + expect(result.relay.status).toBe("unavailable"); expect(result.discovery.revision).toBe(result.revision); expect(result.discovery_complete).toBe(true); expect(result.discovery.bindings.map((binding) => binding.binding_id)) @@ -234,7 +231,6 @@ describe("Iroh trust broker registration", () => { observed_at: "2026-07-09T19:55:00.000Z", expires_at: "2026-07-09T20:45:00.000Z", }]); - expect(fixture.minter.calls).toBe(1); }); test("persists and publishes signed family-specific direct ports to the same account", async () => { @@ -345,16 +341,7 @@ describe("Iroh trust broker registration", () => { ]); }); - test("relay failure cannot roll back an authenticated registration", async () => { - const fixture = makeFixture({ minterFailure: true }); - const result = await Effect.runPromise( - fixture.broker.register(USER_A, await fixture.signedRegistration(), NOW), - ) as { relay: { status: string } }; - expect(result.relay.status).toBe("unavailable"); - expect(fixture.repository.bindings).toHaveLength(1); - }); - - test("does not mint another relay token when refreshing the same binding", async () => { + test("reports not_requested when refreshing the same binding", async () => { const fixture = makeFixture(); await Effect.runPromise(fixture.broker.register( USER_A, @@ -369,7 +356,6 @@ describe("Iroh trust broker registration", () => { )) as { relay: { status: string } }; expect(refreshed.relay.status).toBe("not_requested"); - expect(fixture.minter.calls).toBe(1); }); test("marks a truncated registration discovery page incomplete", async () => { @@ -1704,18 +1690,6 @@ describe("Iroh discovery and grants", () => { bindingBody, ), ), "IrohNotFoundError"); - await expectEffectFailure(fixture.broker.issueRelayToken( - USER_A, - bindingBody, - NOW, - "dev.cmux.app.beta", - fixture.bindingProof( - beta.id, - "POST", - "api/relay/token", - bindingBody, - ), - ), "IrohNotFoundError"); const pairBody = { initiatorBindingId: internal.id, acceptorBindingId: mac.id, @@ -1734,7 +1708,6 @@ describe("Iroh discovery and grants", () => { ), "IrohNotFoundError"); expect(internal.revokedAt).toBeNull(); - expect(fixture.minter.calls).toBe(0); expect(fixture.repository.pairGrantAudits).toHaveLength(0); }); @@ -1835,13 +1808,13 @@ describe("Iroh discovery and grants", () => { const fixture = makeFixture(); const active = binding({ userId: USER_A, platform: "ios" }); fixture.repository.bindings.push(active); - const noVerificationKeys = makeIrohTrustBroker(fixture.repository, fixture.minter, { + const noVerificationKeys = makeIrohTrustBroker(fixture.repository, { ...fixture.config, grantVerificationKeysJson: undefined, }); await expectEffectFailure(noVerificationKeys.discover(USER_A, NOW), "IrohConfigurationError"); - const noAccountSubject = makeIrohTrustBroker(fixture.repository, fixture.minter, { + const noAccountSubject = makeIrohTrustBroker(fixture.repository, { ...fixture.config, accountSubjectSecretBase64: undefined, }); @@ -1851,74 +1824,49 @@ describe("Iroh discovery and grants", () => { }); }); -describe("Iroh relay quotas", () => { - test("never calls the minter for an unregistered or revoked binding", async () => { - const fixture = makeFixture(); - await expectEffectFailure( - fixture.broker.issueRelayToken(USER_A, { bindingId: randomUUID() }, NOW), - "IrohNotFoundError", - ); - const revoked = binding({ userId: USER_A, revokedAt: NOW }); - fixture.repository.bindings.push(revoked); - await expectEffectFailure( - fixture.broker.issueRelayToken(USER_A, { bindingId: revoked.id }, NOW), - "IrohNotFoundError", - ); - expect(fixture.minter.calls).toBe(0); - }); - - test("treats authenticated relay renewal as binding activity", async () => { - const fixture = makeFixture(); - const active = binding({ - userId: USER_A, - lastSeenAt: new Date(NOW.getTime() - 48 * 60 * 60 * 1_000), - updatedAt: new Date(NOW.getTime() - 48 * 60 * 60 * 1_000), - }); - fixture.repository.bindings.push(active); - - await Effect.runPromise(fixture.broker.issueRelayToken( - USER_A, - { bindingId: active.id }, - NOW, - )); - - expect(active.lastSeenAt).toEqual(NOW); - expect(active.updatedAt).toEqual(NOW); - }); - - test("does not return a relay credential when the binding is revoked during mint", async () => { - const fixture = makeFixture(); - const active = binding({ userId: USER_A }); - fixture.repository.bindings.push(active); - fixture.minter.afterMint = () => { - active.revokedAt = NOW; - }; - - await expectEffectFailure( - fixture.broker.issueRelayToken(USER_A, { bindingId: active.id }, NOW), - "IrohNotFoundError", - ); - expect(fixture.repository.relayIssuances[0]?.status).toBe("failed"); - }); -}); - type MutableBinding = IrohBindingRecord & { userId: string; directPortV4: number | null; directPortV6: number | null; }; +// Mirrors the live repository's SQL discovery-scope filter for the in-memory +// fixture (the production filter lives in repository.ts SQL, not TypeScript). +function bindingMatchesDiscoveryScope( + binding: { + readonly deviceUuid: string; + readonly appInstanceId: string; + readonly tag: string; + readonly platform: string; + readonly pairingEnabled: boolean; + }, + scope: IrohDiscoveryScope, +): boolean { + const local = scope.localBinding; + if ( + binding.deviceUuid === local.deviceId + && binding.appInstanceId === local.appInstanceId + && binding.tag === local.tag + && binding.platform === local.platform + ) { + return true; + } + const peers = scope.peerBindings; + return binding.platform === peers.platform + && ( + peers.tags === undefined + || peers.tags.includes(binding.tag.toLowerCase()) + ) + && ( + peers.pairingEnabled === undefined + || binding.pairingEnabled === peers.pairingEnabled + ); +} + class MemoryRepository implements IrohRepositoryShape { readonly challenges: IrohChallengeRecord[] = []; readonly bindings: MutableBinding[] = []; readonly pairGrantAudits: unknown[] = []; - readonly relayIssuances: Array<{ - id: string; - userId: string; - bindingId: string; - requestedAt: Date; - status: string; - }> = []; private lanGenerations = new Map(); private routeRevisions = new Map(); beforeDiscoverySnapshot: (() => Promise) | undefined; @@ -2324,63 +2272,10 @@ class MemoryRepository implements IrohRepositoryShape { } return Effect.void; } - - reserveRelayIssuance(input: Parameters[0]) { - const active = this.bindings.find((row) => - row.id === input.bindingId && row.userId === input.userId && !row.revokedAt); - if (!active) return Effect.fail(new IrohNotFoundError({ resource: "binding" })); - if (active.clientNamespace !== (input.clientNamespace ?? "legacy")) { - return Effect.fail(new IrohNotFoundError({ resource: "binding" })); - } - active.lastSeenAt = input.now; - active.updatedAt = input.now; - const issuanceId = randomUUID(); - this.relayIssuances.push({ id: issuanceId, userId: input.userId, bindingId: active.id, requestedAt: input.now, status: "pending" }); - return Effect.succeed({ issuanceId, binding: active }); - } - - completeRelayIssuance(input: Parameters[0]) { - const row = this.relayIssuances.find((candidate) => candidate.id === input.issuanceId); - const active = this.bindings.find((candidate) => - candidate.id === input.bindingId && - candidate.userId === input.userId && - candidate.endpointId === input.endpointId && - !candidate.revokedAt); - if (!row || !active) { - if (row) row.status = "failed"; - return Effect.succeed(false); - } - row.status = "succeeded"; - return Effect.succeed(true); - } - - failRelayIssuance(input: Parameters[0]) { - const row = this.relayIssuances.find((candidate) => candidate.id === input.issuanceId); - if (row) row.status = "failed"; - return Effect.void; - } -} - -class FakeMinter implements IrohRelayMinterShape { - calls = 0; - afterMint: (() => void) | undefined; - constructor(private readonly fail: boolean) {} - - mint(input: Parameters[0]) { - this.calls += 1; - if (this.fail) return Effect.fail(new IrohRelayMintError({ code: "test_failure" })); - const result = { - token: `relay-token-${this.calls}-with-safe-length`, - expiresAt: new Date(input.now.getTime() + IROH_RELAY_TOKEN_LIFETIME_SECONDS * 1_000), - }; - this.afterMint?.(); - return Effect.succeed(result); - } } function makeFixture(options: { repository?: MemoryRepository; - minterFailure?: boolean; appInstanceId?: string; deviceId?: string; identityGeneration?: number; @@ -2399,7 +2294,6 @@ function makeFixture(options: { const endpointPublicDer = endpointKeys.publicKey.export({ format: "der", type: "spki" }); const endpointId = Buffer.from(endpointPublicDer).subarray(-32).toString("hex"); const repository = options.repository ?? new MemoryRepository(); - const minter = new FakeMinter(options.minterFailure ?? false); const appInstanceId = options.appInstanceId ?? randomUUID(); const deviceId = options.deviceId ?? randomUUID(); const identityGeneration = options.identityGeneration ?? 1; @@ -2424,22 +2318,18 @@ function makeFixture(options: { }, ], }), - relayMinterInsecureLoopbackOptIn: false, - deploymentEnvironment: "test", - isVercelDeployment: false, }; let relayPreference = options.relayPreference ?? { mode: "automatic" as const, selectedManagedRelayIds: [], customRelays: [], }; - const broker = makeIrohTrustBroker(repository, minter, config, { + const broker = makeIrohTrustBroker(repository, config, { getPreference: () => Effect.succeed({ preference: relayPreference, revision: 0 }), }); return { repository, - minter, broker, config, endpointId,