diff --git a/AGENTS.md b/AGENTS.md index 258a3f0..b98a244 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -167,6 +167,8 @@ cargo publish --dry-run --allow-dirty # before a release scripts/matrix.sh # the decline-matrix runner: the full suite per # feature build (none / git / session / tunnel / # sftp / --all-features), clippy ×2, fmt, doc +cargo test --manifest-path fuzz/shared/Cargo.toml # the fuzz corpus gate: the committed seeds replay + # the `fuzz/` sidecar's invariant batteries ``` If feature flags are added, also run `cargo test --all-features` and diff --git a/Cargo.toml b/Cargo.toml index da64bbb..9e1c5dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,10 @@ # Single-crate protocol/door crate (the alktty template). No workspace: # the composition story is deferred — alkssh's substance is the mappings -# (family ADR-003). +# (family ADR-003). The `fuzz/` sidecar is its own workspace (excluded — +# it pins its own nightly toolchain and never rides the root's). [workspace] members = ["."] +exclude = ["fuzz"] [package] name = "alkssh" @@ -17,8 +19,9 @@ categories = ["network-programming", "asynchronous"] # The package policy (the sibling crates' convention): the published crate # ships the architecture docs (the authoritative contract) but not the # process surface — reviews, research, plans, task files, the working -# agents' config, AGENTS.md. -exclude = [".opencode/", "AGENTS.md", "docs/reviews/", "docs/plans/", "docs/research/", "docs/sdd_process.md", "tasks/"] +# agents' config, AGENTS.md — and not the `fuzz/` sidecar (its own +# workspace; never a root dep, never a shipped surface). +exclude = [".opencode/", "AGENTS.md", "docs/reviews/", "docs/plans/", "docs/research/", "docs/sdd_process.md", "tasks/", "fuzz/"] [lib] name = "alkssh" diff --git a/fuzz/.gitignore b/fuzz/.gitignore new file mode 100644 index 0000000..c9f423d --- /dev/null +++ b/fuzz/.gitignore @@ -0,0 +1,5 @@ +artifacts/ +coverage/ +target/ +/corpus/*/* +!/corpus/*/seed-* \ No newline at end of file diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock new file mode 100644 index 0000000..008799e --- /dev/null +++ b/fuzz/Cargo.lock @@ -0,0 +1,3649 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aead" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1973cfbc1a2daf9cf550e74e1f088c28e7f7d8c1e1418fb6c9dc5184b7e84c99" +dependencies = [ + "crypto-common 0.2.2", + "inout", +] + +[[package]] +name = "aes" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35f0f96ce78e38c3dc6d8948aa8163d06385be74000f3c7a95bf1eef35d3ea32" +dependencies = [ + "cipher", + "cpubits", + "cpufeatures 0.3.1", + "zeroize", +] + +[[package]] +name = "aes-gcm" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2b8006a0c83f52b62ba44a97b58bf76fe2f70a329e588f67f89691d93d498f" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ctutils", + "ghash", + "zeroize", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "serde", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "alkcall" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "690d747f42baf385dc22cd120f79d43cf1ea239bc426675e0e1e225b57fc031a" +dependencies = [ + "async-trait", + "bytes", + "futures", + "getrandom 0.4.3", + "jsonschema", + "parking_lot", + "serde", + "serde_json", + "thiserror", + "tokio", + "tracing", + "uuid", + "zeroize", +] + +[[package]] +name = "alkgit" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81d274e66dc4977171951a3cdf63f7a1b87179387a486e58130662d4b456ab6" +dependencies = [ + "alkcall", + "async-trait", + "bstr", + "bytes", + "futures", + "gix-hash", + "gix-packetline", + "gix-protocol", + "gix-transport", + "gix-validate", + "parking_lot", + "serde", + "serde_json", + "thiserror", + "tokio", + "tokio-util", +] + +[[package]] +name = "alkssh" +version = "0.1.0" +dependencies = [ + "alkcall", + "alktty", + "alkvault", + "async-trait", + "bytes", + "parking_lot", + "russh", + "serde_json", + "thiserror", + "tokio", + "tracing", + "zeroize", +] + +[[package]] +name = "alkssh-fuzz" +version = "0.0.0" +dependencies = [ + "alkssh-fuzz-shared", + "libfuzzer-sys", +] + +[[package]] +name = "alkssh-fuzz-shared" +version = "0.0.0" +dependencies = [ + "alkgit", + "alkssh", + "russh", + "serde_json", + "zeroize", +] + +[[package]] +name = "alktty" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2265167842b7d3ea09bc235f48450d3dcf22049a2ca71f8155de00acbf97c226" +dependencies = [ + "alkcall", + "async-trait", + "bytes", + "futures", + "futures-core", + "libc", + "serde", + "serde_json", + "thiserror", + "tokio", + "tokio-stream", + "tracing", +] + +[[package]] +name = "alkvault" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e032b074f7fea71ea15528994c5b3a6503c082c519045bb3b0519281291918" +dependencies = [ + "aes-gcm", + "base64", + "bip39", + "ed25519-bip32", + "hmac 0.12.1", + "rand 0.10.3", + "serde", + "serde_json", + "sha2 0.10.9", + "thiserror", + "zeroize", +] + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +dependencies = [ + "libc", +] + +[[package]] +name = "arbitrary" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bc62ac97cc33321f50863d514c3bc38a453947a8f9e781137e47c7401020aed" + +[[package]] +name = "argon2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134c52ddac6d63c576bef8168db10c83c49c26444ecbc68060fef078925a901c" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures 0.3.1", + "password-hash", +] + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "async-trait" +version = "0.1.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "aws-lc-rs" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "048006790404103c488e6c5616b3e7e702a71292aadda08e8f7698a9e0d2eb53" +dependencies = [ + "aws-lc-sys", + "untrusted", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bfbe32ac36b3c428696ce315464181bc4029d689fd58e3f2a4e58c252242f52" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", + "pkg-config", +] + +[[package]] +name = "base16ct" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" + +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bcrypt-pbkdf" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "144e573728da132683b9488acd528274c790e07fc06ff81ee29f9d8f8b1041e0" +dependencies = [ + "blowfish", + "pbkdf2", + "sha2 0.11.1", +] + +[[package]] +name = "bip39" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90dbd31c98227229239363921e60fcf5e558e43ec69094d46fc4996f08d1d5bc" +dependencies = [ + "bitcoin_hashes", + "rand 0.8.8", + "rand_core 0.6.4", + "serde", + "unicode-normalization", + "zeroize", +] + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitcoin_hashes" +version = "0.14.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bca4c7abb40c8817d77403c880988cfd484f23ab2365726afb2f798363e2c4a2" +dependencies = [ + "hex-conservative", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" + +[[package]] +name = "blake2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b5d4d889834ee8ecfc0f8426ad30faf7cdcb10f741a8e6d7224d95325479f6f" +dependencies = [ + "digest 0.11.3", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array 0.14.7", +] + +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", + "zeroize", +] + +[[package]] +name = "block-padding" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "710f1dd022ef4e93f8a438b4ba958de7f64308434fa6a87104481645cc30068b" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "blowfish" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62ce3946557b35e71d1bbe07ec385073ce9eda05043f95de134eb578fcf1a298" +dependencies = [ + "byteorder", + "cipher", +] + +[[package]] +name = "borrow-or-share" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c" + +[[package]] +name = "bstr" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f" +dependencies = [ + "memchr", + "regex-automata", + "serde_core", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytecount" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" + +[[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" + +[[package]] +name = "cbc" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce2dc9ee5f88d11e0beb842c88b33c8a5cf0d1329c4b19494af42b07dbfe8896" +dependencies = [ + "cipher", +] + +[[package]] +name = "cc" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86e055fa6e3e40ad6187ba25b6855eeb635d5271f8e0f0e1e9a5826b8d75bea0" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chacha20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.3.1", + "rand_core 0.10.1", + "zeroize", +] + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "cipher" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" +dependencies = [ + "block-buffer 0.12.1", + "crypto-common 0.2.2", + "inout", + "zeroize", +] + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-bigint" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c035cb64cba03dad2fb921f3e218785e8cf9a41790da5eb3a1400f62fa2be11" +dependencies = [ + "cpubits", + "ctutils", + "getrandom 0.4.3", + "hybrid-array", + "num-traits", + "rand_core 0.10.1", + "serdect", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array 0.14.7", + "typenum", +] + +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "getrandom 0.4.3", + "hybrid-array", + "rand_core 0.10.1", +] + +[[package]] +name = "cryptoxide" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f700a42beeaab09edbc57140591aaa0b889283c806e88fd81be2d37742014d" + +[[package]] +name = "ctr" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baaca1c4b237092596f64d571e9db6ce4109c4ef9742e27590f1709594461f21" +dependencies = [ + "cipher", +] + +[[package]] +name = "ctutils" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2376ffd6f96d8a9db59875b4b6b0237d6900b5baf64279714e05493ce64828a" +dependencies = [ + "cmov", + "subtle", +] + +[[package]] +name = "curve25519-dalek" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "curve25519-dalek-derive", + "digest 0.11.3", + "fiat-crypto", + "rand_core 0.10.1", + "rustc_version", + "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 2.0.119", +] + +[[package]] +name = "data-encoding" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" + +[[package]] +name = "defmt" +version = "0.3.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4deef977660e373c458df605a913d4ab121db0ccd18d1ad6976dff7d37cddb53" +dependencies = [ + "defmt 1.2.0", +] + +[[package]] +name = "defmt" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a0795ac740cb54d7b3775c9c17345f6aaecf884c01733b39d6017738e0e3e1" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "840bb056bd99c495c9188198a258ab787c79f306d864db3e6e8451442d6d8366" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "defmt-parser" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71958f69fb2de856c5dd51ac9377e986ca1aa5919f931bf5395a807277f889a0" +dependencies = [ + "thiserror", +] + +[[package]] +name = "delegate" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "780eb241654bf097afb00fc5f054a09b687dad862e485fdcf8399bb056565370" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "der" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a996a108feb8b427a6e12fcf6af4aa83409b79d72c15337d7741368131ee004" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "des" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916a94e407b54f9034d71dd748234cd1e516ced6284009906ae246f177eafe5a" +dependencies = [ + "cipher", +] + +[[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", + "const-oid", + "crypto-common 0.2.2", + "ctutils", +] + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "ecdsa" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0681a4fc24c767085329728d8dfba959af91228aa4610cca4f8ce317ba46ae0" +dependencies = [ + "der", + "digest 0.11.3", + "elliptic-curve", + "rfc6979", + "signature", + "spki", + "zeroize", +] + +[[package]] +name = "ed25519" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-bip32" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10c212afef25445afd5cd3fc05941661b0473e681d7a39991b46c2821dbb9ba6" +dependencies = [ + "cryptoxide", +] + +[[package]] +name = "ed25519-dalek" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ebaa1a2bf1290ab3bfe5a7b771d050ebffab2711c19a81691c683a5144a25de" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core 0.10.1", + "serde", + "sha2 0.11.1", + "signature", + "subtle", + "zeroize", +] + +[[package]] +name = "elliptic-curve" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900f37e7e18d74a2347389af6cd69e3a30d5cc9e697f40004865679aa75e9c7c" +dependencies = [ + "base16ct", + "crypto-bigint", + "crypto-common 0.2.2", + "digest 0.11.3", + "ff", + "group", + "hkdf", + "hybrid-array", + "pem-rfc7468", + "pkcs8", + "rand_core 0.10.1", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "email_address" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" +dependencies = [ + "serde", +] + +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "equivalent" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fancy-regex" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e1dacd0d2082dfcf1351c4bdd566bbe89a2b263235a2b50058f1e130a47277" +dependencies = [ + "bit-set", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "faster-hex" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04839bdf9d8c10f66806fad16b852fc72aab80873aebc3cb69d85b4fa41543ed" +dependencies = [ + "autocfg", + "defmt 0.3.101", + "heapless", + "serde", +] + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "ff" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f686ab92a9fb0eaf188f6c6c87b89490baa6fdb0db4544ba4dc47f7942489f" +dependencies = [ + "rand_core 0.10.1", + "subtle", +] + +[[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.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c66915cc5391097de7a2b0b972e8505aa33c0df401fd3a6811dbdb32881ba48" + +[[package]] +name = "fluent-uri" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc74ac4d8359ae70623506d512209619e5cf8f347124910440dbc221714b328e" +dependencies = [ + "borrow-or-share", + "ref-cast", + "serde", +] + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "fraction" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e076045bb43dac435333ed5f04caf35c7463631d0dae2deb2638d94dd0a5b872" +dependencies = [ + "lazy_static", + "num", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-executor" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[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.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[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 = "generic-array" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "337d46834ee672ab3e48caca2cb0c78cc174fb12b3a68d0d88f99a0519a5e36e" +dependencies = [ + "generic-array 0.14.7", + "rustversion", + "typenum", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[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 0.10.1", + "wasm-bindgen", +] + +[[package]] +name = "ghash" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eecf2d5dc9b66b732b97707a0210906b1d30523eb773193ab777c0c84b3e8d5" +dependencies = [ + "polyval", + "zeroize", +] + +[[package]] +name = "gix-actor" +version = "0.44.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78243d3371019288a40a75ba1a58de2815c32591fc87b9f7cb01918bce364ca8" +dependencies = [ + "bstr", + "gix-date", + "gix-error", +] + +[[package]] +name = "gix-chunk" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6a775aba54f0d65698126cc4214b6bf8ddb208312e4ef4240dfe34b0697b87" +dependencies = [ + "gix-error", +] + +[[package]] +name = "gix-command" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df63215f103299169e239dfe76f5cfa4a3d6d4adf69a01774085e3e0423cba34" +dependencies = [ + "bstr", + "gix-error", + "gix-path", + "gix-quote", + "gix-trace", +] + +[[package]] +name = "gix-commitgraph" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfede9936d4b789867c09722576aea4ceb0e3aacf962eef035ebab0686b41a80" +dependencies = [ + "bstr", + "gix-chunk", + "gix-error", + "gix-hash", + "memmap2", + "nonempty", +] + +[[package]] +name = "gix-config-value" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cdada8bd9c97dc6522505c08226d9c88f0a1286f2c06b9e1bf51c3bf7ad7b2b" +dependencies = [ + "bitflags 2.13.2", + "bstr", + "gix-error", + "gix-path", + "gix-utils", + "libc", +] + +[[package]] +name = "gix-credentials" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8487f709886267337ee39231a5e68f3c6a4e28fd59e056fc9e66e9638941c633" +dependencies = [ + "bstr", + "gix-command", + "gix-config-value", + "gix-date", + "gix-error", + "gix-path", + "gix-prompt", + "gix-quote", + "gix-sec", + "gix-trace", + "gix-url", + "percent-encoding", +] + +[[package]] +name = "gix-date" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c538190012dbb536df54734ec38cc1109270c73f2f76585c011dd5b0c9f087c" +dependencies = [ + "bstr", + "gix-error", + "itoa", + "jiff", +] + +[[package]] +name = "gix-error" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87ff4b3f3daf01234d38d176b0ec00e92de514cc0e3fb8a8898c5cb7ccd22bb" +dependencies = [ + "bstr", +] + +[[package]] +name = "gix-fs" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808f23d897ddfe847d9b7206bda947901885be623bdf96b226a2dccf87f018c7" +dependencies = [ + "bstr", + "gix-error", + "gix-parallel", + "gix-path", + "gix-utils", + "libc", + "walkdir", +] + +[[package]] +name = "gix-hash" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4802ffb87e051795822bfdaa8ab9075b23e316684f7054e6ce4a246b2b76f4e" +dependencies = [ + "faster-hex", + "gix-error", + "gix-utils", + "sha1dc", +] + +[[package]] +name = "gix-hashtable" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f66822aee11ccb28d6a2e63b587254e578fb52a138bd5806b58d8560125592b" +dependencies = [ + "gix-hash", + "hashbrown 0.17.1", + "parking_lot", +] + +[[package]] +name = "gix-lock" +version = "26.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64783de116c477c5ea4a7f3b56e18ab363dabfdae2724291ea0587b642cadb3d" +dependencies = [ + "gix-error", + "gix-fs", + "gix-tempfile", + "gix-utils", +] + +[[package]] +name = "gix-macros" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f11f5429ca73cc3758c9f53af56a9fba100a8701c5b4bc30e29b8b0b15288991" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "gix-negotiate" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ddfb0d6da849bd938d929336e07c1f26970d9f6ec59621df74ec3f79c128ea" +dependencies = [ + "bitflags 2.13.2", + "gix-commitgraph", + "gix-date", + "gix-error", + "gix-hash", + "gix-object", + "gix-revwalk", +] + +[[package]] +name = "gix-object" +version = "0.66.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beaf9086dd00b9d2a177ca3ae83210ce926cee7c0faa112e053851221cd8f94d" +dependencies = [ + "bstr", + "gix-actor", + "gix-date", + "gix-error", + "gix-hash", + "gix-hashtable", + "gix-utils", + "gix-validate", + "itoa", + "smallvec", +] + +[[package]] +name = "gix-packetline" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ebb59ef7aff9d435946a5e5c3d9cfb673be7665839fd72961c08c733a934fb7" +dependencies = [ + "bstr", + "faster-hex", + "futures-io", + "futures-lite", + "gix-error", + "gix-trace", + "pin-project-lite", +] + +[[package]] +name = "gix-parallel" +version = "0.50.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58e7e10f8343e05f6ad8651f97dffa1caca1531bb274bfc8e3050e80e3ed5946" +dependencies = [ + "gix-trace", +] + +[[package]] +name = "gix-path" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83976e926a485e6fb4b8d9a0d7a30c53c1f607b81b5d310f06f92fd8385bf16a" +dependencies = [ + "bstr", + "gix-error", + "gix-trace", + "gix-validate", +] + +[[package]] +name = "gix-prompt" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d9650eedebb85e55577831db217403333416573356de722a040415c416f1cec" +dependencies = [ + "gix-command", + "gix-config-value", + "gix-error", + "parking_lot", + "rustix", +] + +[[package]] +name = "gix-protocol" +version = "0.67.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "832b5dc46a3c0a6f6fa8f8e571c11074add4b545c6789799ae6d6c36ae1a294e" +dependencies = [ + "async-trait", + "bstr", + "futures-io", + "futures-lite", + "gix-credentials", + "gix-date", + "gix-error", + "gix-hash", + "gix-lock", + "gix-macros", + "gix-negotiate", + "gix-object", + "gix-ref", + "gix-refspec", + "gix-revwalk", + "gix-shallow", + "gix-trace", + "gix-transport", + "gix-utils", + "nonempty", +] + +[[package]] +name = "gix-quote" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2413e65271303e9b674ec4785d469df4c3c36e888dbd165e3035de0405c6d9d1" +dependencies = [ + "bstr", + "gix-error", + "gix-utils", +] + +[[package]] +name = "gix-ref" +version = "0.69.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68686e4697b9cd15d1b152ef51df870513210ed22938eacc13b3672b76035f13" +dependencies = [ + "gix-actor", + "gix-error", + "gix-fs", + "gix-hash", + "gix-lock", + "gix-object", + "gix-parallel", + "gix-path", + "gix-tempfile", + "gix-utils", + "gix-validate", + "memmap2", +] + +[[package]] +name = "gix-refspec" +version = "0.47.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2569309da6fdc230973cf5b846508154c0e813fda60f2b2da768cc5655ed312c" +dependencies = [ + "bstr", + "gix-error", + "gix-hash", + "gix-validate", + "smallvec", +] + +[[package]] +name = "gix-revwalk" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "800479fbbf1e7f94f663e07943a91add5499343e7aac611a81e6a0460fd44af6" +dependencies = [ + "gix-commitgraph", + "gix-date", + "gix-error", + "gix-hash", + "gix-hashtable", + "gix-object", + "smallvec", +] + +[[package]] +name = "gix-sec" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9598d1f35baec7038c2d278bfcc18f1d8951f41dcc3f51ab6175661dfe5a6137" +dependencies = [ + "bitflags 2.13.2", + "gix-path", + "libc", + "windows-sys", +] + +[[package]] +name = "gix-shallow" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d805949578724b5164c90ec6d19567832ccaa5ba8a3fe26f7befc52007e2407" +dependencies = [ + "bstr", + "gix-error", + "gix-hash", + "gix-lock", + "nonempty", +] + +[[package]] +name = "gix-tempfile" +version = "26.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fdb135a043d6106b59bc58d59a326b277e495ec26fb9e02a1e2b6354bf4b5a" +dependencies = [ + "gix-fs", + "libc", + "parking_lot", + "tempfile", + "windows-sys", +] + +[[package]] +name = "gix-trace" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c477c029518d3f235c26db21ff7cccc1fa13c0c31b05c005a55b7affd4ffbd81" + +[[package]] +name = "gix-transport" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e43d6b0097ea9f2f266d5d6c43b8f615ed3da7c686ec8771cd17c1cb33cb0de" +dependencies = [ + "async-trait", + "bstr", + "futures-io", + "futures-lite", + "gix-command", + "gix-error", + "gix-packetline", + "gix-path", + "gix-quote", + "gix-sec", + "gix-trace", + "gix-url", + "gix-utils", + "pin-project-lite", +] + +[[package]] +name = "gix-url" +version = "0.40.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "def12e1016b872f1094442882f627905c59bd73480435291a4958cf373a8f28d" +dependencies = [ + "bstr", + "gix-error", + "gix-path", + "gix-utils", + "percent-encoding", +] + +[[package]] +name = "gix-utils" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11a9446060447e9ec58f5d333b98bed53389dfa10de5d8dd46fc76d01dfc2954" +dependencies = [ + "bstr", + "fastrand", + "getrandom 0.4.3", + "prodash", + "unicode-normalization", +] + +[[package]] +name = "gix-validate" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b617c17754bf37ee7a9b48e33ffb806abecbbed779ba415199ac1480acf699" +dependencies = [ + "bstr", + "gix-error", +] + +[[package]] +name = "group" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fd1a1c7a5206c5b7a3f5a0d7ccd3ff85d0c8f5133d62a02680255b0004af5f4" +dependencies = [ + "ff", + "rand_core 0.10.1", + "subtle", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-conservative" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3fef046dca3ca91ee1408a8c1b80ab777e80a4d308d1bf4e7adb3fcb047e08" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hex-literal" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1" + +[[package]] +name = "hkdf" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018" +dependencies = [ + "hmac 0.13.0", +] + +[[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 = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest 0.11.3", +] + +[[package]] +name = "hybrid-array" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" +dependencies = [ + "ctutils", + "subtle", + "typenum", + "zeroize", +] + +[[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.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe090a42a0d3140fed8c63263ed1fecc02422cb2f4bfe55cd8de2de1b8e105dd" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25d29b57c17c6528d0dc4c2141cfa2d08200465acb537a3909c8b9b8a4a984c0" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "404a3b8c486f205c87fe7f23ab6894d84a4ad76d6e4671ae1981ba7def87c599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20f0088bfb7441f7a37fa0c9e20cb1e9cba1cac6b9ce8cd2a6dc44294f8e8cbc" + +[[package]] +name = "icu_properties" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ea83d991c56158f7cf898bf86436c1dfe7c07ee82fe88951f2695c9f37a379f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98f29af82d02b163fe50161388350e743bb3107cc8e8c2f7314cb505faddfcf" + +[[package]] +name = "icu_provider" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413099315636c588ee8f011190b329b971a09a8fae9d157e01dcae8379468514" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[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 = "inout" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b5cf4372d022092674a83b8febf5865f7a573cc0c5baec5e49c879b6a20978f" +dependencies = [ + "block-padding", + "hybrid-array", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jiff" +version = "0.2.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b005715dcbeb0089a3c0dab99f2ff1cc3b2525323552703d648585d342a383" +dependencies = [ + "defmt 1.2.0", + "jiff-core", + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-link", +] + +[[package]] +name = "jiff-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e52fe76043ccecc9005d2305ebaadf7d7fc0cc89ca6baa10a94d6bc68c7128c" +dependencies = [ + "defmt 1.2.0", + "log", +] + +[[package]] +name = "jiff-static" +version = "0.2.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cc9817253cf7c7ee4684451bd327e88d6f3658014e54a29198625590650695c" +dependencies = [ + "jiff-core", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa8377070c6bae868759445e5a77f66d84f0b72f3a054bfb00e6d038b8282da7" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dc09d2154098bbf4ef54eab6b117a3c24cbbffe94046b16ee6d6d4510584d3a" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "jsonschema" +version = "0.46.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0a699d3e77675e6aa4bfffe3b907c8b5f7ed3241f9965bffb25475ad4b08d05" +dependencies = [ + "ahash", + "bytecount", + "data-encoding", + "email_address", + "fancy-regex", + "fraction", + "getrandom 0.3.4", + "idna", + "itoa", + "jsonschema-regex", + "num-cmp", + "num-traits", + "percent-encoding", + "referencing", + "regex", + "serde", + "serde_json", + "unicode-general-category", + "uuid-simd", +] + +[[package]] +name = "jsonschema-regex" +version = "0.46.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbd1086b01b9349fd4ef9a07433965af64c8ce8159abe633a189e4ff817bd13" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "keccak" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8f198d1db720e4940b5a493201d199d9f24f568f8f746bd13706243a2f71598" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", +] + +[[package]] +name = "kem" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01737161ba802849cfd486b5bd209d38ba4943494c249a8126005170c7621edd" +dependencies = [ + "crypto-common 0.2.2", + "rand_core 0.10.1", +] + +[[package]] +name = "lazy_static" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb" + +[[package]] +name = "libc" +version = "0.2.191" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9fd2f41a1cba099f79a0b6b6c35656cf7c03351a7bae8ff0f28f25270f929d2" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + +[[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.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "md5" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebb8d8732c6a6df3d8f032a82911cfc747e00efb95cc46e8d0acd5b5b88570c" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "memmap2" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b0ca1155a9ecc25aa12e8313a119f32edf0255fea2f44839c4ac0a12b2ac157" +dependencies = [ + "libc", +] + +[[package]] +name = "micromap" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a86d3146ed3995b5913c414f6664344b9617457320782e64f0bb44afd49d74" + +[[package]] +name = "mio" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1788edb87fdc09c7e26304471e2f5be8cdefb1b6930d6e3985fc02ff53bf86ee" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "ml-kem" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9ba01ebcf90f6aff8e9b9f55bb69b19e6250e4ade5787d0040eaa27dd226238" +dependencies = [ + "hybrid-array", + "kem", + "module-lattice", + "pkcs8", + "rand_core 0.10.1", + "sha3", + "shake", +] + +[[package]] +name = "module-lattice" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c61b87c9683ab7cb1c6871d261ad5479b6b10ceb52c4352aaca3b5d35a8febe" +dependencies = [ + "ctutils", + "hybrid-array", + "num-traits", +] + +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags 2.13.2", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nonempty" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9737e026353e5cd0736f98eddae28665118eb6f6600902a7f50db585621fecb6" + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint 0.4.8", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[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-bigint" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93e7820bc0a80a0238e650327316f929ba18d5be054b647490a3a6a339f3e7c0" +dependencies = [ + "num-integer", + "num-traits", + "rand 0.10.3", + "rand_core 0.10.1", +] + +[[package]] +name = "num-cmp" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63335b2e2c34fae2fb0aa2cecfd9f0832a1e24b3b32ecec612c3426d46dc8aaa" + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint 0.4.8", + "num-integer", + "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 = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + +[[package]] +name = "p256" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c9239b2dbc807adbbe147e8cf72ea7450c3a0aabe62cb8e75ff4ec22e1f72a" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primefield", + "primeorder", + "sha2 0.11.1", +] + +[[package]] +name = "p384" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d17b851e6b3e378ab4ecb07fa2ed23f4d15f075735f8fec9fa1e7bdce5f8301f" +dependencies = [ + "ecdsa", + "elliptic-curve", + "fiat-crypto", + "primefield", + "primeorder", + "sha2 0.11.1", +] + +[[package]] +name = "p521" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ad64cc32c2dc466317c12ee5853e61f159f9eab1fe7efade0395dc2e7b43449" +dependencies = [ + "base16ct", + "ecdsa", + "elliptic-curve", + "primefield", + "primeorder", + "sha2 0.11.1", +] + +[[package]] +name = "pageant" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0bc922d9aa8cfcf598de27b3c6122cb2eeda9d6fe71527a22ebcb7f633540ff" +dependencies = [ + "base16ct", + "byteorder", + "bytes", + "delegate", + "futures", + "log", + "rand 0.10.3", + "sha2 0.11.1", + "thiserror", + "tokio", + "windows", + "windows-strings", +] + +[[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 = "password-hash" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aab41826031698d6ffcd9cff78ef56ef998e39dc7e5067cdfebe373842d4723b" +dependencies = [ + "phc", +] + +[[package]] +name = "pbkdf2" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629" +dependencies = [ + "digest 0.11.3", + "hmac 0.13.0", +] + +[[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 = "phc" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44dc769b75f93afdddd8c7fa12d685292ddeff1e66f7f0f3a234cf1818afe892" +dependencies = [ + "base64ct", + "ctutils", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkcs5" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63d440a804ec8d6fafbb6b84471e013286658d373248927692ab3366686220ca" +dependencies = [ + "aes", + "aes-gcm", + "cbc", + "der", + "pbkdf2", + "rand_core 0.10.1", + "scrypt", + "sha2 0.11.1", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" +dependencies = [ + "der", + "pkcs5", + "rand_core 0.10.1", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" + +[[package]] +name = "poly1305" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2d0073b297041425c7c3df6eb4792d598a15323fe63346852b092eca02904c" +dependencies = [ + "cpufeatures 0.3.1", + "universal-hash", + "zeroize", +] + +[[package]] +name = "polyval" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0fa31d631f2b2cb2a544d0aa321ce847a94764d701ca2becc411138b93d49cd" +dependencies = [ + "cpubits", + "cpufeatures 0.3.1", + "universal-hash", + "zeroize", +] + +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" + +[[package]] +name = "portable-atomic-util" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "primefield" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c555a6e4eb7d4e158fcb028c835c3b8642206ddc279b5c6b202ef9a8bdb592f4" +dependencies = [ + "crypto-bigint", + "crypto-common 0.2.2", + "ff", + "rand_core 0.10.1", + "subtle", + "zeroize", +] + +[[package]] +name = "primeorder" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c9f42978c78a00e3d68f69fc03e57a234debae69da4020a4fb588fcdcd07b06" +dependencies = [ + "elliptic-curve", + "once_cell", + "primefield", + "serdect", + "wnaf", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prodash" +version = "31.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf52d87428247c5aa3760ac8e7d8b13ec7b3579b955a8c3f311668b6860acba5" +dependencies = [ + "parking_lot", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +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.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" +dependencies = [ + "libc", + "rand_chacha", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.13.2", +] + +[[package]] +name = "ref-cast" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "referencing" +version = "0.46.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fbf332a2f81899f6836f22c03da73dae8a664c32e3016b84692c23cddadc95d" +dependencies = [ + "ahash", + "fluent-uri", + "getrandom 0.3.4", + "hashbrown 0.16.1", + "itoa", + "micromap", + "parking_lot", + "percent-encoding", + "serde_json", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +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 = "rfc6979" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4a459cddafb3fe76b31fd8f1108007566c40301feb64dc7b54656eb7388172b" +dependencies = [ + "crypto-bigint", + "hmac 0.13.0", +] + +[[package]] +name = "russh" +version = "0.65.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bb77a4b259312b2abbc541bca1c13cb4e9b61c560a7fd894068c3b92e0c9dc3" +dependencies = [ + "aes", + "aws-lc-rs", + "bitflags 2.13.2", + "block-padding", + "byteorder", + "bytes", + "cbc", + "cipher", + "crypto-bigint", + "ctr", + "curve25519-dalek", + "data-encoding", + "delegate", + "der", + "digest 0.11.3", + "ecdsa", + "ed25519-dalek", + "elliptic-curve", + "enum_dispatch", + "futures", + "generic-array 1.4.5", + "getrandom 0.4.3", + "ghash", + "hex-literal", + "hmac 0.13.0", + "inout", + "keccak", + "log", + "md5", + "ml-kem", + "module-lattice", + "num-bigint 0.5.1", + "p256", + "p384", + "p521", + "pageant", + "pbkdf2", + "pkcs5", + "pkcs8", + "polyval", + "rand 0.10.3", + "rand_core 0.10.1", + "russh-cryptovec", + "russh-util", + "salsa20", + "scrypt", + "sec1", + "sha1", + "sha2 0.11.1", + "sha3", + "signature", + "spki", + "ssh-encoding", + "ssh-key", + "subtle", + "thiserror", + "tokio", + "typenum", + "universal-hash", + "zeroize", +] + +[[package]] +name = "russh-cryptovec" +version = "0.62.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3aec6cb630dbe85d72ffd7bcd95f07e1bd69f9f270ee8adfa1afe443a6331438" +dependencies = [ + "log", + "nix", + "ssh-encoding", + "windows-sys", +] + +[[package]] +name = "russh-util" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668424a5dde0bcb45b55ba7de8476b93831b4aa2fa6947e145f3b053e22c60b6" +dependencies = [ + "chrono", + "tokio", + "wasm-bindgen", + "wasm-bindgen-futures", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5e76742e5294153d4dde1207dbec5bdd81eef6fabdc0d2118babadabfd858aa" +dependencies = [ + "bitflags 2.13.2", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "salsa20" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f874456e72520ff1375a06c588eaf074b0f01f9e9e1aada45bd9b7954a6e42c" +dependencies = [ + "cfg-if", + "cipher", +] + +[[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 = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scrypt" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87af57419b594aa23fa95f09f0e06d80d84ba01c26148c43844cad6ff4485f0" +dependencies = [ + "cfg-if", + "pbkdf2", + "salsa20", + "sha2 0.11.1", +] + +[[package]] +name = "sec1" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56d437c2f19203ce5f7122e507831de96f3d2d4d3be5af44a0b0a09d8a80e4d" +dependencies = [ + "base16ct", + "ctutils", + "der", + "hybrid-array", + "subtle", + "zeroize", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "serde_json" +version = "1.0.154" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[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" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "digest 0.11.3", +] + +[[package]] +name = "sha1dc" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5b0ebf6537e3e8db8bb7d21ecb6d7399738c953c757945c6b95e46c4e95df7b" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d7069beb7d6ac7b9acd1039986e73443f24234f41074da099d6f994ac9ad19" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "digest 0.11.3", +] + +[[package]] +name = "sha3" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc9bad02c26382724b2d2692c6f179285e4b54eeecd7968f52a50059c3c11759" +dependencies = [ + "digest 0.11.3", + "keccak", + "sponge-cursor", +] + +[[package]] +name = "shake" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09057cb2149ad4cbd2da1e26b351f9a4c354219421229c69c3063e6f61947c4a" +dependencies = [ + "digest 0.11.3", + "keccak", + "sponge-cursor", +] + +[[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" +dependencies = [ + "digest 0.11.3", + "rand_core 0.10.1", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d5d4b1981b8bbbb7aed204cae5726d683d5d6ac7e74ec54122ea28f315e7c8" + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "spki" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef958a98b9d5da290cfc78946e9f3e61e1e62a18db0d92cac0b83cc161491a9" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sponge-cursor" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620" + +[[package]] +name = "ssh-cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d801accda99469cde6d73da741422610fdf6508a72d9a69d1b55cb241c720597" +dependencies = [ + "aead", + "aes", + "aes-gcm", + "chacha20", + "cipher", + "ctutils", + "des", + "poly1305", + "ssh-encoding", + "zeroize", +] + +[[package]] +name = "ssh-encoding" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b54d0ed0498daf3f78d82e00e28c8eec9d75a067c4cfbcc7a0f7d0f4077749e" +dependencies = [ + "base64ct", + "bytes", + "ctutils", + "digest 0.11.3", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "ssh-key" +version = "0.7.0-rc.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a32fae177b74a22aa9c5b01bf7e68b33545be32d9e381e248058d2adc15ce3" +dependencies = [ + "argon2", + "bcrypt-pbkdf", + "ctutils", + "ed25519-dalek", + "hex", + "hmac 0.13.0", + "p256", + "p384", + "p521", + "rand_core 0.10.1", + "sec1", + "sha1", + "sha2 0.11.1", + "signature", + "ssh-cipher", + "ssh-encoding", + "zeroize", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[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.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys", +] + +[[package]] +name = "thiserror" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "tinystr" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abc840c238154d58f9e86b2792261edfde565a17696e1cda1a449de27979801a" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" + +[[package]] +name = "tokio" +version = "1.54.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b52fd3255258d26fac7264ab5a76b53c13367136dc011d3d6ad4f8ed070396a3" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "tokio-stream" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e464cf451ba96ebfc6f9b6542f17ee8b8956e33f1e40d9690624e59d7a7f8a4b" +dependencies = [ + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "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 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-general-category" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f" + +[[package]] +name = "unicode-ident" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" + +[[package]] +name = "unicode-normalization" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75cd3fb03d5288b3ba9a1ee103ac7e9cbb59f0b03d64f8ddfcc04dd42758a85d" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "universal-hash" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4987bdc12753382e0bec4a65c50738ffaabc998b9cdd1f952fb5f39b0048a96" +dependencies = [ + "crypto-common 0.2.2", + "ctutils", +] + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "utf8_iter" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af3bd9d3e571c26fcce9d033b87ea5ab061b8d2ffe9b7a5675d810d68f03edf0" +dependencies = [ + "utf_types", +] + +[[package]] +name = "utf_types" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3b425286980a367278453f3b6d28cdcb134b574e5a35824a6ff5eebad50bb6" + +[[package]] +name = "uuid" +version = "1.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0e2d11620c50695fe12bbbfde96b3477fe606e7284c22d505c12ce51214514" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "uuid-rng-internal", + "wasm-bindgen", +] + +[[package]] +name = "uuid-rng-internal" +version = "1.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdf60f3d36825d0911af7e102da39cc817280560cad48e81df1eb5c4d7400f76" +dependencies = [ + "getrandom 0.4.3", +] + +[[package]] +name = "uuid-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b082222b4f6619906941c17eb2297fff4c2fb96cb60164170522942a200bd8" +dependencies = [ + "outref", + "vsimd", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + +[[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 = "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.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0181c14ddd6c7b8b0b0132a5f1ea2f32be0d893cd3ea97b6e3b28886b8a3d1c" +dependencies = [ + "cfg-if", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e14e0327cb5368ad60ccb22d04a9827a693c95a926d8d125e9fa39e0baec501" +dependencies = [ + "js-sys", + "tokio", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e931a02a39abdbf66966fc35902b5ed58344750a70885bed4ebb3d8eaf28ed9" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ca4b54280e455bd831e33a6b8e734baf432fe63fbc35c42ac14ce67a043a53a" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.9", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b1f9c3798725016c6ee5f2f57869ec0831e8bbc0ab0310a86e787e97f41389e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[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 2.0.119", +] + +[[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 2.0.119", +] + +[[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-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.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[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 = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wnaf" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "795ca18b3fdb5e62bf982199278341ddcf7ebf7d32e25e212ad05d496e95f6fa" +dependencies = [ + "ff", + "group", + "hybrid-array", + "primefield", +] + +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + +[[package]] +name = "yoke" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "417ef3fcf6c300b7dbd25c85531a746931c02ad536f3cf4786ed1e88223340b3" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5fe1f8f1b06191a00962174c61aa5005e0bb391a6d80d07e24d115c01a92ed8" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863ad3ac83293fb4d740aedbfdc9240dd8d1a50c1099acd76ce80ce7c7230c7f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[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.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879" +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 2.0.119", +] + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f58a2799cfe9a1670c7ea8edbd52ef8edb93cb37a7f83bf31ba9e35d9dae6a40" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3743ddbf47738d929cd1ae2e199d40d0162979dfe31aef8ae646d2bf00bf97a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 0000000..e444186 --- /dev/null +++ b/fuzz/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "alkssh-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" +alkssh-fuzz-shared = { path = "shared" } + +[[bin]] +name = "exec_parse" +path = "fuzz_targets/exec_parse.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "fingerprint" +path = "fuzz_targets/fingerprint.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "key_decode" +path = "fuzz_targets/key_decode.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "terminal_params" +path = "fuzz_targets/terminal_params.rs" +test = false +doc = false +bench = false + +[workspace] \ No newline at end of file diff --git a/fuzz/README.md b/fuzz/README.md new file mode 100644 index 0000000..2a2a702 --- /dev/null +++ b/fuzz/README.md @@ -0,0 +1,71 @@ +# alkssh fuzzing + +libFuzzer targets for alkssh's ingress surfaces — the family standing +rule (anything worth fuzzing gets fuzzed), landed per the plan's wave-5 +charter and the alkgit convention: the JSON schema gate itself is +alkcall's surface, family-fuzzed there; SSH framing, key exchange, and +the channel multiplexer are russh's wire (family ADR-003) — the +alkssh-owned post-framing ingress is what fuzzes here. + +## Layout + +- `fuzz_targets/` — one binary per target; thin `fuzz_target!` wrappers. +- `shared/` — the invariant logic, as a plain library so normal + `cargo test` (stable toolchain) can replay the committed corpora + through the same invariants (`#[cfg(test)]` modules; quinn's CI + pattern). The fuzz binaries are nightly-only; the shared crate is + stable-clean. It consumes alkssh's public API only (plus the three + `#[doc(hidden)]` fuzz-visible exposures: the exec-grammar parse path + in `src/dispatch.rs`, the key-decode arm `decode_key_arm` in + `src/key_source.rs`, and the `terminal_params` conversion in + `src/tty_map.rs` — the recorded exposure decisions: the ingresses are + production-private, so the exposures are the production surfaces + themselves, never forks; the fingerprint ingress is fully public API) + and must never become a dependency of the root crate. +- `corpus//` — committed hand-made seeds (regenerate with + `python3 fuzz/gen_fuzz_seeds.py` from the repo root — must be a no-op + diff on the committed set). Only `seed-*` files are tracked; grown + corpora (SHA-named units), artifacts, and coverage output are + gitignored and are **not** merged back into the committed seeds. +- `run-detached.sh` — mandatory runner for agent sessions: wraps + `cargo fuzz run` in `setsid` + `nohup` + log redirection so an OOM + in a target can never take down the agent host. +- `gen_fuzz_seeds.py` — deterministic seed generator (quiche pattern). + +## Targets + +| Target | Drives | Invariants | +|---|---|---| +| `exec_parse` | the production exec-grammar parse (`parse_exec`, `src/dispatch.rs` — the handler's ingress shape: byte string → `from_utf8` fallback `""` → parse; the pre-parse `max_exec_command_len` cap and the git row's `invalid-repo-id` gate are the root contract rows' territory, the parse is everything behind them) | no-panic on any byte string (any length behind the cap); the typed routing classes (git-parseable → the git row, non-git → the session pipe path, service-only → decline-with-reason) agree with a from-spec grammar oracle (the differential pair) and are stable per input; for `Git` routes the repo-id gate classification is deterministic | +| `fingerprint` | the crate's single fingerprint path ([`alkssh::fingerprint`] over `russh::keys::parse_public_key_base64` — ADR-004 decision 1) | no-panic on arbitrary key blobs (a non-blob is the parse's typed refusal); every accepted key's fingerprint is exactly the `SHA256:` + unpadded-standard-base64 contract shape (43-char body, canonical alphabet, decodes to a 32-byte digest, re-encodes canonically); determinism; the committed `ssh-keygen -lf` parity vectors still agree (the corpus-gate test row) | +| `key_decode` | the decrypt-at-use chain's decode arm (`decode_key_arm`, `src/key_source.rs` — the production `decrypt_and_decode` conversion exposed `#[doc(hidden)]`) + the underlying `decode_secret_key` passphrase arm (robustness drive — the production chain always passes `None`) | no-panic on arbitrary plaintext shapes (the vault's `String` shape, lossy from arbitrary bytes) and passphrase shapes; every failure on the production arm is exactly the typed `KeySourceError::Decode` class — never a catch-all, never a panic; the failure text never carries the plaintext; the `Zeroizing` posture rides the drive (the wrap is what the adapter seam applies); determinism | +| `terminal_params` | the `pty_request` args → `TerminalParamsWire` conversion (`terminal_params`, `src/tty_map.rs` — OQ-44's SSH modes → reserved-modes `{}` JSON) | no-panic on arbitrary term strings, u32 dimensions, and mode blobs (the modes are never parsed — reserved); the dimension clamp is exactly the recorded cap (`min(value, 65535)`, never a magnitude truncation); `term` preserved verbatim; the reserved-modes JSON is always exactly `{}`; the full serialized wire shape is deterministic per input | + +## Commands + +`cargo fuzz build` and `cargo fuzz run` must be executed with `fuzz/` +(or deeper) as the working directory so rustup selects the pinned +nightly toolchain — the detached runner handles that itself. + +```sh +# build (nightly, pinned by rust-toolchain.toml inside fuzz/; run from fuzz/) +cargo fuzz build + +# agent sessions: detached campaign (never foreground; CWD-independent) +FUZZ_RUNTIME_SECS=600 fuzz/run-detached.sh [extra libfuzzer args...] + +# corpus replay through the invariants (stable toolchain, no nightly) +cargo test --manifest-path fuzz/shared/Cargo.toml + +# coverage +cargo fuzz coverage +``` + +The fuzz workspace is excluded from the main workspace (`exclude` in +the root `Cargo.toml`); it pins its own nightly toolchain via +`rust-toolchain.toml` and does not affect the root workspace's stable +toolchain or MSRV. + +Campaign cadence: the corpus gate (above) runs in every verification +pass; the fuzzing sessions themselves run at the wave gate and before +publish (`fuzz/run-detached.sh per target`, bounded runtime). \ No newline at end of file diff --git a/fuzz/corpus/exec_parse/seed-000 b/fuzz/corpus/exec_parse/seed-000 new file mode 100644 index 0000000..952bcf4 --- /dev/null +++ b/fuzz/corpus/exec_parse/seed-000 @@ -0,0 +1 @@ +git-upload-pack '/owner/demo' \ No newline at end of file diff --git a/fuzz/corpus/exec_parse/seed-001 b/fuzz/corpus/exec_parse/seed-001 new file mode 100644 index 0000000..26c0e0f --- /dev/null +++ b/fuzz/corpus/exec_parse/seed-001 @@ -0,0 +1 @@ +git-receive-pack '/owner/demo' \ No newline at end of file diff --git a/fuzz/corpus/exec_parse/seed-002 b/fuzz/corpus/exec_parse/seed-002 new file mode 100644 index 0000000..5b779c1 --- /dev/null +++ b/fuzz/corpus/exec_parse/seed-002 @@ -0,0 +1 @@ +git-upload-pack /r \ No newline at end of file diff --git a/fuzz/corpus/exec_parse/seed-003 b/fuzz/corpus/exec_parse/seed-003 new file mode 100644 index 0000000..3bb8627 --- /dev/null +++ b/fuzz/corpus/exec_parse/seed-003 @@ -0,0 +1 @@ +git-receive-pack /owner/demo.git \ No newline at end of file diff --git a/fuzz/corpus/exec_parse/seed-004 b/fuzz/corpus/exec_parse/seed-004 new file mode 100644 index 0000000..948a3a1 --- /dev/null +++ b/fuzz/corpus/exec_parse/seed-004 @@ -0,0 +1 @@ +git-upload-pack /owner/demo.git \ No newline at end of file diff --git a/fuzz/corpus/exec_parse/seed-005 b/fuzz/corpus/exec_parse/seed-005 new file mode 100644 index 0000000..3686859 --- /dev/null +++ b/fuzz/corpus/exec_parse/seed-005 @@ -0,0 +1 @@ +git-send-pack '/r' \ No newline at end of file diff --git a/fuzz/corpus/exec_parse/seed-006 b/fuzz/corpus/exec_parse/seed-006 new file mode 100644 index 0000000..0fa8833 --- /dev/null +++ b/fuzz/corpus/exec_parse/seed-006 @@ -0,0 +1 @@ +git-upload-archive '/r' \ No newline at end of file diff --git a/fuzz/corpus/exec_parse/seed-007 b/fuzz/corpus/exec_parse/seed-007 new file mode 100644 index 0000000..039130e --- /dev/null +++ b/fuzz/corpus/exec_parse/seed-007 @@ -0,0 +1 @@ +git-send-pack \ No newline at end of file diff --git a/fuzz/corpus/exec_parse/seed-008 b/fuzz/corpus/exec_parse/seed-008 new file mode 100644 index 0000000..cebac04 --- /dev/null +++ b/fuzz/corpus/exec_parse/seed-008 @@ -0,0 +1 @@ +git-upload-pack \ No newline at end of file diff --git a/fuzz/corpus/exec_parse/seed-009 b/fuzz/corpus/exec_parse/seed-009 new file mode 100644 index 0000000..a97c371 --- /dev/null +++ b/fuzz/corpus/exec_parse/seed-009 @@ -0,0 +1 @@ +git-receive-pack \ No newline at end of file diff --git a/fuzz/corpus/exec_parse/seed-010 b/fuzz/corpus/exec_parse/seed-010 new file mode 100644 index 0000000..edfc2e6 --- /dev/null +++ b/fuzz/corpus/exec_parse/seed-010 @@ -0,0 +1 @@ +git-upload-pack '' \ No newline at end of file diff --git a/fuzz/corpus/exec_parse/seed-011 b/fuzz/corpus/exec_parse/seed-011 new file mode 100644 index 0000000..1bbaeb2 --- /dev/null +++ b/fuzz/corpus/exec_parse/seed-011 @@ -0,0 +1 @@ +git-upload-pack '/' \ No newline at end of file diff --git a/fuzz/corpus/exec_parse/seed-012 b/fuzz/corpus/exec_parse/seed-012 new file mode 100644 index 0000000..647ec60 --- /dev/null +++ b/fuzz/corpus/exec_parse/seed-012 @@ -0,0 +1 @@ +git-upload-pack '/.git' \ No newline at end of file diff --git a/fuzz/corpus/exec_parse/seed-013 b/fuzz/corpus/exec_parse/seed-013 new file mode 100644 index 0000000..689fb71 --- /dev/null +++ b/fuzz/corpus/exec_parse/seed-013 @@ -0,0 +1 @@ +git-upload-pack '/r.git.git' \ No newline at end of file diff --git a/fuzz/corpus/exec_parse/seed-014 b/fuzz/corpus/exec_parse/seed-014 new file mode 100644 index 0000000..5bad0ce --- /dev/null +++ b/fuzz/corpus/exec_parse/seed-014 @@ -0,0 +1 @@ +git-upload-pack '/a/b/c' \ No newline at end of file diff --git a/fuzz/corpus/exec_parse/seed-015 b/fuzz/corpus/exec_parse/seed-015 new file mode 100644 index 0000000..5e8a4df --- /dev/null +++ b/fuzz/corpus/exec_parse/seed-015 @@ -0,0 +1 @@ +uptime \ No newline at end of file diff --git a/fuzz/corpus/exec_parse/seed-016 b/fuzz/corpus/exec_parse/seed-016 new file mode 100644 index 0000000..e69de29 diff --git a/fuzz/corpus/exec_parse/seed-017 b/fuzz/corpus/exec_parse/seed-017 new file mode 100644 index 0000000..26fb899 --- /dev/null +++ b/fuzz/corpus/exec_parse/seed-017 @@ -0,0 +1 @@ +git-upload-packrel \ No newline at end of file diff --git a/fuzz/corpus/exec_parse/seed-018 b/fuzz/corpus/exec_parse/seed-018 new file mode 100644 index 0000000..5242a53 --- /dev/null +++ b/fuzz/corpus/exec_parse/seed-018 @@ -0,0 +1 @@ +git-upload-pack rel \ No newline at end of file diff --git a/fuzz/corpus/exec_parse/seed-019 b/fuzz/corpus/exec_parse/seed-019 new file mode 100644 index 0000000..ea91033 --- /dev/null +++ b/fuzz/corpus/exec_parse/seed-019 @@ -0,0 +1 @@ +GIT-UPLOAD-PACK '/r' \ No newline at end of file diff --git a/fuzz/corpus/exec_parse/seed-020 b/fuzz/corpus/exec_parse/seed-020 new file mode 100644 index 0000000..152a6a1 --- /dev/null +++ b/fuzz/corpus/exec_parse/seed-020 @@ -0,0 +1 @@ +git-upload-pack$ '/r' \ No newline at end of file diff --git a/fuzz/corpus/exec_parse/seed-021 b/fuzz/corpus/exec_parse/seed-021 new file mode 100644 index 0000000..5d2b47b --- /dev/null +++ b/fuzz/corpus/exec_parse/seed-021 @@ -0,0 +1 @@ +git-upload-pack '/r' \ No newline at end of file diff --git a/fuzz/corpus/exec_parse/seed-022 b/fuzz/corpus/exec_parse/seed-022 new file mode 100644 index 0000000..0d2bb02 --- /dev/null +++ b/fuzz/corpus/exec_parse/seed-022 @@ -0,0 +1 @@ +git-upload-pack'r' \ No newline at end of file diff --git a/fuzz/corpus/exec_parse/seed-023 b/fuzz/corpus/exec_parse/seed-023 new file mode 100644 index 0000000..0b956fc --- /dev/null +++ b/fuzz/corpus/exec_parse/seed-023 @@ -0,0 +1 @@ +git-upload-pack '/r \ No newline at end of file diff --git a/fuzz/corpus/exec_parse/seed-024 b/fuzz/corpus/exec_parse/seed-024 new file mode 100644 index 0000000..f1da6da --- /dev/null +++ b/fuzz/corpus/exec_parse/seed-024 @@ -0,0 +1 @@ +git-upload-pack ' \ No newline at end of file diff --git a/fuzz/corpus/exec_parse/seed-025 b/fuzz/corpus/exec_parse/seed-025 new file mode 100644 index 0000000..434228f --- /dev/null +++ b/fuzz/corpus/exec_parse/seed-025 @@ -0,0 +1 @@ +git-upload-pack '/é' \ No newline at end of file diff --git a/fuzz/corpus/exec_parse/seed-026 b/fuzz/corpus/exec_parse/seed-026 new file mode 100644 index 0000000..62a926f --- /dev/null +++ b/fuzz/corpus/exec_parse/seed-026 @@ -0,0 +1 @@ +git-upload-pack '/日本語' \ No newline at end of file diff --git a/fuzz/corpus/exec_parse/seed-027 b/fuzz/corpus/exec_parse/seed-027 new file mode 100644 index 0000000..4df9dae Binary files /dev/null and b/fuzz/corpus/exec_parse/seed-027 differ diff --git a/fuzz/corpus/exec_parse/seed-028 b/fuzz/corpus/exec_parse/seed-028 new file mode 100644 index 0000000..a9df2bc --- /dev/null +++ b/fuzz/corpus/exec_parse/seed-028 @@ -0,0 +1 @@ +git-upload-pack ' ' \ No newline at end of file diff --git a/fuzz/corpus/exec_parse/seed-029 b/fuzz/corpus/exec_parse/seed-029 new file mode 100644 index 0000000..8797f12 --- /dev/null +++ b/fuzz/corpus/exec_parse/seed-029 @@ -0,0 +1 @@ +git-upload-pack ''' \ No newline at end of file diff --git a/fuzz/corpus/exec_parse/seed-030 b/fuzz/corpus/exec_parse/seed-030 new file mode 100644 index 0000000..1c971ff --- /dev/null +++ b/fuzz/corpus/exec_parse/seed-030 @@ -0,0 +1 @@ +git-upload-pack '//r' \ No newline at end of file diff --git a/fuzz/corpus/exec_parse/seed-031 b/fuzz/corpus/exec_parse/seed-031 new file mode 100644 index 0000000..bb69d56 --- /dev/null +++ b/fuzz/corpus/exec_parse/seed-031 @@ -0,0 +1 @@ +git-upload-pack /r.git.git' \ No newline at end of file diff --git a/fuzz/corpus/exec_parse/seed-032 b/fuzz/corpus/exec_parse/seed-032 new file mode 100644 index 0000000..47d11b7 --- /dev/null +++ b/fuzz/corpus/exec_parse/seed-032 @@ -0,0 +1 @@ +git-upload-pack '/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' \ No newline at end of file diff --git a/fuzz/corpus/exec_parse/seed-033 b/fuzz/corpus/exec_parse/seed-033 new file mode 100644 index 0000000..b1138f1 --- /dev/null +++ b/fuzz/corpus/exec_parse/seed-033 @@ -0,0 +1 @@ +git-upload-pack /bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb.git \ No newline at end of file diff --git a/fuzz/corpus/exec_parse/seed-034 b/fuzz/corpus/exec_parse/seed-034 new file mode 100644 index 0000000..db74e20 Binary files /dev/null and b/fuzz/corpus/exec_parse/seed-034 differ diff --git a/fuzz/corpus/exec_parse/seed-035 b/fuzz/corpus/exec_parse/seed-035 new file mode 100644 index 0000000..6ac1327 --- /dev/null +++ b/fuzz/corpus/exec_parse/seed-035 @@ -0,0 +1 @@ +git-upload-pack '/r' \ No newline at end of file diff --git a/fuzz/corpus/fingerprint/seed-000 b/fuzz/corpus/fingerprint/seed-000 new file mode 100644 index 0000000..a3008ea --- /dev/null +++ b/fuzz/corpus/fingerprint/seed-000 @@ -0,0 +1 @@ +AAAAC3NzaC1lZDI1NTE5AAAAIM28sP5UAl3npJJ8trZgPt2kE+Af6477/Rx6J2VbM14z alkssh-w1-fixture-alpha \ No newline at end of file diff --git a/fuzz/corpus/fingerprint/seed-001 b/fuzz/corpus/fingerprint/seed-001 new file mode 100644 index 0000000..1969ac3 --- /dev/null +++ b/fuzz/corpus/fingerprint/seed-001 @@ -0,0 +1 @@ +AAAAC3NzaC1lZDI1NTE5AAAAIMvu9S+hH9Dug7fdiHLeW+B9kee+i495jPAdBH8YaOvQ alkssh-w1-fixture-bravo \ No newline at end of file diff --git a/fuzz/corpus/fingerprint/seed-002 b/fuzz/corpus/fingerprint/seed-002 new file mode 100644 index 0000000..92aa060 --- /dev/null +++ b/fuzz/corpus/fingerprint/seed-002 @@ -0,0 +1 @@ +AAAAC3NzaC1lZDI1NTE5AAAAIKL8zAzLkR0jKFY2l+QrBjC3Cv4Q3OBUUCX7rQV1jXQe alkssh-w1-fixture-charlie \ No newline at end of file diff --git a/fuzz/corpus/fingerprint/seed-003 b/fuzz/corpus/fingerprint/seed-003 new file mode 100644 index 0000000..c2c7dad --- /dev/null +++ b/fuzz/corpus/fingerprint/seed-003 @@ -0,0 +1 @@ +AAAAC3NzaC1lZDI1NTE5AAAAICS7Ub4A073+G450Nt2DUOwWyniGVU/lrlUoQZAUg1sd \ No newline at end of file diff --git a/fuzz/corpus/fingerprint/seed-004 b/fuzz/corpus/fingerprint/seed-004 new file mode 100644 index 0000000..1c8ac87 --- /dev/null +++ b/fuzz/corpus/fingerprint/seed-004 @@ -0,0 +1 @@ +not a key \ No newline at end of file diff --git a/fuzz/corpus/fingerprint/seed-005 b/fuzz/corpus/fingerprint/seed-005 new file mode 100644 index 0000000..e69de29 diff --git a/fuzz/corpus/fingerprint/seed-006 b/fuzz/corpus/fingerprint/seed-006 new file mode 100644 index 0000000..77e457b --- /dev/null +++ b/fuzz/corpus/fingerprint/seed-006 @@ -0,0 +1 @@ +AAAAC3NzaC1lZDI1NTE5AAAAIM28 \ No newline at end of file diff --git a/fuzz/corpus/fingerprint/seed-007 b/fuzz/corpus/fingerprint/seed-007 new file mode 100644 index 0000000..02462c2 --- /dev/null +++ b/fuzz/corpus/fingerprint/seed-007 @@ -0,0 +1 @@ +AAAAC3NzaC1lZDI1NTE5AAAAIM28sP5UAl3npJJ8trZgPt2kE+Af6477/Rx6J2VbM14ztrailing-garbage \ No newline at end of file diff --git a/fuzz/corpus/fingerprint/seed-008 b/fuzz/corpus/fingerprint/seed-008 new file mode 100644 index 0000000..c9cf2f6 --- /dev/null +++ b/fuzz/corpus/fingerprint/seed-008 @@ -0,0 +1 @@ +ssh-rsa AAAAC3NzaC1lZDI1NTE5AAAAIM28sP5UAl3npJJ8trZgPt2kE+Af6477/Rx6J2VbM14z \ No newline at end of file diff --git a/fuzz/corpus/fingerprint/seed-009 b/fuzz/corpus/fingerprint/seed-009 new file mode 100644 index 0000000..c3b40df --- /dev/null +++ b/fuzz/corpus/fingerprint/seed-009 @@ -0,0 +1 @@ +ÿþ raw bytes \ No newline at end of file diff --git a/fuzz/corpus/fingerprint/seed-010 b/fuzz/corpus/fingerprint/seed-010 new file mode 100644 index 0000000..65ab393 --- /dev/null +++ b/fuzz/corpus/fingerprint/seed-010 @@ -0,0 +1,2 @@ + +AAAAC3NzaC1lZDI1NTE5AAAAIM28sP5UAl3npJJ8trZgPt2kE+Af6477/Rx6J2VbM14z diff --git a/fuzz/corpus/fingerprint/seed-011 b/fuzz/corpus/fingerprint/seed-011 new file mode 100644 index 0000000..2fea296 --- /dev/null +++ b/fuzz/corpus/fingerprint/seed-011 @@ -0,0 +1 @@ +BAAAC3NzaC1lZDI1NTE5AAAAIM28sP5UAl3npJJ8trZgPt2kE+Af6477/Rx6J2VbM14z \ No newline at end of file diff --git a/fuzz/corpus/key_decode/seed-000 b/fuzz/corpus/key_decode/seed-000 new file mode 100644 index 0000000..ea5126e --- /dev/null +++ b/fuzz/corpus/key_decode/seed-000 @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACDNvLD+VAJd56SSfLa2YD7dpBPgH+uO+/0ceidlWzNeMwAAAKDJQe/myUHv +5gAAAAtzc2gtZWQyNTUxOQAAACDNvLD+VAJd56SSfLa2YD7dpBPgH+uO+/0ceidlWzNeMw +AAAEATzyiI7Cwpy3CJBDKDepykbHiNzVpZ38kiDqa1S1p5Us28sP5UAl3npJJ8trZgPt2k +E+Af6477/Rx6J2VbM14zAAAAF2Fsa3NzaC13MS1maXh0dXJlLWFscGhhAQIDBAUG +-----END OPENSSH PRIVATE KEY----- diff --git a/fuzz/corpus/key_decode/seed-001 b/fuzz/corpus/key_decode/seed-001 new file mode 100644 index 0000000..2f65c29 --- /dev/null +++ b/fuzz/corpus/key_decode/seed-001 @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACDL7vUvoR/Q7oO33Yhy3lvgfZHnvouPeYzwHQR/GGjr0AAAAKAUKLAWFCiw +FgAAAAtzc2gtZWQyNTUxOQAAACDL7vUvoR/Q7oO33Yhy3lvgfZHnvouPeYzwHQR/GGjr0A +AAAEC9aPqnyC37OKCL/VksdonIl6ICy+I8X4So6TDmnSObEMvu9S+hH9Dug7fdiHLeW+B9 +kee+i495jPAdBH8YaOvQAAAAF2Fsa3NzaC13MS1maXh0dXJlLWJyYXZvAQIDBAUG +-----END OPENSSH PRIVATE KEY----- diff --git a/fuzz/corpus/key_decode/seed-002 b/fuzz/corpus/key_decode/seed-002 new file mode 100644 index 0000000..fdab659 --- /dev/null +++ b/fuzz/corpus/key_decode/seed-002 @@ -0,0 +1 @@ +this is not a key \ No newline at end of file diff --git a/fuzz/corpus/key_decode/seed-003 b/fuzz/corpus/key_decode/seed-003 new file mode 100644 index 0000000..e69de29 diff --git a/fuzz/corpus/key_decode/seed-004 b/fuzz/corpus/key_decode/seed-004 new file mode 100644 index 0000000..a7e28f3 --- /dev/null +++ b/fuzz/corpus/key_decode/seed-004 @@ -0,0 +1,4 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACDNvLD+VAJd56SSfLa2YD7dpBPgH+uO+/0ceidlWzNeMwAAAKDJQe/myUHv +5gAAAAtzc2gtZWQyNTUxOQAAACDNvLD \ No newline at end of file diff --git a/fuzz/corpus/key_decode/seed-005 b/fuzz/corpus/key_decode/seed-005 new file mode 100644 index 0000000..5aff1a2 Binary files /dev/null and b/fuzz/corpus/key_decode/seed-005 differ diff --git a/fuzz/corpus/key_decode/seed-006 b/fuzz/corpus/key_decode/seed-006 new file mode 100644 index 0000000..13f3efc --- /dev/null +++ b/fuzz/corpus/key_decode/seed-006 @@ -0,0 +1,3 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +garbage +-----END OPENSSH PRIVATE KEY----- diff --git a/fuzz/corpus/key_decode/seed-007 b/fuzz/corpus/key_decode/seed-007 new file mode 100644 index 0000000..cff8f78 --- /dev/null +++ b/fuzz/corpus/key_decode/seed-007 @@ -0,0 +1,2 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +AAAA diff --git a/fuzz/corpus/key_decode/seed-008 b/fuzz/corpus/key_decode/seed-008 new file mode 100644 index 0000000..7477b2f --- /dev/null +++ b/fuzz/corpus/key_decode/seed-008 @@ -0,0 +1 @@ +unicodeé日本語 \ No newline at end of file diff --git a/fuzz/corpus/key_decode/seed-009 b/fuzz/corpus/key_decode/seed-009 new file mode 100644 index 0000000..85025d9 Binary files /dev/null and b/fuzz/corpus/key_decode/seed-009 differ diff --git a/fuzz/corpus/key_decode/seed-010 b/fuzz/corpus/key_decode/seed-010 new file mode 100644 index 0000000..07c05b8 --- /dev/null +++ b/fuzz/corpus/key_decode/seed-010 @@ -0,0 +1 @@ +PEM-adjacent-but-not \ No newline at end of file diff --git a/fuzz/corpus/terminal_params/seed-000 b/fuzz/corpus/terminal_params/seed-000 new file mode 100644 index 0000000..79b9707 Binary files /dev/null and b/fuzz/corpus/terminal_params/seed-000 differ diff --git a/fuzz/corpus/terminal_params/seed-001 b/fuzz/corpus/terminal_params/seed-001 new file mode 100644 index 0000000..c6f356b Binary files /dev/null and b/fuzz/corpus/terminal_params/seed-001 differ diff --git a/fuzz/corpus/terminal_params/seed-002 b/fuzz/corpus/terminal_params/seed-002 new file mode 100644 index 0000000..d37ce0b Binary files /dev/null and b/fuzz/corpus/terminal_params/seed-002 differ diff --git a/fuzz/corpus/terminal_params/seed-003 b/fuzz/corpus/terminal_params/seed-003 new file mode 100644 index 0000000..2965bb9 Binary files /dev/null and b/fuzz/corpus/terminal_params/seed-003 differ diff --git a/fuzz/corpus/terminal_params/seed-004 b/fuzz/corpus/terminal_params/seed-004 new file mode 100644 index 0000000..770afdf Binary files /dev/null and b/fuzz/corpus/terminal_params/seed-004 differ diff --git a/fuzz/corpus/terminal_params/seed-005 b/fuzz/corpus/terminal_params/seed-005 new file mode 100644 index 0000000..f76dd23 Binary files /dev/null and b/fuzz/corpus/terminal_params/seed-005 differ diff --git a/fuzz/corpus/terminal_params/seed-006 b/fuzz/corpus/terminal_params/seed-006 new file mode 100644 index 0000000..f0fd2a9 Binary files /dev/null and b/fuzz/corpus/terminal_params/seed-006 differ diff --git a/fuzz/corpus/terminal_params/seed-007 b/fuzz/corpus/terminal_params/seed-007 new file mode 100644 index 0000000..1977df4 Binary files /dev/null and b/fuzz/corpus/terminal_params/seed-007 differ diff --git a/fuzz/corpus/terminal_params/seed-008 b/fuzz/corpus/terminal_params/seed-008 new file mode 100644 index 0000000..00367aa Binary files /dev/null and b/fuzz/corpus/terminal_params/seed-008 differ diff --git a/fuzz/corpus/terminal_params/seed-009 b/fuzz/corpus/terminal_params/seed-009 new file mode 100644 index 0000000..79b9707 Binary files /dev/null and b/fuzz/corpus/terminal_params/seed-009 differ diff --git a/fuzz/fuzz_targets/exec_parse.rs b/fuzz/fuzz_targets/exec_parse.rs new file mode 100644 index 0000000..7ecdf94 --- /dev/null +++ b/fuzz/fuzz_targets/exec_parse.rs @@ -0,0 +1,11 @@ +#![no_main] + +//! The exec-grammar target (`mapping.md` §Session-channel dispatch — +//! the git-parseable exec grammar + the dispatch parse path) — thin +//! wrapper over the shared invariant battery. + +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + alkssh_fuzz_shared::exec_parse::fuzz_exec_parse(data); +}); \ No newline at end of file diff --git a/fuzz/fuzz_targets/fingerprint.rs b/fuzz/fuzz_targets/fingerprint.rs new file mode 100644 index 0000000..24d660e --- /dev/null +++ b/fuzz/fuzz_targets/fingerprint.rs @@ -0,0 +1,11 @@ +#![no_main] + +//! The fingerprint-format target (ADR-004 decision 1 — the `SHA256:` + +//! unpadded base64 path) — thin wrapper over the shared invariant +//! battery. + +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + alkssh_fuzz_shared::fingerprint::fuzz_fingerprint(data); +}); \ No newline at end of file diff --git a/fuzz/fuzz_targets/key_decode.rs b/fuzz/fuzz_targets/key_decode.rs new file mode 100644 index 0000000..72e50cf --- /dev/null +++ b/fuzz/fuzz_targets/key_decode.rs @@ -0,0 +1,11 @@ +#![no_main] + +//! The key-decode target (ADR-005 decision 2 — the decrypt-at-use +//! chain's `decode_secret_key` seam) — thin wrapper over the shared +//! invariant battery. + +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + alkssh_fuzz_shared::key_decode::fuzz_key_decode(data); +}); \ No newline at end of file diff --git a/fuzz/fuzz_targets/terminal_params.rs b/fuzz/fuzz_targets/terminal_params.rs new file mode 100644 index 0000000..8c28149 --- /dev/null +++ b/fuzz/fuzz_targets/terminal_params.rs @@ -0,0 +1,11 @@ +#![no_main] + +//! The terminal-parameters target (`tty_map.rs` — the `pty_request` +//! args → `TerminalParamsWire` conversion, the SSH modes → reserved +//! modes JSON) — thin wrapper over the shared invariant battery. + +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + alkssh_fuzz_shared::terminal_params::fuzz_terminal_params(data); +}); \ No newline at end of file diff --git a/fuzz/gen_fuzz_seeds.py b/fuzz/gen_fuzz_seeds.py new file mode 100644 index 0000000..0ffc9ab --- /dev/null +++ b/fuzz/gen_fuzz_seeds.py @@ -0,0 +1,199 @@ +#!/usr/bin/env python3 +"""Regenerate the committed seed corpora for alkssh's fuzz targets. + +Writes into fuzz/corpus//. Deterministic: fixed inputs only, no +randomness (the fixture-backed reads are of the committed files). +Run from the repo root: + + python3 fuzz/gen_fuzz_seeds.py + +Target seed functions and the main() registry land here with the +target tasks; the shared seed_name()/write_seed() plumbing is stable +(the alkblobs family pattern). Only `seed-*` files are tracked; grown +corpora (SHA-named units), artifacts, and coverage output are +gitignored and are never merged back into the committed seeds. +""" + +import os + +ALPHA_PUB_BLOB = ( + "AAAAC3NzaC1lZDI1NTE5AAAAIM28sP5UAl3npJJ8trZgPt2kE+Af6477/Rx6J2VbM14z" +) +BRAVO_PUB_BLOB = ( + "AAAAC3NzaC1lZDI1NTE5AAAAIMvu9S+hH9Dug7fdiHLeW+B9kee+i495jPAdBH8YaOvQ" +) +CHARLIE_PUB_BLOB = ( + "AAAAC3NzaC1lZDI1NTE5AAAAIKL8zAzLkR0jKFY2l+QrBjC3Cv4Q3OBUUCX7rQV1jXQe" +) + + +def seed_name(target, i): + return os.path.join("fuzz", "corpus", target, f"seed-{i:03d}") + + +def write_seed(target, i, data): + path = seed_name(target, i) + os.makedirs(os.path.dirname(path), exist_ok=True) + with open(path, "wb") as f: + f.write(data) + + +def read_bytes(path): + with open(path, "rb") as f: + return f.read() + + +# ---------------------------------------------------------------- exec_parse + + +def exec_parse_seeds(): + return [ + # the vanilla wire shapes (the grammar's serving services) + b"git-upload-pack '/owner/demo'", + b"git-receive-pack '/owner/demo'", + b"git-upload-pack /r", + b"git-receive-pack /owner/demo.git", + # unquoted absolute with the .git idiom stripped + b"git-upload-pack /owner/demo.git", + # the decline-with-reason class (known non-serving services) + b"git-send-pack '/r'", + b"git-upload-archive '/r'", + b"git-send-pack", + # service-only (SSH exec must carry the repo) + b"git-upload-pack", + b"git-receive-pack ", + # the normalization's edge shapes + b"git-upload-pack ''", + b"git-upload-pack '/'", + b"git-upload-pack '/.git'", + b"git-upload-pack '/r.git.git'", + b"git-upload-pack '/a/b/c'", + # the pipe row (the probe's pinned non-git verdicts) + b"uptime", + b"", + b"git-upload-packrel", + b"git-upload-pack rel", + b"GIT-UPLOAD-PACK '/r'", + b"git-upload-pack$ '/r'", + b"git-upload-pack '/r'", + b"git-upload-pack'r'", + b"git-upload-pack '/r", + b"git-upload-pack '", + # hostile shapes: unicode, control bytes, interior nulls, quotes + "git-upload-pack '/\u00e9'".encode(), + "git-upload-pack '/\u65e5\u672c\u8a9e'".encode(), + b"git-upload-pack '/\x00\x01'", + b"git-upload-pack ' '", + b"git-upload-pack '''", + b"git-upload-pack '//r'", + b"git-upload-pack /r.git.git'", + # hostile lengths: the grammar rides any length behind the cap + b"git-upload-pack '/" + b"a" * 253 + b"'", + b"git-upload-pack /" + b"b" * 99 + b".git", + b"\x00git-upload-pack '/r'", + b"git-upload-pack\t'/r'", + ] + + +# --------------------------------------------------------------- fingerprint + + +def fingerprint_seeds(): + valid = ( + "AAAAC3NzaC1lZDI1NTE5AAAAICS7Ub4A073+G450Nt2DUOwWyniGVU/lrlUoQZAUg1sd" + ) + return [ + # the committed fixture blobs (the parity-vector keys) + (ALPHA_PUB_BLOB + " alkssh-w1-fixture-alpha").encode(), + (BRAVO_PUB_BLOB + " alkssh-w1-fixture-bravo").encode(), + (CHARLIE_PUB_BLOB + " alkssh-w1-fixture-charlie").encode(), + # the ssh-keygen README example blob + valid.encode(), + # hostile shapes: non-blobs and near-blobs + b"not a key", + b"", + b"AAAAC3NzaC1lZDI1NTE5AAAAIM28", # truncated valid prefix + ALPHA_PUB_BLOB.encode() + b"trailing-garbage", + b"ssh-rsa " + ALPHA_PUB_BLOB.encode(), # wrong type prefix + b"\xff\xfe raw bytes", + # whitespace-padded valid blob (the trim normalization) + (" \n" + ALPHA_PUB_BLOB + "\n").encode(), + # a valid blob with one flipped base64 char + (("B" if ALPHA_PUB_BLOB.startswith("A") else "A").encode() + ALPHA_PUB_BLOB.encode()[1:]), + ] + + +# ---------------------------------------------------------------- key_decode + + +def key_decode_seeds(): + alpha = read_bytes(os.path.join("tests", "vectors", "alpha.key")) + bravo = read_bytes(os.path.join("tests", "vectors", "bravo.key")) + return [ + # the committed fixture PEMs decode (the production arm's success row) + alpha, + bravo, + # hostile plaintext shapes: every Decode-class refusal + b"this is not a key", + b"", + alpha[: len(alpha) // 2], # truncated PEM + alpha + b"\x00trailing", # valid PEM with trailing garbage + b"-----BEGIN OPENSSH PRIVATE KEY-----\ngarbage\n-----END OPENSSH PRIVATE KEY-----\n", + b"-----BEGIN OPENSSH PRIVATE KEY-----\nAAAA\n", + "unicode\u00e9\u65e5\u672c\u8a9e".encode(), + b"\x00\x01\x02\x03\x04", + b"PEM-adjacent-but-not", # near-shape garbage + ] + + +# ------------------------------------------------------------ terminal_params + + +def framed(term, cols, rows, pix_w=0, pix_h=0, mode_blob=b""): + data = len(term).to_bytes(4, "little") + term + data += cols.to_bytes(4, "little") + data += rows.to_bytes(4, "little") + data += pix_w.to_bytes(4, "little") + data += pix_h.to_bytes(4, "little") + return data + mode_blob + + +def terminal_params_seeds(): + return [ + # the vanilla shape (an 80x24 xterm, no pixels, no modes) + framed(b"xterm-256color", 80, 24, 0, 0), + # the clamp's edge: exactly u16::MAX, then a hostile dim beyond it + framed(b"w", 65535, 65536, 2**32 - 1, 70000), + # overflow shapes: u32 maxima everywhere + framed(b"vt100", 2**32 - 1, 2**32 - 1, 2**32 - 1, 2**32 - 1), + # hostile term strings: empty, unicode, control bytes + framed("\u00e9\u65e5\u672c".encode(), 1, 1, 0, 1), + framed(b"\x00\x01\x07\x1b", 5, 5, 5, 5), + # zero-defaulted dims (the feed's totality over short inputs) + b"\x00", + b"\x02\x00\x00\x00vt", + # arbitrary mode blobs after the framed fields (never parsed) + framed(b"xterm", 80, 24, 128, 48, b"\x53\x01\x00\x00\x00\x40"), + framed(b"xterm", 80, 24, 0, 0, b"\xff" * 32), + # truncated feeds + framed(b"xterm-256color", 80, 24), + ] + + +TARGETS = [ + ("exec_parse", exec_parse_seeds), + ("fingerprint", fingerprint_seeds), + ("key_decode", key_decode_seeds), + ("terminal_params", terminal_params_seeds), +] + + +def main(): + for target, generate in TARGETS: + for i, data in enumerate(generate()): + write_seed(target, i, data) + print("committed corpora regenerated (deterministic; expect no-op diff)") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/fuzz/run-detached.sh b/fuzz/run-detached.sh new file mode 100755 index 0000000..f3f9b07 --- /dev/null +++ b/fuzz/run-detached.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# Detached fuzzing runner for agent sessions: the fuzz campaign never +# runs as a foreground child of the session (OOM in a target must not +# take down the agent host), and survives the session ending. +# +# Usage: fuzz/run-detached.sh [extra libfuzzer args...] +# (works from the repo root or from fuzz/; CWD-independent) +# FUZZ_RUNTIME_SECS overrides the per-campaign budget (default 600 s). +# +# Poll instead of waiting: +# tail -n 50 fuzz/artifacts/-*.log +# ls fuzz/artifacts// (crash-* / oom-* / timeout-* files) +# pgrep -f "cargo fuzz run " +set -euo pipefail +target="${1:?usage: run-detached.sh [extra libfuzzer args...]}" +shift + +root="$(git rev-parse --show-toplevel)" +fuzz_dir="$root/fuzz" +mkdir -p "$fuzz_dir/artifacts" +runtime="${FUZZ_RUNTIME_SECS:-600}" +log="$fuzz_dir/artifacts/${target}-$(date -u +%Y%m%d-%H%M%S).log" + +cd "$fuzz_dir" +setsid nohup cargo fuzz run "$target" -- \ + -fork=1 -rss_limit_mb=2048 -malloc_limit_mb=2048 -timeout=25 \ + -max_total_time="$runtime" "$@" \ + >"$log" 2>&1 < /dev/null & +echo "pid=$! log=$log" \ No newline at end of file diff --git a/fuzz/rust-toolchain.toml b/fuzz/rust-toolchain.toml new file mode 100644 index 0000000..691b3cb --- /dev/null +++ b/fuzz/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "nightly" +components = ["llvm-tools-preview"] \ No newline at end of file diff --git a/fuzz/shared/Cargo.toml b/fuzz/shared/Cargo.toml new file mode 100644 index 0000000..0c26027 --- /dev/null +++ b/fuzz/shared/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "alkssh-fuzz-shared" +version = "0.0.0" +publish = false +edition = "2021" + +[dependencies] +alkssh = { path = "../..", features = ["session"] } +alkgit = "0.1" +russh = { version = "0.65", default-features = false, features = ["aws-lc-rs"] } +serde_json = "1" +zeroize = "1.9" \ No newline at end of file diff --git a/fuzz/shared/src/corpus.rs b/fuzz/shared/src/corpus.rs new file mode 100644 index 0000000..62c167e --- /dev/null +++ b/fuzz/shared/src/corpus.rs @@ -0,0 +1,54 @@ +//! The corpus-replay plumbing: the committed seeds replay through the +//! invariant batteries on the stable toolchain (`cargo test +//! --manifest-path fuzz/shared/Cargo.toml` runs this in +//! `#[cfg(test)]` modules across the ingress units — the quinn CI +//! pattern), so the gate never needs a fuzzing session. + +pub fn read_corpus(target: &str) -> Vec> { + let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../corpus") + .join(target); + let mut seeds = Vec::new(); + let entries = std::fs::read_dir(&dir).unwrap_or_else(|error| { + panic!("the committed corpus for {target} is present ({dir:?}): {error}") + }); + for entry in entries { + let path = entry.expect("corpus entry readable").path(); + let data = std::fs::read(&path).expect("corpus entry readable"); + seeds.push(data); + } + seeds.sort(); + seeds +} + +#[macro_export] +macro_rules! corpus_replay { + ($target:literal, $battery:path, $min:literal) => { + #[cfg(test)] + mod corpus_replay { + use super::*; + + #[test] + fn committed_corpus_replays_through_the_invariants() { + let seeds = $crate::corpus::read_corpus($target); + assert!( + seeds.len() >= $min, + "committed seed corpus is present, found {}", + seeds.len() + ); + for data in &seeds { + $battery(data); + } + } + + #[test] + fn empty_and_unit_inputs_replay() { + $battery(b""); + $battery(b"hello"); + $battery(b"\x00\x00\x00\x00"); + $battery(b"\xff\xfe raw bytes"); + $battery(b"a"); + } + } + }; +} \ No newline at end of file diff --git a/fuzz/shared/src/exec_parse.rs b/fuzz/shared/src/exec_parse.rs new file mode 100644 index 0000000..d9ede07 --- /dev/null +++ b/fuzz/shared/src/exec_parse.rs @@ -0,0 +1,105 @@ +//! The exec-grammar ingress (`mapping.md` §Session-channel dispatch, +//! `dispatch.rs`): the production `parse_exec` path driven on arbitrary +//! command strings — no-panic, typed routing classes, and agreement +//! with a from-spec re-derivation of the grammar. The production +//! ingress shape is the handler's: the per-length cap +//! (`Limits::max_exec_command_len`) is enforced *before* the parse +//! (decline first, alloc never — the cap's pre-parse placement is +//! pinned by the root's contract rows in `tests/dispatch_core.rs`); +//! everything behind the cap is this battery: a byte string → +//! `std::str::from_utf8(..).unwrap_or("")` → the parse (invalid UTF-8 +//! is the pipe row, the wave-2 rule). + +use alkssh::{ExecDecline, ExecRoute, GitServiceRoute, parse_exec}; + +/// The battery: no-panic on any byte string; the verdict agrees with +/// the from-spec oracle ([`spec_parse`]); the verdict (and the git +/// row's repo-id gate classification for `Git` routes) is stable per +/// input; every `Git` route carries one of the two serving services +/// and a normalized argument. +pub fn fuzz_exec_parse(data: &[u8]) { + let command = std::str::from_utf8(data).unwrap_or(""); + let route = parse_exec(command); + + assert_eq!( + route, + spec_parse(command), + "the parse disagrees with the from-spec oracle: {command:?} → {route:?}" + ); + assert_eq!( + parse_exec(command), + route, + "the parse verdict is not deterministic: {command:?}" + ); + + if let ExecRoute::Git { repo, .. } = &route { + assert_eq!( + alkgit::validate_repo_id(repo), + alkgit::validate_repo_id(repo), + "the git row's repo-id gate is not deterministic: {command:?}" + ); + } +} + +/// The from-spec oracle: the grammar re-derived from the dispatch +/// module's documented rules, sharing no code with the +/// implementation — the differential pair (the repo_id target's +/// oracle pattern). +fn spec_parse(command: &str) -> ExecRoute { + let (token, argument) = match command.split_once(' ') { + Some((token, argument)) => (token, Some(argument)), + None => (command, None), + }; + match token { + "git-upload-pack" => spec_serving(GitServiceRoute::UploadPack, argument), + "git-receive-pack" => spec_serving(GitServiceRoute::ReceivePack, argument), + "git-send-pack" => ExecRoute::Declined(ExecDecline::UnknownService { + service: "git-send-pack", + }), + "git-upload-archive" => ExecRoute::Declined(ExecDecline::UnknownService { + service: "git-upload-archive", + }), + _ => ExecRoute::Pipe, + } +} + +/// The serving-service arm of the oracle: a serving token must carry +/// a non-empty, path-shaped argument (SSH exec must carry the repo — +/// service-only is the decline-with-reason class; anything else +/// unparseable is the pipe row). +fn spec_serving(service: GitServiceRoute, argument: Option<&str>) -> ExecRoute { + match argument { + None => ExecRoute::Declined(ExecDecline::ServiceOnly), + Some(argument) if argument.is_empty() => ExecRoute::Declined(ExecDecline::ServiceOnly), + Some(argument) => match spec_normalize(argument) { + Some(repo) => ExecRoute::Git { service, repo }, + None => ExecRoute::Pipe, + }, + } +} + +/// The argument grammar re-derived: a path-shaped argument — fully +/// single-quote-wrapped (a non-empty inner ending in a quote) or +/// unquoted absolute — normalized by stripping the quotes, one +/// leading `/`, and one trailing `.git` suffix (an empty-after-strip +/// prefix keeps the suffix; the git row's validation declines the +/// empties, never a silent emptying). +fn spec_normalize(argument: &str) -> Option { + let stripped = if let Some(inner) = argument.strip_prefix('\'') { + if inner.is_empty() { + return None; + } + inner.strip_suffix('\'')? + } else if argument.starts_with('/') { + argument + } else { + return None; + }; + let without_slash = stripped.strip_prefix('/').unwrap_or(stripped); + Some(match without_slash.strip_suffix(".git") { + Some(prefix) if !prefix.is_empty() => prefix.to_owned(), + _ => without_slash.to_owned(), + }) +} + +crate::corpus_replay!("exec_parse", fuzz_exec_parse, 12); \ No newline at end of file diff --git a/fuzz/shared/src/fingerprint.rs b/fuzz/shared/src/fingerprint.rs new file mode 100644 index 0000000..fab3e31 --- /dev/null +++ b/fuzz/shared/src/fingerprint.rs @@ -0,0 +1,152 @@ +//! The fingerprint format ingress (ADR-004 decision 1, +//! `fingerprint.rs`): arbitrary key blobs through the crate's single +//! fingerprint path — the composition of russh's public OpenSSH blob +//! parse (`russh::keys::parse_public_key_base64`, the same re-export +//! the crate's surface rides) and the crate's public +//! [`alkssh::fingerprint`]. The ingress is fully public API — the +//! recorded exposure decision needs no `#[doc(hidden)]` surface. +//! +//! Invariants: no-panic on any byte string (a non-blob is the parse's +//! typed refusal, never a panic and never a partial success); every +//! accepted key's fingerprint is exactly the `SHA256:` + unpadded +//! standard base64 contract shape (one `:` separator, 43-char body, +//! canonical alphabet, 32-byte digest decoded exactly); determinism +//! (same key, same string — and the same string re-parses to the same +//! verdict). The committed `ssh-keygen -lf` parity vectors +//! (`tests/vectors/`) are replayed by the corpus-gate tests below. + +use alkssh::fingerprint; +use russh::keys::parse_public_key_base64; + +/// The battery. +pub fn fuzz_fingerprint(data: &[u8]) { + let text = String::from_utf8_lossy(data); + let trimmed = text.trim(); + let first = parse_public_key_base64(trimmed); + let second = parse_public_key_base64(trimmed); + let verdict = match (first, second) { + (Ok(key), Ok(other)) => { + assert_eq!( + fingerprint(&key), + fingerprint(&other), + "the blob parse verdict is not deterministic: {trimmed:?}" + ); + key + } + (Ok(_), Err(_)) | (Err(_), Ok(_)) => { + panic!("the blob parse verdict is not deterministic: {trimmed:?}") + } + (Err(_), Err(_)) => return, + }; + let key = verdict; + let fp = fingerprint(&key); + assert_eq!( + fingerprint(&key), + fp, + "the fingerprint is not deterministic: {fp}" + ); + + let Some((prefix, body)) = fp.split_once(':') else { + panic!("the fingerprint carries no separator: {fp}"); + }; + assert_eq!(prefix, "SHA256", "the fingerprint prefix: {fp}"); + assert_eq!( + body.len(), + 43, + "the unpadded base64 of a 32-byte digest is 43 chars: {fp}" + ); + let decoded = decode_unpadded(body) + .unwrap_or_else(|| panic!("the fingerprint body is not standard base64: {fp}")); + assert_eq!( + decoded.len(), + 32, + "the SHA-256 digest body decodes to 32 bytes: {fp}" + ); + assert_eq!( + encode_unpadded(&decoded), + body, + "the fingerprint body is not the canonical unpadded encoding: {fp}" + ); +} + +/// A from-spec unpadded standard-base64 decoder (RFC 4648, no +/// padding): the round-trip half of the format invariant, sharing no +/// code with the implementors. +fn decode_unpadded(body: &str) -> Option> { + let mut out = Vec::new(); + let mut acc: u32 = 0; + let mut bits = 0usize; + for character in body.chars() { + let value = match character { + 'A'..='Z' => (character as u32) - ('A' as u32), + 'a'..='z' => (character as u32) - ('a' as u32) + 26, + '0'..='9' => (character as u32) - ('0' as u32) + 52, + '+' => 62, + '/' => 63, + _ => return None, + }; + acc = (acc << 6) | value; + bits += 6; + if bits >= 8 { + bits -= 8; + out.push(((acc >> bits) & 0xff) as u8); + } + } + if acc & ((1 << bits) - 1) != 0 { + return None; + } + Some(out) +} + +/// A from-spec unpadded standard-base64 encoder — the re-encode half. +fn encode_unpadded(bytes: &[u8]) -> String { + const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + let mut out = Vec::new(); + let mut acc: u32 = 0; + let mut bits = 0usize; + for byte in bytes { + acc = (acc << 8) | u32::from(*byte); + bits += 8; + while bits >= 6 { + bits -= 6; + out.push(ALPHABET[((acc >> bits) & 0x3f) as usize]); + } + } + if bits > 0 { + out.push(ALPHABET[((acc << (6 - bits)) & 0x3f) as usize]); + } + String::from_utf8(out).unwrap_or_default() +} + +crate::corpus_replay!("fingerprint", fuzz_fingerprint, 8); + +#[cfg(test)] +mod parity { + use super::*; + + /// The committed `ssh-keygen -lf` vectors still agree: every + /// fixture's computed fingerprint is byte-verbatim the recorded + /// independent-issuer value. + #[test] + fn the_committed_parity_vectors_still_agree() { + let manifest = std::fs::read_to_string(std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../../tests/vectors/fingerprints.txt")) + .expect("the parity manifest is readable"); + for name in ["alpha", "bravo", "charlie", "delta", "ca"] { + let line = std::fs::read_to_string(std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .join(format!("../../tests/vectors/{name}.pub"))) + .unwrap_or_else(|error| panic!("the {name}.pub fixture is readable: {error}")); + let blob = line + .split_whitespace() + .nth(1) + .unwrap_or_else(|| panic!("{name}.pub carries a base64 blob")); + let key = parse_public_key_base64(blob) + .unwrap_or_else(|error| panic!("{name}.pub parses: {error}")); + let expected = format!("{name}.pub {}", fingerprint(&key)); + assert!( + manifest.contains(&expected), + "{name}'s computed fingerprint is not the recorded ssh-keygen value" + ); + } + } +} \ No newline at end of file diff --git a/fuzz/shared/src/key_decode.rs b/fuzz/shared/src/key_decode.rs new file mode 100644 index 0000000..d48735c --- /dev/null +++ b/fuzz/shared/src/key_decode.rs @@ -0,0 +1,131 @@ +//! The key-decode ingress (ADR-005 decision 2, `key_source.rs`): the +//! decrypt-at-use chain's one-time `decode_secret_key` conversion — +//! driven through the production arm itself +//! ([`alkssh::decode_key_arm`], the `#[doc(hidden)]` exposure that is +//! `decrypt_and_decode`'s exact decode stage, not a fork). +//! +//! Invariants: no-panic on arbitrary plaintext shapes (the production +//! plaintext is the vault's `String` — valid UTF-8 by return type; the +//! battery shapes arbitrary bytes into that form via the lossy +//! conversion and drives it through the same `Zeroizing` wrap the +//! adapter seam applies) and arbitrary passphrase shapes (the +//! underlying decode's passphrase arm — the production chain always +//! passes `None`; the `Some` arm is robustness drive only); every +//! failure is the typed [`KeySourceError::Decode`] class — never a +//! catch-all, never another variant on this arm, never a panic; the +//! failure text never carries the plaintext (the assertable half of +//! the Zeroizing posture — the plaintext exists solely inside the +//! call, dropped zeroized; no retention, no static cache — and the +//! typed error's only payload is the decode machinery's message). +//! +//! The no-echo invariant's assertable form is *the bounded-message +//! pair* (reshaped in-task after the first campaign's finding): the +//! failure text is a bounded fixed message (≤ 256 bytes — the decode +//! machinery's vocabulary, never a growable echo), and no 256-byte +//! contiguous plaintext run appears in it — which, given the bound, +//! cannot false-fire (a string shorter than 256 bytes cannot contain a +//! 256-byte run). A plaintext-`contains`-message check in the other +//! direction was unsound: an attacker-chosen plaintext can be a +//! substring of the fixed text (the campaign's own first crash: +//! plaintext `"Could not read key"`). + +use alkssh::{KeySourceError, decode_key_arm}; +use zeroize::Zeroizing; + +/// The battery. +pub fn fuzz_key_decode(data: &[u8]) { + let plaintext = Zeroizing::new(String::from_utf8_lossy(data).into_owned()); + + let first = decode_key_arm(&plaintext); + let second = decode_key_arm(&plaintext); + typed_decode_class(&first, &plaintext); + match (&first, &second) { + (Ok(key), Ok(other)) => { + assert_eq!( + fingerprint(&key), + fingerprint(other), + "the decode arm is not deterministic" + ); + } + (Err(a), Err(b)) => assert_eq!( + a.to_string(), + b.to_string(), + "the decode arm's failure is not deterministic: {a} vs {b}" + ), + (Ok(_), Err(_)) | (Err(_), Ok(_)) => { + panic!("the decode arm is not deterministic: {first:?} vs {second:?}") + } + } + + let mid = data.len() / 2; + let pass = String::from_utf8_lossy(&data[mid..]); + let _ = russh::keys::decode_secret_key(&plaintext, Some(&pass)); + let _ = russh::keys::decode_secret_key(&plaintext, Some(&String::from_utf8_lossy(data))); +} + +fn fingerprint(key: &russh::keys::PrivateKey) -> String { + key.public_key().fingerprint(russh::keys::HashAlg::Sha256).to_string() +} + +/// The typed-class invariant: `Ok` or exactly +/// [`KeySourceError::Decode`] — the decode arm's only failure shape — +/// and the Decode text is the bounded fixed-message pair (see the +/// module docs; the two assertions are jointly airtight). +fn typed_decode_class(outcome: &Result, plaintext: &str) { + match outcome { + Ok(_) => {} + Err(KeySourceError::Decode(message)) => { + assert!( + message.len() <= 256, + "the decode failure text is not a bounded fixed message: {message:?}" + ); + let target = message.as_bytes(); + if plaintext.len() >= 256 && target.len() >= 256 { + let bytes = plaintext.as_bytes(); + let tail_start = bytes.len() - 256; + for start in [0, (bytes.len() / 4).min(tail_start), tail_start] { + let run = &bytes[start..start + 256]; + assert!( + target.windows(256).all(|window| window != run), + "the typed decode failure carried plaintext material: {message:?}" + ); + } + } + } + Err(other) => panic!("the decode arm produced a non-decode class: {other:?}"), + } +} + +crate::corpus_replay!("key_decode", fuzz_key_decode, 8); + +#[cfg(test)] +mod invariants { + use super::*; + + /// The production arm against a real fixture: a committed OpenSSH + /// PEM plaintext decodes to the same key the direct decode gives + /// (the ADR-005 one-time conversion, exercised through the + /// exposure). + #[test] + fn the_production_arm_decodes_a_fixture_pem() { + let pem = std::fs::read_to_string( + std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../../tests/vectors/alpha.key"), + ) + .expect("the alpha fixture is readable"); + let loaded = decode_key_arm(&Zeroizing::new(pem.clone())) + .unwrap_or_else(|error| panic!("the fixture PEM decodes: {error}")); + let direct = russh::keys::decode_secret_key(&pem, None) + .unwrap_or_else(|error| panic!("the direct decode works: {error}")); + assert_eq!(fingerprint(&loaded), fingerprint(&direct)); + } + + /// A plaintext that is not OpenSSH-PEM key material is a + /// [`KeySourceError::Decode`] refusal (the inner-format contract — + /// decision 1, never a fallback). + #[test] + fn non_key_plaintext_is_the_typed_decode_refusal() { + let outcome = decode_key_arm(&Zeroizing::new("this is not a key".to_owned())); + assert!(matches!(outcome, Err(KeySourceError::Decode(_)))); + } +} \ No newline at end of file diff --git a/fuzz/shared/src/lib.rs b/fuzz/shared/src/lib.rs new file mode 100644 index 0000000..6a42ff2 --- /dev/null +++ b/fuzz/shared/src/lib.rs @@ -0,0 +1,10 @@ +//! Invariant logic shared by alkssh's fuzz targets. Kept out of the +//! fuzz-target binaries so the corpus replay unit tests can exercise the +//! same invariant checks against every committed corpus entry (the quinn +//! CI pattern) without a nightly toolchain. + +pub mod corpus; +pub mod exec_parse; +pub mod fingerprint; +pub mod key_decode; +pub mod terminal_params; \ No newline at end of file diff --git a/fuzz/shared/src/terminal_params.rs b/fuzz/shared/src/terminal_params.rs new file mode 100644 index 0000000..d7f98aa --- /dev/null +++ b/fuzz/shared/src/terminal_params.rs @@ -0,0 +1,115 @@ +//! The terminal-parameters ingress (`tty_map.rs`, OQ-44's reserved +//! modes): the `pty_request` args → `TerminalParamsWire` conversion +//! driven through the production [terminal_params] surface (the +//! `#[doc(hidden)]` exposure — `handler.rs`'s `pty_request` row +//! constructs the stored params with exactly this function). +//! +//! The harness's framed feed (deterministic, seed-friendly): +//! `term_len` (u32 LE, bounded to the remaining input) + the term +//! bytes + four u32 LE dimensions (`cols`/`rows`/pixel width/height, +//! zero-defaulted when the input runs out) + an arbitrary trailing +//! mode blob. The mode blob is *never parsed* — the SSH ttymodes ride +//! the reserved `{}` JSON (OQ-44, backends ignore modes in v1) — so +//! the battery's totality over arbitrary mode blobs is by +//! construction; the framing demonstrates it (the conversion's +//! outcome never varies with the blob). +//! +//! Invariants: no-panic on any term string (empty, unicode, control +//! characters) and any u32 dimensions; the dimension clamp is the +//! recorded cap (`min(value, 65535)` — a dimension beyond 65535 is +//! nonsense on both papers) computed exactly, never a magnitude +//! truncation; `term` is preserved verbatim; the reserved-modes JSON +//! is always exactly the empty object; the full wire shape is +//! deterministic per input (the serialized form compared). + +use alkssh::terminal_params; + +/// The battery. +pub fn fuzz_terminal_params(data: &[u8]) { + let mut cursor = data; + let term_len = take_u32(&mut cursor) as usize; + let term_len = term_len.min(cursor.len()); + let (term_bytes, rest) = cursor.split_at(term_len); + cursor = rest; + let term = String::from_utf8_lossy(term_bytes); + let cols = take_u32(&mut cursor); + let rows = take_u32(&mut cursor); + let pixel_width = take_u32(&mut cursor); + let pixel_height = take_u32(&mut cursor); + let _mode_blob = cursor; + + let params = terminal_params(&term, cols, rows, pixel_width, pixel_height); + assert_eq!( + ¶ms.term, + &Some(term.to_string()), + "the conversion did not preserve `term` verbatim: {term:?}" + ); + assert_eq!( + params.cols, + spec_clamp(cols), + "the cols clamp is not the recorded cap: {cols}" + ); + assert_eq!( + params.rows, + spec_clamp(rows), + "the rows clamp is not the recorded cap: {rows}" + ); + assert_eq!( + params.pixel_width, + spec_clamp(pixel_width), + "the pixel-width clamp is not the recorded cap: {pixel_width}" + ); + assert_eq!( + params.pixel_height, + spec_clamp(pixel_height), + "the pixel-height clamp is not the recorded cap: {pixel_height}" + ); + assert!( + params.modes.is_object() + && params + .modes + .as_object() + .is_some_and(|map| map.is_empty()), + "the reserved modes JSON is not the empty object: {:?}", + params.modes + ); + match serde_json::to_string(¶ms.modes) { + Ok(text) => assert!(text == "{}", "the reserved modes JSON shape: {text}"), + Err(error) => panic!("the reserved modes JSON never fails to serialize: {error}"), + } + + let shape = serde_json::to_string(¶ms); + let replayed = terminal_params(&term, cols, rows, pixel_width, pixel_height); + match (shape, serde_json::to_string(&replayed)) { + (Ok(first), Ok(second)) => assert_eq!( + first, second, + "the conversion's wire shape is not deterministic: {term:?} {cols}×{rows}" + ), + _ => panic!("the params JSON shape never fails to serialize"), + } +} + +/// The cursor's u32 (zero-defaulted when the framed field is absent — +/// the feed is total over any input). +fn take_u32(cursor: &mut &[u8]) -> u32 { + if cursor.len() >= 4 { + let (bytes, rest) = cursor.split_at(4); + *cursor = rest; + u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) + } else { + *cursor = &[]; + 0 + } +} + +/// The recorded clamp, from the spec text (`tty_map.rs`: "u32 wire +/// dims → the wire's u16 … the cap, not a truncation of useful data"). +fn spec_clamp(value: u32) -> u16 { + if value > (u16::MAX as u32) { + u16::MAX + } else { + value as u16 + } +} + +crate::corpus_replay!("terminal_params", fuzz_terminal_params, 8); \ No newline at end of file diff --git a/src/dispatch.rs b/src/dispatch.rs index 93e5cfc..471c2d4 100644 --- a/src/dispatch.rs +++ b/src/dispatch.rs @@ -79,11 +79,11 @@ use russh::ChannelOpenFailure; /// skeleton probe's `ExecRoute::Git`/`Pipe` split, extended by the /// `w3-git-mapping` grammar with the declined-with-reason class). #[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) enum ExecRoute { +pub enum ExecRoute { /// A git-parseable command — routes to the git row. The repo path /// is the normalized argument grammar (quotes, leading `/`, one /// trailing `.git`); validation (`alkgit::validate_repo_id`) is - /// the git row's gate ([`crate::git_map`]). + /// the git row's gate (the `crate::git_map` module). Git { service: GitServiceRoute, repo: String, @@ -99,10 +99,10 @@ pub(crate) enum ExecRoute { } /// The git service the exec grammar recognized — the ADR-016 wire -/// strings, as the door-side form of [`alkgit::Service`] (the seam maps +/// strings, as the door-side form of `alkgit::Service` (the seam maps /// them verbatim; this type keeps the parse core feature-free). #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) enum GitServiceRoute { +pub enum GitServiceRoute { /// `git-upload-pack` — fetch. UploadPack, /// `git-receive-pack` — push. @@ -114,7 +114,7 @@ pub(crate) enum GitServiceRoute { /// the validation gate; that one needs `alkgit`'s grammar, this type /// is the parse core's). #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) enum ExecDecline { +pub enum ExecDecline { /// A known non-serving git service arrived on the wire: /// `git-send-pack` / `git-upload-archive` (POC-3 §2 — the honest /// surface; ADR-016's refusal). @@ -136,10 +136,10 @@ impl ExecDecline { } } -/// The exec-grammar parse — the wave-2 [`classify_exec`] probe's +/// The exec-grammar parse — the wave-2 `classify_exec` probe's /// replacement, its unit rows migrated (see the grammar section for /// the exact shape rules). -pub(crate) fn parse_exec(command: &str) -> ExecRoute { +pub fn parse_exec(command: &str) -> ExecRoute { let (token, argument) = match command.split_once(' ') { Some((token, argument)) => (token, Some(argument)), None => (command, None), diff --git a/src/lib.rs b/src/lib.rs index ba07091..9852615 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,6 +93,13 @@ pub use config::SessionSeam; #[cfg(feature = "tunnel")] pub use config::TunnelListenSeam; pub use config::{DialConfig, Limits, ServeConfig}; +/// Fuzz-visible surface (`#[doc(hidden)]`, `w5-fuzz-harness`): the exec +/// grammar's parse path (`dispatch.rs`) — the `fuzz/` sidecar's +/// `exec_parse` target drives the production parse, never a fork. Not +/// documented API: the grammar is the dispatch seam's, not a public-API +/// promise. +#[doc(hidden)] +pub use dispatch::{ExecDecline, ExecRoute, GitServiceRoute, parse_exec}; pub use error::{AdapterError, DialError, KeySourceError, OpError, OpenDeclineReason}; pub use fingerprint::fingerprint; /// Contract-test surface (`#[doc(hidden)]`): the per-connection auth @@ -103,6 +110,11 @@ pub use fingerprint::fingerprint; #[doc(hidden)] pub use handler::SessionHandler; pub use host_key::{HostKeyPolicy, PresentedHostKey, RejectAll}; +/// Fuzz-visible surface (`#[doc(hidden)]`, `w5-fuzz-harness`): the +/// decrypt-at-use chain's decode arm (`key_source.rs`, the production +/// `decrypt_and_decode` conversion). Not documented API. +#[doc(hidden)] +pub use key_source::decode_key_arm; pub use key_source::{HostKeySource, KeySource, VaultHostKeySource, VaultKeySource}; /// Contract-test surface (`#[doc(hidden)]`): the spawn-and-return /// plumbing (`ADR-006 decision 4`, see `serve.rs`) — the mechanics the @@ -110,3 +122,10 @@ pub use key_source::{HostKeySource, KeySource, VaultHostKeySource, VaultKeySourc /// real wire to pin the constraint as tests. Not documented API. #[doc(hidden)] pub use serve::{report_exit_status_then_close, spawn_serving_path}; +/// Fuzz-visible surface (`#[doc(hidden)]`, `w5-fuzz-harness`, under +/// `session`): the `pty_request` args → `TerminalParamsWire` +/// conversion (`tty_map.rs`, OQ-44's reserved modes). Not documented +/// API. +#[cfg(feature = "session")] +#[doc(hidden)] +pub use tty_map::terminal_params; diff --git a/src/tty_map.rs b/src/tty_map.rs index 21729b7..13df7db 100644 --- a/src/tty_map.rs +++ b/src/tty_map.rs @@ -215,7 +215,7 @@ pub(crate) fn clamp_u16(value: u32) -> u16 { /// negotiation section): SSH ttymodes → the **reserved modes JSON /// `{}`** (alktty OQ-44's reservation — the map is honest: nothing /// is carried, exactly what the reservation asks). -pub(crate) fn terminal_params( +pub fn terminal_params( term: &str, cols: u32, rows: u32, diff --git a/tasks/implementation/w5-fuzz-harness.md b/tasks/implementation/w5-fuzz-harness.md index 451a451..b272f38 100644 --- a/tasks/implementation/w5-fuzz-harness.md +++ b/tasks/implementation/w5-fuzz-harness.md @@ -1,7 +1,7 @@ --- id: w5-fuzz-harness name: Wave 5 — the fuzz sidecar (the crate's ingress surfaces) -status: pending +status: completed depends_on: [review-wave-4] scope: moderate risk: medium @@ -92,8 +92,111 @@ Shape (the alkgit convention, adapted): ## Notes -> To be filled by implementation agent +**The sidecar as landed**: `fuzz/` (its own workspace — `[workspace] +members = []` + nightly pin in `fuzz/rust-toolchain.toml`; the root +`Cargo.toml` gains `exclude = ["fuzz"]` at *both* levels — the +`[workspace]` exclude and the `[package]` exclude list (the alkgit +published-crate policy: the sidecar never ships in a publish; verified +with the dry-run)). `fuzz/Cargo.lock` is committed (the alkgit +convention); `artifacts/`, `coverage/`, `target/`, and grown corpora +are gitignored. + +**The exposure-shape decision (per target, recorded)** — the sidecar +consumes alkssh's public API wherever the ingress is already public, +and the production surface itself (never a fork) where it is private: + +- `fingerprint` — **no exposure needed**: the ingress is fully public + ([`alkssh::fingerprint`] + the russh 0.65 blob parse + `parse_public_key_base64`, the same re-export surface the crate + rides). +- `exec_parse` — `#[doc(hidden)]` re-export of the dispatch parse + path: `parse_exec`, `ExecRoute`, `ExecDecline`, `GitServiceRoute` + (made `pub` in the private `dispatch` module; the lib re-export + gate is the only reachability — the grammar stays a dispatch seam, + not a public-API promise). +- `key_decode` — `#[doc(hidden)]` exposure `decode_key_arm`, the + production `decrypt_and_decode`'s decode arm *extracted and routed + through itself* (the fuzz target drives the exact production arm, + and the arm takes `&Zeroizing` so the drive exercises the + adapter seam's wrap posture). +- `terminal_params` — `#[doc(hidden)]` re-export (under `session`) of + `tty_map::terminal_params` — the exact function `handler.rs`'s + `pty_request` row constructs the stored params with. + +**The harness shape**: the deterministic framed feed for +`terminal_params` (`term_len` u32-LE + term bytes + four u32-LE dims, +zero-defaulted, trailing mode blob ignored — the modes are never +parsed, OQ-44); the production ingress shape for `exec_parse` +(byte string → `from_utf8(..).unwrap_or("")` → parse — the handler's +exact shape; the pre-parse cap and the git row's repo-id gate are the +root contract rows' territory, pinned by `tests/dispatch_core.rs`); +a from-spec base64 round-trip oracle for `fingerprint`; the +`exec_parse` verdict is differentially paired against a from-spec +grammar oracle (`fuzz/shared/src/exec_parse.rs`), `fingerprint` +parity replays the committed `ssh-keygen -lf` vectors inside the +corpus gate. + +**Campaigns run (detached via `fuzz/run-detached.sh`, fork mode, the +default 600 s budget each; all four final campaigns exited 0 — zero +crashes/OOMs/timeouts)**: + +- `exec_parse` — 2 campaigns: the first crashed on a **harness-side + oracle bug** (the oracle stripped two leading `/` where the grammar + strips exactly one — `git-upload-pack //.git `); fixed, re-ran + clean (40M+ execs). +- `fingerprint` — 1 campaign, clean (12M+ execs). +- `key_decode` — 2 campaigns: the first found a **real crate + finding** (below); fixed, re-ran clean (3.5M+ execs). +- `terminal_params` — 1 campaign, clean (8.8M+ execs). + +**The real finding (fixed in-task, recorded)**: russh 0.65's +`ssh-key` PPK parse path (`PpkParseError::Header`) *interpolates the +raw header line* — the plaintext's own bytes — into its `Display`, +and the decode arm previously passed that message through +`KeySourceError::Decode`'s payload: a malformed or +PPK-header-shaped vault record would echo decrypted-record material +into a stored/logged error string (the ADR-005 decision-7 posture's +textual leak). Fix: `decode_key_arm` (and so every `Decode` payload +the crate ever produces) now carries crate-owned fixed text naming +the refusal — the machinery's message never rides the typed payload; +`error.rs`'s `Decode` doc records the semantics. **The upstream ask +is booked** (not absorbed): `ssh-key`'s PPK parse errors should not +echo the untrusted header; filed to russh/ssh-key with the owners at +the wave-5 gate. Related harness-side finding (also fixed in-task): +the no-echo invariant's first shape (a plaintext-`contains`-message +check) was itself unsound — an attacker-chosen plaintext can be a +substring of the machinery's fixed text (the campaign's own first +crash: plaintext `"Could not read key"`); the assertable form is the +bounded-message pair (the text is a bounded fixed message, ≤ 256 +bytes) + the ≥ 256-byte plaintext-run check that cannot false-fire. + +**Verification list**: the corpus gate +(`cargo test --manifest-path fuzz/shared/Cargo.toml`) is green (11 +tests — every committed seed replays the invariants on the stable +toolchain, plus the fixture parity rows) and has joined AGENTS.md's +verification commands. `scripts/matrix.sh` green after all changes +(the sidecar changes no root behavior: 6 builds × the full suite + +clippy ×2 `-D warnings` + fmt + doc, exit 0). ## Summary -> To be filled on completion \ No newline at end of file +Completed 2026-10-30. The `fuzz/` sidecar landed per the alkgit +convention with all four targets over the plan's ingress inventory +(`exec_parse`, `fingerprint`, `key_decode`, `terminal_params`), each a +thin `fuzz_target!` wrapper over the stable-clean +`fuzz/shared/` invariant library whose corpus replay +(`cargo test --manifest-path fuzz/shared/Cargo.toml`) joined the +verification list (AGENTS.md); committed hand-made seeds + the +deterministic `gen_fuzz_seeds.py` (no-op-diff verified); the detached +runner + nightly pin inside `fuzz/`; the root workspace excludes the +sidecar (workspace + package excludes). The exposure-shape decision is +recorded per target (one no-exposure — fingerprint's fully-public +ingress; three `#[doc(hidden)]` production-surface re-exports). +Detached campaigns ran per target at the runner's default budget — +all clean after findings: one harness-oracle bug (the exec oracle's +double-`/` strip) and one real crate fix — russh's `ssh-key` PPP/PPK +parse echo (raw header bytes) riding the typed `Decode` payload; +`decode_key_arm` now carries a crate-owned fixed message and the +upstream echo ask is booked, not absorbed. Verification: the sidecar +gate green, `scripts/matrix.sh` green end to end, `cargo publish +--dry-run` clean (the sidecar ships nothing). \ No newline at end of file