diff --git a/Cargo.lock b/Cargo.lock index cc242ed..42c6c28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,7 +20,7 @@ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -67,6 +67,15 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "arc-swap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" +dependencies = [ + "rustversion", +] + [[package]] name = "arrayref" version = "0.3.9" @@ -101,15 +110,6 @@ dependencies = [ "rustc_version", ] -[[package]] -name = "atomic-polyfill" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" -dependencies = [ - "critical-section", -] - [[package]] name = "atomic-waker" version = "1.1.2" @@ -146,10 +146,10 @@ dependencies = [ ] [[package]] -name = "base32" -version = "0.5.1" +name = "base16ct" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "022dfe9eb35f19ebbcb51e0b40a5ab759f46ad60cadf7297e0bd085afb50e076" +checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" [[package]] name = "base64" @@ -165,22 +165,22 @@ checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] name = "blake3" -version = "1.8.3" +version = "1.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" +checksum = "4d2d5991425dfd0785aed03aedcf0b321d61975c9b5b3689c774a2610ae0b51e" dependencies = [ "arrayref", "arrayvec", "cc", "cfg-if", "constant_time_eq", - "cpufeatures", + "cpufeatures 0.3.0", ] [[package]] @@ -194,9 +194,9 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96eb4cdd6cf1b31d671e9efe75c5d1ec614776856cefbe109ca373554a6d514f" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" dependencies = [ "hybrid-array", ] @@ -216,12 +216,6 @@ version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - [[package]] name = "bytes" version = "1.11.1" @@ -233,14 +227,20 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.57" +version = "1.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" dependencies = [ "find-msvc-tools", "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfg-if" version = "1.0.4" @@ -253,6 +253,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + [[package]] name = "chrono" version = "0.4.44" @@ -275,13 +286,29 @@ dependencies = [ "inout", ] +[[package]] +name = "cmov" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746" + [[package]] name = "cobs" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" dependencies = [ - "thiserror", + "thiserror 2.0.18", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", ] [[package]] @@ -315,6 +342,26 @@ dependencies = [ "tracing", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -330,6 +377,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "critical-section" version = "1.2.0" @@ -388,6 +444,15 @@ dependencies = [ "cipher", ] +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -395,7 +460,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "curve25519-dalek-derive", "fiat-crypto 0.2.9", "rand_core 0.6.4", @@ -405,16 +470,16 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "5.0.0-pre.1" +version = "5.0.0-pre.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f9200d1d13637f15a6acb71e758f64624048d85b31a5fdbfd8eca1e2687d0b7" +checksum = "335f1947f241137a14106b6f5acc5918a5ede29c9d71d3f2cb1678d5075d9fc3" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "curve25519-dalek-derive", - "digest 0.11.0-rc.10", + "digest 0.11.2", "fiat-crypto 0.3.0", - "rand_core 0.9.5", + "rand_core 0.10.1", "rustc_version", "serde", "subtle", @@ -473,6 +538,26 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" +[[package]] +name = "data-encoding-macro" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8142a83c17aa9461d637e649271eae18bf2edd00e91f2e105df36c3c16355bdb" +dependencies = [ + "data-encoding", + "data-encoding-macro-internal", +] + +[[package]] +name = "data-encoding-macro-internal" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" +dependencies = [ + "data-encoding", + "syn", +] + [[package]] name = "der" version = "0.8.0" @@ -566,11 +651,11 @@ dependencies = [ [[package]] name = "digest" -version = "0.11.0-rc.10" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afa94b64bfc6549e6e4b5a3216f22593224174083da7a90db47e951c4fb31725" +checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c" dependencies = [ - "block-buffer 0.11.0", + "block-buffer 0.12.0", "const-oid", "crypto-common 0.2.1", ] @@ -600,51 +685,48 @@ dependencies = [ [[package]] name = "dlopen2" -version = "0.5.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b4f5f101177ff01b8ec4ecc81eead416a8aa42819a2869311b3420fa114ffa" +checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" dependencies = [ "libc", "once_cell", "winapi", ] -[[package]] -name = "document-features" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" -dependencies = [ - "litrs", -] - [[package]] name = "ed25519" -version = "3.0.0-rc.4" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e914c7c52decb085cea910552e24c63ac019e3ab8bf001ff736da9a9d9d890" +checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a" dependencies = [ "pkcs8", - "serde", + "serdect", "signature", ] [[package]] name = "ed25519-dalek" -version = "3.0.0-pre.1" +version = "3.0.0-pre.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad207ed88a133091f83224265eac21109930db09bedcad05d5252f2af2de20a1" +checksum = "20449acd54b660981ae5caa2bcb56d1fe7f25f2e37a38ec507400fab034d4bb6" dependencies = [ - "curve25519-dalek 5.0.0-pre.1", + "curve25519-dalek 5.0.0-pre.6", "ed25519", - "rand_core 0.9.5", + "rand_core 0.10.1", "serde", - "sha2 0.11.0-rc.2", + "sha2 0.11.0", "signature", "subtle", "zeroize", ] +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "embedded-io" version = "0.4.0" @@ -657,18 +739,6 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" -[[package]] -name = "enum-as-inner" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "enum-assoc" version = "1.3.0" @@ -696,23 +766,11 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "fastbloom" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7f34442dbe69c60fe8eaf58a8cafff81a1f278816d8ab4db255b3bef4ac3c4" -dependencies = [ - "getrandom 0.3.4", - "libm", - "rand", - "siphasher", -] - [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "fiat-crypto" @@ -790,7 +848,7 @@ dependencies = [ "diatomic-waker", "futures-core", "pin-project-lite", - "spin 0.10.0", + "spin", ] [[package]] @@ -930,20 +988,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi 5.3.0", - "wasip2", - "wasm-bindgen", -] - [[package]] name = "getrandom" version = "0.4.2" @@ -953,7 +997,8 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi 6.0.0", + "r-efi", + "rand_core 0.10.1", "wasip2", "wasip3", "wasm-bindgen", @@ -1000,15 +1045,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "hash32" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" -dependencies = [ - "byteorder", -] - [[package]] name = "hashbrown" version = "0.15.5" @@ -1020,29 +1056,15 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.1" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" dependencies = [ "allocator-api2", "equivalent", "foldhash 0.2.0", ] -[[package]] -name = "heapless" -version = "0.7.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" -dependencies = [ - "atomic-polyfill", - "hash32", - "rustc_version", - "serde", - "spin 0.9.8", - "stable_deref_trait", -] - [[package]] name = "heck" version = "0.5.0" @@ -1056,28 +1078,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] -name = "hickory-proto" -version = "0.25.2" +name = "hickory-net" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" +checksum = "e2295ed2f9c31e471e1428a8f88a3f0e1f4b27c15049592138d1eebe9c35b183" dependencies = [ "async-trait", "bytes", "cfg-if", "data-encoding", - "enum-as-inner", "futures-channel", "futures-io", "futures-util", "h2", + "hickory-proto", "http", "idna", "ipnet", - "once_cell", + "jni 0.22.4", "rand", - "ring", "rustls", - "thiserror", + "thiserror 2.0.18", "tinyvec", "tokio", "tokio-rustls", @@ -1085,24 +1106,49 @@ dependencies = [ "url", ] +[[package]] +name = "hickory-proto" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bab31817bfb44672a252e97fe81cd0c18d1b2cf892108922f6818820df8c643" +dependencies = [ + "data-encoding", + "idna", + "ipnet", + "jni 0.22.4", + "once_cell", + "prefix-trie", + "rand", + "ring", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "url", +] + [[package]] name = "hickory-resolver" -version = "0.25.2" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" +checksum = "f0d58d28879ceecde6607729660c2667a081ccdc082e082675042793960f178c" dependencies = [ "cfg-if", "futures-util", + "hickory-net", "hickory-proto", "ipconfig", + "ipnet", + "jni 0.22.4", "moka", + "ndk-context", "once_cell", "parking_lot", "rand", "resolv-conf", "rustls", "smallvec", - "thiserror", + "system-configuration", + "thiserror 2.0.18", "tokio", "tokio-rustls", "tracing", @@ -1173,18 +1219,18 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hybrid-array" -version = "0.4.8" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8655f91cd07f2b9d0c24137bd650fe69617773435ee5ec83022377777ce65ef1" +checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214" dependencies = [ "typenum", ] [[package]] name = "hyper" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ "atomic-waker", "bytes", @@ -1197,7 +1243,6 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", "want", @@ -1205,19 +1250,17 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.7" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ "http", "hyper", "hyper-util", "rustls", - "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", - "webpki-roots", ] [[package]] @@ -1237,7 +1280,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.3", + "socket2", "tokio", "tower-service", "tracing", @@ -1269,12 +1312,13 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -1282,9 +1326,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -1295,9 +1339,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -1309,15 +1353,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", @@ -1329,15 +1373,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", @@ -1389,11 +1433,10 @@ dependencies = [ [[package]] name = "igd-next" -version = "0.16.2" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "516893339c97f6011282d5825ac94fc1c7aad5cad26bdc2d0cee068c0bf97f97" +checksum = "bac9a3c8278f43b4cd8463380f4a25653ac843e5b177e1d3eaf849cc9ba10d4d" dependencies = [ - "async-trait", "attohttpc", "bytes", "futures", @@ -1410,12 +1453,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "serde", "serde_core", ] @@ -1431,14 +1474,15 @@ dependencies = [ [[package]] name = "ipconfig" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" dependencies = [ - "socket2 0.5.10", + "socket2", "widestring", - "windows-sys 0.48.0", - "winreg", + "windows-registry", + "windows-result", + "windows-sys 0.61.2", ] [[package]] @@ -1446,12 +1490,15 @@ name = "ipnet" version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" +dependencies = [ + "serde", +] [[package]] name = "iri-string" -version = "0.7.10" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" dependencies = [ "memchr", "serde", @@ -1459,35 +1506,36 @@ dependencies = [ [[package]] name = "iroh" -version = "0.97.0" +version = "1.0.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feb56e7e4b0ec7fba7efa6a236b016a52b5d927d50244aceb9e20566159b1a32" +checksum = "bef865dc2d11a19fe670ff217b68ffc3b511bddf473dc3a3e120090b9f691803" dependencies = [ "backon", + "blake3", "bytes", "cfg_aliases", + "ctutils", "data-encoding", "derive_more", "ed25519-dalek", "futures-util", - "getrandom 0.3.4", + "getrandom 0.4.2", "hickory-resolver", "http", "ipnet", "iroh-base", + "iroh-dns", "iroh-metrics", "iroh-relay", - "n0-error", + "n0-error 1.0.0-rc.0", "n0-future", - "n0-watcher", + "n0-watcher 1.0.0-rc.0", "netwatch", "noq", "noq-proto", "noq-udp", "papaya", "pin-project", - "pkarr", - "pkcs8", "portable-atomic", "portmapper", "rand", @@ -1499,7 +1547,6 @@ dependencies = [ "serde", "smallvec", "strum", - "sync_wrapper", "time", "tokio", "tokio-stream", @@ -1512,14 +1559,15 @@ dependencies = [ [[package]] name = "iroh-auth" -version = "0.1.4" +version = "0.2.0-pre.1" dependencies = [ "anyhow", "hkdf", "iroh", "iroh-gossip", + "lru", "n0-future", - "n0-watcher", + "n0-watcher 0.6.1", "rand", "secrecy", "sha2 0.10.9", @@ -1532,45 +1580,70 @@ dependencies = [ [[package]] name = "iroh-base" -version = "0.97.0" +version = "1.0.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55a354e3396b62c14717ee807dfee9a7f43f6dad47e4ac0fd1d49f1ffad14ef0" +checksum = "af93d67701c00c504982154569192ad384738c0450ba1196930314b955100552" dependencies = [ - "curve25519-dalek 5.0.0-pre.1", + "curve25519-dalek 5.0.0-pre.6", "data-encoding", + "data-encoding-macro", "derive_more", - "digest 0.11.0-rc.10", + "digest 0.11.2", "ed25519-dalek", - "n0-error", - "rand_core 0.9.5", + "getrandom 0.4.2", + "n0-error 1.0.0-rc.0", + "rand", "serde", - "sha2 0.11.0-rc.2", + "sha2 0.11.0", "url", "zeroize", "zeroize_derive", ] +[[package]] +name = "iroh-dns" +version = "1.0.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de4112c91eb64094d77df9d3112606dcf7ff216421afccd2dc762fda5a7b2879" +dependencies = [ + "arc-swap", + "cfg_aliases", + "derive_more", + "hickory-resolver", + "iroh-base", + "n0-error 1.0.0-rc.0", + "n0-future", + "ndk-context", + "rand", + "reqwest", + "rustls", + "simple-dns", + "strum", + "tokio", + "tracing", + "url", +] + [[package]] name = "iroh-gossip" -version = "0.97.0" +version = "0.100.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4db5b64f3cb0a0c8b68b57888acd4cefcd2f0774f1a132d2a498cbb2a92fbc55" +checksum = "2f5cada641f84a2ff3d1acbd43e16490cf3b2a4f675e580c5c2caa5d68ee63b5" dependencies = [ "blake3", "bytes", + "constant_time_eq", "data-encoding", "derive_more", "ed25519-dalek", "futures-concurrency", - "futures-lite", - "futures-util", "hex", "indexmap", "iroh", "iroh-base", "iroh-metrics", "irpc", - "n0-error", + "n0-error 1.0.0-rc.0", "n0-future", "postcard", "rand", @@ -1582,15 +1655,14 @@ dependencies = [ [[package]] name = "iroh-metrics" -version = "0.38.3" +version = "1.0.0-rc.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "761b45ba046134b11eb3e432fa501616b45c4bf3a30c21717578bc07aa6461dd" +checksum = "d102597d0ee523f17fdb672c532395e634dbe945429284c811430d63bacc0d8a" dependencies = [ "iroh-metrics-derive", "itoa", - "n0-error", + "n0-error 1.0.0-rc.0", "portable-atomic", - "postcard", "ryu", "serde", "tracing", @@ -1598,9 +1670,9 @@ dependencies = [ [[package]] name = "iroh-metrics-derive" -version = "0.4.1" +version = "1.0.0-rc.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab063c2bfd6c3d5a33a913d4fdb5252f140db29ec67c704f20f3da7e8f92dbf" +checksum = "91c8e0c97f1dc787107f388433c349397c565572fe6406d600ff7bb7b7fe3b30" dependencies = [ "heck", "proc-macro2", @@ -1610,31 +1682,31 @@ dependencies = [ [[package]] name = "iroh-relay" -version = "0.97.0" +version = "1.0.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d786b260cadfe82ae0b6a9e372e8c78949096a06c857d1c3521355cefced0f55" +checksum = "a70030b9e71c1183bd4f88fbdbebfa1af2a5be549dd6f20a1e8ac3cd0202ee9d" dependencies = [ "blake3", "bytes", "cfg_aliases", "data-encoding", "derive_more", - "getrandom 0.3.4", + "getrandom 0.4.2", "hickory-resolver", "http", "http-body-util", "hyper", "hyper-util", "iroh-base", + "iroh-dns", "iroh-metrics", "lru", - "n0-error", + "n0-error 1.0.0-rc.0", "n0-future", "noq", "noq-proto", "num_enum", "pin-project", - "pkarr", "postcard", "rand", "reqwest", @@ -1652,18 +1724,17 @@ dependencies = [ "vergen-gitcl", "webpki-roots", "ws_stream_wasm", - "z32", ] [[package]] name = "irpc" -version = "0.13.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f47b7c52662d673df377b5ac40c121c7ff56eb764e520fae6543686132f7957" +checksum = "a05799eb70acd04843c327ef939233ccf80f607d30e9ca94857ac7f3d8f18b46" dependencies = [ "futures-util", "irpc-derive", - "n0-error", + "n0-error 1.0.0-rc.0", "n0-future", "serde", "tokio", @@ -1673,9 +1744,9 @@ dependencies = [ [[package]] name = "irpc-derive" -version = "0.10.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c1a4b460634aeed6dc01236a0047867de70e30562d91a0ad031dcb3ac33fb4" +checksum = "445d81dbc1eed4dab6379bf7f97d12ac28ce8e6f3f7d6660c9f333b7b5d8d03b" dependencies = [ "proc-macro2", "quote", @@ -1684,58 +1755,122 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] -name = "js-sys" -version = "0.3.91" +name = "jni" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" dependencies = [ - "once_cell", - "wasm-bindgen", + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", ] [[package]] -name = "lazy_static" -version = "1.5.0" +name = "jni" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.18", + "walkdir", + "windows-link", +] [[package]] -name = "leb128fmt" -version = "0.1.0" +name = "jni-macros" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn", +] [[package]] -name = "libc" -version = "0.2.183" +name = "jni-sys" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] [[package]] -name = "libm" -version = "0.2.16" +name = "jni-sys" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] [[package]] -name = "litemap" -version = "0.8.1" +name = "jni-sys-macros" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn", +] [[package]] -name = "litrs" -version = "1.0.0" +name = "js-sys" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" - -[[package]] -name = "lock_api" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.185" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "lock_api" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" @@ -1764,11 +1899,11 @@ dependencies = [ [[package]] name = "lru" -version = "0.16.3" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" +checksum = "8a860605968fce16869fd239cf4237a82f3ac470723415db603b0e8b6c8d4fb9" dependencies = [ - "hashbrown 0.16.1", + "hashbrown 0.17.0", ] [[package]] @@ -1800,9 +1935,9 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "mio" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "wasi", @@ -1811,9 +1946,9 @@ dependencies = [ [[package]] name = "moka" -version = "0.12.14" +version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85f8024e1c8e71c778968af91d43700ce1d11b219d127d79fb2934153b82b42b" +checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" dependencies = [ "crossbeam-channel", "crossbeam-epoch", @@ -1831,9 +1966,19 @@ name = "n0-error" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af4782b4baf92d686d161c15460c83d16ebcfd215918763903e9619842665cae" +dependencies = [ + "n0-error-macros 0.1.3", + "spez", +] + +[[package]] +name = "n0-error" +version = "1.0.0-rc.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "223e946a84aa91644507a6b7865cfebbb9a231ace499041c747ab0fd30408212" dependencies = [ "anyhow", - "n0-error-macros", + "n0-error-macros 1.0.0-rc.0", "spez", ] @@ -1848,6 +1993,17 @@ dependencies = [ "syn", ] +[[package]] +name = "n0-error-macros" +version = "1.0.0-rc.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "565305a21e6b3bf26640ad98f05a0fda12d3ab4315394566b52a7bddb8b34828" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "n0-future" version = "0.3.2" @@ -1876,15 +2032,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38795f7932e6e9d1c6e989270ef5b3ff24ebb910e2c9d4bed2d28d8bae3007dc" dependencies = [ "derive_more", - "n0-error", + "n0-error 0.1.3", + "n0-future", +] + +[[package]] +name = "n0-watcher" +version = "1.0.0-rc.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928d8039a66cce5efcfd35e88b32d3defc8eba630b3ac451522997f563956a52" +dependencies = [ + "derive_more", + "n0-error 1.0.0-rc.0", "n0-future", ] +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + [[package]] name = "netdev" -version = "0.40.1" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b0a0096d9613ee878dba89bbe595f079d373e3f1960d882e4f2f78ff9c30a0a" +checksum = "57bacaf873ee4eab5646f99b381b271ec75e716902a67cf962c0f328c5eb5bfb" dependencies = [ "block2", "dispatch2", @@ -1893,13 +2066,15 @@ dependencies = [ "libc", "mac-addr", "netlink-packet-core", - "netlink-packet-route", + "netlink-packet-route 0.29.0", "netlink-sys", "objc2-core-foundation", + "objc2-core-wlan", + "objc2-foundation", "objc2-system-configuration", "once_cell", "plist", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1923,6 +2098,18 @@ dependencies = [ "netlink-packet-core", ] +[[package]] +name = "netlink-packet-route" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be8919612f6028ab4eacbbfe1234a9a43e3722c6e0915e7ff519066991905092" +dependencies = [ + "bitflags", + "libc", + "log", + "netlink-packet-core", +] + [[package]] name = "netlink-proto" version = "0.12.0" @@ -1934,7 +2121,7 @@ dependencies = [ "log", "netlink-packet-core", "netlink-sys", - "thiserror", + "thiserror 2.0.18", ] [[package]] @@ -1952,9 +2139,9 @@ dependencies = [ [[package]] name = "netwatch" -version = "0.15.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b1b27babe89ef9f2237bc6c028bea24fa84163a1b6f8f17ff93573ebd7d861f" +checksum = "2071e0c2b5b229622c459096b84f1ad51afa150cdeeefdad491ef3704e581d91" dependencies = [ "atomic-waker", "bytes", @@ -1962,12 +2149,12 @@ dependencies = [ "derive_more", "js-sys", "libc", - "n0-error", + "n0-error 1.0.0-rc.0", "n0-future", - "n0-watcher", + "n0-watcher 1.0.0-rc.0", "netdev", "netlink-packet-core", - "netlink-packet-route", + "netlink-packet-route 0.30.0", "netlink-proto", "netlink-sys", "noq-udp", @@ -1975,7 +2162,7 @@ dependencies = [ "objc2-system-configuration", "pin-project-lite", "serde", - "socket2 0.6.3", + "socket2", "time", "tokio", "tokio-util", @@ -1988,19 +2175,20 @@ dependencies = [ [[package]] name = "noq" -version = "0.17.0" +version = "1.0.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df966fb44ac763bc86da97fa6c811c54ae82ef656575949f93c6dae0c9f09bf" +checksum = "198b99fc085a5db1f7d259edb5ede8311e59f28cdd2687920b4313613d21a73f" dependencies = [ "bytes", "cfg_aliases", + "derive_more", "noq-proto", "noq-udp", "pin-project-lite", "rustc-hash", "rustls", - "socket2 0.6.3", - "thiserror", + "socket2", + "thiserror 2.0.18", "tokio", "tokio-stream", "tracing", @@ -2009,26 +2197,26 @@ dependencies = [ [[package]] name = "noq-proto" -version = "0.16.0" +version = "1.0.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c61b72abd670eebc05b5cf720e077b04a3ef3354bc7bc19f1c3524cb424db7b" +checksum = "1ab0ac774795ce1e42a7e61266e71f3be8110210630441169ac8dda403dd23f1" dependencies = [ "aes-gcm", "bytes", "derive_more", "enum-assoc", - "fastbloom", - "getrandom 0.3.4", + "getrandom 0.4.2", "identity-hash", "lru-slab", "rand", + "rand_pcg", "ring", "rustc-hash", "rustls", "rustls-pki-types", "slab", "sorted-index-buffer", - "thiserror", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -2036,32 +2224,17 @@ dependencies = [ [[package]] name = "noq-udp" -version = "0.9.0" +version = "1.0.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb9be4fedd6b98f3ba82ccd3506f4d0219fb723c3f97c67e12fe1494aa020e44" +checksum = "b3c1520eacd33fd6b009e2e70116b05508ade51db5e0d315ff8bf6b702148c2b" dependencies = [ "cfg_aliases", "libc", - "socket2 0.6.3", + "socket2", "tracing", "windows-sys 0.61.2", ] -[[package]] -name = "ntimestamp" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50f94c405726d3e0095e89e72f75ce7f6587b94a8bd8dc8054b73f65c0fd68c" -dependencies = [ - "base32", - "document-features", - "getrandom 0.2.17", - "httpdate", - "js-sys", - "once_cell", - "serde", -] - [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -2073,9 +2246,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] name = "num-traits" @@ -2139,12 +2312,39 @@ dependencies = [ "objc2", ] +[[package]] +name = "objc2-core-wlan" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e34919aba0d701380d911702455038a8a3587467fe0141d6a71501e7ffe48" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-foundation", + "objc2-foundation", + "objc2-security", + "objc2-security-foundation", +] + [[package]] name = "objc2-encode" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags", + "block2", + "libc", + "objc2", + "objc2-core-foundation", +] + [[package]] name = "objc2-security" version = "0.3.2" @@ -2156,6 +2356,16 @@ dependencies = [ "objc2-core-foundation", ] +[[package]] +name = "objc2-security-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef76382e9cedd18123099f17638715cc3d81dba3637d4c0d39ab69df2ef345a5" +dependencies = [ + "objc2", + "objc2-foundation", +] + [[package]] name = "objc2-system-configuration" version = "0.3.2" @@ -2186,11 +2396,17 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + [[package]] name = "papaya" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f92dd0b07c53a0a0c764db2ace8c541dc47320dad97c2200c2a637ab9dd2328f" +checksum = "997ee03cd38c01469a7046643714f0ad28880bcb9e6679ff0666e24817ca19b7" dependencies = [ "equivalent", "seize", @@ -2282,36 +2498,11 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkarr" -version = "5.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f950360d31be432c0c9467fba5024a94f55128e7f32bc9d32db140369f24c77" -dependencies = [ - "base32", - "bytes", - "cfg_aliases", - "document-features", - "ed25519-dalek", - "getrandom 0.4.2", - "ntimestamp", - "self_cell", - "serde", - "simple-dns", - "thiserror", -] - [[package]] name = "pkcs8" -version = "0.11.0-rc.11" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12922b6296c06eb741b02d7b5161e3aaa22864af38dfa025a1a3ba3f68c84577" +checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" dependencies = [ "der", "spki", @@ -2337,7 +2528,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] @@ -2353,26 +2544,25 @@ dependencies = [ [[package]] name = "portmapper" -version = "0.15.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74748bc706fa6b6aebac6bbe0bbe0de806b384cb5c557ea974f771360a4e3858" +checksum = "64959cbabf952c8ffcbaea13745308508f1f825922f4068353f3de08d42cf214" dependencies = [ "base64", "bytes", "derive_more", - "futures-lite", - "futures-util", "hyper-util", "igd-next", "iroh-metrics", "libc", - "n0-error", + "n0-error 1.0.0-rc.0", + "n0-future", "netwatch", "num_enum", "rand", "serde", "smallvec", - "socket2 0.6.3", + "socket2", "time", "tokio", "tokio-util", @@ -2390,7 +2580,6 @@ dependencies = [ "cobs", "embedded-io 0.4.0", "embedded-io 0.6.1", - "heapless", "postcard-derive", "serde", ] @@ -2408,9 +2597,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -2422,12 +2611,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] -name = "ppv-lite86" -version = "0.2.21" +name = "prefix-trie" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +checksum = "23370be78b7e5bcbb0cab4a02047eb040279a693c78daad04c2c5f1c24a83503" dependencies = [ - "zerocopy", + "either", + "ipnet", + "num-traits", ] [[package]] @@ -2467,61 +2658,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "quinn" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" -dependencies = [ - "bytes", - "cfg_aliases", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "socket2 0.6.3", - "thiserror", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-proto" -version = "0.11.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" -dependencies = [ - "bytes", - "getrandom 0.3.4", - "lru-slab", - "rand", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "slab", - "thiserror", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2 0.6.3", - "tracing", - "windows-sys 0.60.2", -] - [[package]] name = "quote" version = "1.0.45" @@ -2531,12 +2667,6 @@ 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" @@ -2545,22 +2675,13 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.9.2" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ - "rand_chacha", - "rand_core 0.9.5", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.5", + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.1", ] [[package]] @@ -2574,11 +2695,17 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.5" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" dependencies = [ - "getrandom 0.3.4", + "rand_core 0.10.1", ] [[package]] @@ -2609,9 +2736,9 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" -version = "0.12.28" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" dependencies = [ "base64", "bytes", @@ -2627,12 +2754,9 @@ dependencies = [ "log", "percent-encoding", "pin-project-lite", - "quinn", "rustls", "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", + "rustls-platform-verifier", "sync_wrapper", "tokio", "tokio-rustls", @@ -2645,7 +2769,6 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots", ] [[package]] @@ -2670,9 +2793,9 @@ dependencies = [ [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustc_version" @@ -2685,9 +2808,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.37" +version = "0.23.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" dependencies = [ "log", "once_cell", @@ -2698,6 +2821,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pki-types" version = "1.14.0" @@ -2708,11 +2843,38 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni 0.21.1", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" -version = "0.103.9" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "ring", "rustls-pki-types", @@ -2731,6 +2893,24 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -2752,6 +2932,29 @@ dependencies = [ "zeroize", ] +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "seize" version = "0.5.1" @@ -2762,17 +2965,11 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "self_cell" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b12e76d157a900eb52e81bc6e9f3069344290341720e9178cde2407113ac8d89" - [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "send_wrapper" @@ -2834,17 +3031,21 @@ dependencies = [ ] [[package]] -name = "serde_urlencoded" -version = "0.7.1" +name = "serdect" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e" dependencies = [ - "form_urlencoded", - "itoa", - "ryu", + "base16ct", "serde", ] +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + [[package]] name = "sha2" version = "0.10.9" @@ -2852,19 +3053,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest 0.10.7", ] [[package]] name = "sha2" -version = "0.11.0-rc.2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1e3878ab0f98e35b2df35fe53201d088299b41a6bb63e3e34dada2ac4abd924" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ "cfg-if", - "cpufeatures", - "digest 0.11.0-rc.10", + "cpufeatures 0.3.0", + "digest 0.11.2", ] [[package]] @@ -2894,9 +3095,19 @@ dependencies = [ [[package]] name = "signature" -version = "3.0.0-rc.10" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f1880df446116126965eeec169136b2e0251dba37c6223bcc819569550edea3" +checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" + +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version", + "simdutf8", +] [[package]] name = "simdutf8" @@ -2906,19 +3117,13 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "simple-dns" -version = "0.9.3" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee851d0e5e7af3721faea1843e8015e820a234f81fda3dea9247e15bac9a86a" +checksum = "df350943049174c4ae8ced56c604e28270258faec12a6a48637a7655287c9ce0" dependencies = [ "bitflags", ] -[[package]] -name = "siphasher" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" - [[package]] name = "slab" version = "0.4.12" @@ -2931,16 +3136,6 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" -[[package]] -name = "socket2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "socket2" version = "0.6.3" @@ -2980,15 +3175,6 @@ dependencies = [ "syn", ] -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] - [[package]] name = "spin" version = "0.10.0" @@ -2997,9 +3183,9 @@ checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" [[package]] name = "spki" -version = "0.8.0-rc.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8baeff88f34ed0691978ec34440140e1572b68c7dd4a495fd14a3dc1944daa80" +checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" dependencies = [ "base64ct", "der", @@ -3075,19 +3261,60 @@ dependencies = [ "syn", ] +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tagptr" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -3146,9 +3373,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", @@ -3171,25 +3398,25 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.50.0" +version = "1.52.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" dependencies = [ "bytes", "libc", "mio", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.3", + "socket2", "tokio-macros", "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.6.1" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", @@ -3234,20 +3461,21 @@ dependencies = [ [[package]] name = "tokio-websockets" -version = "0.12.3" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1b6348ebfaaecd771cecb69e832961d277f59845d4220a584701f72728152b7" +checksum = "dad543404f98bfc969aeb71994105c592acfc6c43323fddcd016bb208d1c65cb" dependencies = [ "base64", "bytes", "futures-core", "futures-sink", - "getrandom 0.3.4", + "getrandom 0.4.2", "http", "httparse", "rand", "ring", "rustls-pki-types", + "sha1_smol", "simdutf8", "tokio", "tokio-rustls", @@ -3256,18 +3484,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.0.0+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.25.4+spec-1.1.0" +version = "0.25.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" dependencies = [ "indexmap", "toml_datetime", @@ -3277,9 +3505,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.9+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ "winnow", ] @@ -3399,9 +3627,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "unicode-ident" @@ -3411,9 +3639,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] name = "unicode-xid" @@ -3458,9 +3686,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.22.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -3482,32 +3710,21 @@ dependencies = [ "anyhow", "derive_builder", "rustversion", - "vergen-lib 9.1.0", + "vergen-lib", ] [[package]] name = "vergen-gitcl" -version = "1.0.8" +version = "9.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9dfc1de6eb2e08a4ddf152f1b179529638bedc0ea95e6d667c014506377aefe" +checksum = "77ff3b5300a085d6bcd8fc96a507f706a28ae3814693236c9b409db71a1d15b9" dependencies = [ "anyhow", "derive_builder", "rustversion", "time", "vergen", - "vergen-lib 0.1.6", -] - -[[package]] -name = "vergen-lib" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b07e6010c0f3e59fcb164e0163834597da68d1f864e2b8ca49f74de01e9c166" -dependencies = [ - "anyhow", - "derive_builder", - "rustversion", + "vergen-lib", ] [[package]] @@ -3527,6 +3744,16 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -3544,11 +3771,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.57.1", ] [[package]] @@ -3557,14 +3784,14 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.51.0", ] [[package]] name = "wasm-bindgen" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" dependencies = [ "cfg-if", "once_cell", @@ -3575,23 +3802,19 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.64" +version = "0.4.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" +checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" dependencies = [ - "cfg-if", - "futures-util", "js-sys", - "once_cell", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3599,9 +3822,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" dependencies = [ "bumpalo", "proc-macro2", @@ -3612,9 +3835,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" dependencies = [ "unicode-ident", ] @@ -3643,9 +3866,9 @@ dependencies = [ [[package]] name = "wasm-streams" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" dependencies = [ "futures-util", "js-sys", @@ -3668,9 +3891,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.91" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" +checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" dependencies = [ "js-sys", "wasm-bindgen", @@ -3686,11 +3909,20 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webpki-roots" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" dependencies = [ "rustls-pki-types", ] @@ -3717,6 +3949,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -3806,6 +4047,17 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + [[package]] name = "windows-result" version = "0.4.1" @@ -3826,11 +4078,11 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.48.0" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets 0.48.5", + "windows-targets 0.42.2", ] [[package]] @@ -3842,24 +4094,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -3871,17 +4105,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.5" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] @@ -3893,30 +4127,13 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", + "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - [[package]] name = "windows-threading" version = "0.2.1" @@ -3928,9 +4145,9 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.5" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" @@ -3938,17 +4155,11 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - [[package]] name = "windows_aarch64_msvc" -version = "0.48.5" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" @@ -3956,17 +4167,11 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - [[package]] name = "windows_i686_gnu" -version = "0.48.5" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" @@ -3974,29 +4179,17 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - [[package]] name = "windows_i686_msvc" -version = "0.48.5" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" @@ -4004,17 +4197,11 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - [[package]] name = "windows_x86_64_gnu" -version = "0.48.5" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" @@ -4022,17 +4209,11 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.5" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" @@ -4040,17 +4221,11 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - [[package]] name = "windows_x86_64_msvc" -version = "0.48.5" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" @@ -4058,31 +4233,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - [[package]] name = "winnow" -version = "0.7.15" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" dependencies = [ "memchr", ] -[[package]] -name = "winreg" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "wit-bindgen" version = "0.51.0" @@ -4092,6 +4251,12 @@ dependencies = [ "wit-bindgen-rust-macro", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "wit-bindgen-core" version = "0.51.0" @@ -4173,24 +4338,24 @@ dependencies = [ [[package]] name = "wmi" -version = "0.18.3" +version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "003e65f4934cf9449b9ce913ad822cd054a5af669d24f93db101fdb02856bb23" +checksum = "7c81b85c57a57500e56669586496bf2abd5cf082b9d32995251185d105208b64" dependencies = [ "chrono", "futures", "log", "serde", - "thiserror", + "thiserror 2.0.18", "windows", "windows-core", ] [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "ws_stream_wasm" @@ -4205,7 +4370,7 @@ dependencies = [ "pharos", "rustc_version", "send_wrapper", - "thiserror", + "thiserror 2.0.18", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -4228,9 +4393,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -4239,9 +4404,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", @@ -4249,46 +4414,20 @@ dependencies = [ "synstructure", ] -[[package]] -name = "z32" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2164e798d9e3d84ee2c91139ace54638059a3b23e361f5c11781c2c6459bde0f" - -[[package]] -name = "zerocopy" -version = "0.8.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", @@ -4318,9 +4457,9 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", @@ -4329,9 +4468,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -4340,9 +4479,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index f707859..5a65e83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iroh-auth" -version = "0.1.4" +version = "0.2.0-pre.1" edition = "2021" authors = ["rustonbsd "] description = "Authentication middleware for iroh" @@ -12,27 +12,35 @@ categories = ["network-programming"] [dependencies] -iroh = { version = "0.97" } +iroh = { version = "1.0.0-rc.1" } spake2 = { version = "0.4" } hkdf = { version = "0.12" } secrecy = { version = "0.10" } sha2 = { version = "0.10" } subtle = { version = "2.6" } -rand = { version = "0.9" } +rand = { version = "0.10" } n0-future = { version = "0.3" } n0-watcher = { version = "0.6" } tracing = { version = "0.1" } -tokio = { version = "1" } +tokio = { version = "1", features = [ + "macros", + "rt-multi-thread", + "sync", + "time", +] } +lru = { version = "0.18" } -iroh-gossip = { version = "0.97", optional = true } -anyhow = { version = "1", optional = true} -tracing-subscriber = { version = "0.3", features = ["env-filter"], optional = true } +iroh-gossip = { version = "0.100", optional = true } +anyhow = { version = "1", optional = true } +tracing-subscriber = { version = "0.3", features = [ + "env-filter", +], optional = true } [features] -gossip-example = ["iroh-gossip", "anyhow", "tracing-subscriber"] +gossip-example = ["iroh-gossip", "anyhow", "tracing-subscriber", "tokio/signal"] default = [] [[example]] name = "gossip" path = "examples/gossip.rs" -required-features = ["gossip-example"] \ No newline at end of file +required-features = ["gossip-example"] diff --git a/README.md b/README.md index 9d87610..0585fa3 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,8 @@ Add this to your `Cargo.toml`: ```toml [dependencies] -iroh = "0.96" -iroh-auth = "0.1" +iroh = "0.98" +iroh-auth = "0.2.0-pre.1" ``` ## Usage @@ -44,14 +44,14 @@ async fn main() -> Result<(), String> { let auth = Authenticator::new("my-super-secret-password"); // 2. Build the endpoint with the auth hooks - let endpoint = Endpoint::builder() + let endpoint = Endpoint::builder(iroh::endpoint::presets::N0) .hooks(auth.clone()) .bind() .await.map_err(|e| e.to_string())?; // 3. The authenticator needs a reference to the bound endpoint // to initiate authentication handshakes. - auth.set_endpoint(&endpoint); + auth.set_endpoint(&endpoint).await; // 4. Register the auth protocol handler let router = Router::builder(endpoint) @@ -78,7 +78,7 @@ sequenceDiagram Client->>Server: 1. App Connection Request activate Server - Note right of Server: ✋ Paused (Waiting for Auth) + Note right of Server: Paused (Waiting for Auth) rect rgb(225, 255, 225) Note over Client, Server: 2. Authentication (Background) @@ -87,11 +87,11 @@ sequenceDiagram alt Success Server->>Server: Verify Secret (OK) - Note right of Server: ✅ Resumed + Note right of Server: Resumed Server-->>Client: 3. App Connection Accepted else Failure Server->>Server: Verify Secret (Fail) - Note right of Server: ❌ Rejected + Note right of Server: Rejected Server-->>Client: 3. Connection Closed end deactivate Server diff --git a/examples/basic.rs b/examples/basic.rs index f7047bb..78f4800 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -27,7 +27,7 @@ async fn main() -> Result<(), String> { // 3. The authenticator needs a reference to the bound endpoint // to initiate authentication handshakes. - auth.set_endpoint(&endpoint); + auth.set_endpoint(&endpoint).await; // 4. Register the auth protocol handler let router = Router::builder(endpoint) diff --git a/examples/gossip.rs b/examples/gossip.rs index a534e50..3364c77 100644 --- a/examples/gossip.rs +++ b/examples/gossip.rs @@ -23,7 +23,7 @@ async fn main() -> Result<(), String> { // #1 Create Authenticator let auth = Authenticator::new("my-secure-network-secret-12345"); - let endpoint = Endpoint::builder() + let endpoint = Endpoint::builder(iroh::endpoint::presets::N0) // #2 Add auth hooks .hooks(auth.clone()) .bind() @@ -31,7 +31,7 @@ async fn main() -> Result<(), String> { .map_err(|e| e.to_string())?; // #3 Pass endpoint to the Authenticator for establishing auth connections - auth.set_endpoint(&endpoint); + auth.set_endpoint(&endpoint).await; let gossip = Gossip::builder().spawn(endpoint.clone()); diff --git a/src/auth.rs b/src/auth.rs new file mode 100644 index 0000000..d96a5d8 --- /dev/null +++ b/src/auth.rs @@ -0,0 +1,691 @@ +use std::{num::NonZeroUsize, sync::Arc, time::Duration}; + +use hkdf::Hkdf; +use iroh::{endpoint::Connection, Endpoint, EndpointId, PublicKey}; +use lru::LruCache; +use n0_watcher::Watchable; +use secrecy::{ExposeSecret, SecretSlice}; +use sha2::Sha512; +use spake2::{Ed25519Group, Identity, Password, Spake2}; +use subtle::ConstantTimeEq; +use tokio::{ + sync::Mutex, + time::{timeout, Instant}, +}; +use tracing::{error, info, trace, warn}; + +use crate::{ + protocol::release_in_flight, AuthenticatorError, IntoSecret, ALPN, AUTH_TIMEOUT, + TRANSMISSION_TIMEOUT, +}; + +#[derive(Debug, Clone)] +pub struct Authenticator { + secret: SecretSlice, + endpoint: Arc>>, + pub(crate) auth_state: Arc>>, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum AuthState { + Unauthenticated, + InFlight, + Authenticated, + Blocked, +} + +impl std::fmt::Display for AuthState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AuthState::Unauthenticated => write!(f, "Unauthenticated"), + AuthState::InFlight => write!(f, "InFlight"), + AuthState::Authenticated => write!(f, "Authenticated"), + AuthState::Blocked => write!(f, "Blocked"), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum RegisterResponse { + InFlightRegistered, // auth after this + AlreadyInFlight, // another auth was running while we called this, only check is_authenticated, don't auth again + AlreadyAuthenticated, // we are already authenticated, same as AlreadyInFlight => only check is_authenticated, don't auth again + AlreadyBlocked, // we are blocked, DO NOT AUTH AGAIN, just reject +} + +#[derive(Debug, Clone)] +pub(crate) struct WatchableRemote { + id: PublicKey, + inner: Watchable, +} + +impl WatchableRemote { + /// inner is AuthState::Unauthenticated by default + pub fn new(id: PublicKey) -> Self { + Self { + id, + inner: Watchable::new(AuthState::Unauthenticated), + } + } + + pub fn watcher(&self) -> Watchable { + self.inner.clone() + } + + pub fn id(&self) -> &PublicKey { + &self.id + } + + pub fn state(&self) -> AuthState { + self.inner.get() + } + + pub fn set_state(&self, state: AuthState) { + let previous_state = self.inner.get(); + if previous_state == state { + trace!( + "[watchable_remote] endpoint {} state unchanged at {}", + self.id, + state + ); + } else { + trace!( + "[watchable_remote] endpoint {} state transition {} -> {}", + self.id, + previous_state, + state + ); + } + self.inner.set(state).ok(); + } +} + +impl PartialEq for WatchableRemote { + fn eq(&self, other: &Self) -> bool { + self.id() == other.id() + } +} + +impl Eq for WatchableRemote {} + +impl PartialEq for WatchableRemote { + fn eq(&self, other: &PublicKey) -> bool { + self.id() == other + } +} + +impl std::hash::Hash for WatchableRemote { + fn hash(&self, state: &mut H) { + self.id().hash(state); + } +} + +impl Authenticator { + pub const ALPN: &'static [u8] = crate::ALPN; + const ACCEPT_CONTEXT: &'static [u8] = b"iroh-auth-accept"; + const OPEN_CONTEXT: &'static [u8] = b"iroh-auth-open"; + + pub fn new(secret: S) -> Self { + Self { + secret: secret.into_secret(), + endpoint: Arc::new(Mutex::new(None)), + auth_state: Arc::new(Mutex::new(LruCache::new( + NonZeroUsize::new(crate::LRU_CACHE_SIZE).expect("LRU_CACHE_SIZE must be > 0"), + ))), + } + } + + pub async fn set_endpoint(&self, endpoint: &Endpoint) { + let mut guard = self.endpoint.lock().await; + if guard.is_none() { + *guard = Some(endpoint.clone()); + trace!("Authenticator endpoint set to {}", endpoint.id()); + } else { + trace!("Authenticator endpoint already set, ignoring {}", endpoint.id()); + } + } + + async fn id(&self) -> Result { + self.endpoint + .lock() + .await + .as_ref() + .map(|ep| ep.id()) + .ok_or(AuthenticatorError::EndpointNotSet) + } + + pub(crate) async fn endpoint(&self) -> Result { + self.endpoint + .lock() + .await + .as_ref() + .cloned() + .ok_or(AuthenticatorError::EndpointNotSet) + } + + pub async fn is_authenticated(&self, id: &PublicKey) -> bool { + let state = self + .auth_state + .lock() + .await + .get(id) + .map(|watchable| watchable.state()); + + match state { + Some(AuthState::Authenticated) => { + trace!("[is_authenticated] endpoint {} is authenticated", id); + true + } + Some(other) => { + trace!( + "[is_authenticated] endpoint {} is not authenticated, current state {}", + id, + other + ); + false + } + None => { + trace!("[is_authenticated] endpoint {} has no auth state entry", id); + false + } + } + } + + #[cfg(test)] + pub async fn list_authenticated(&self) -> Vec { + self.auth_state + .lock() + .await + .iter() + .filter_map(|(id, watchable)| { + if watchable.state() == AuthState::Authenticated { + Some(*id) + } else { + None + } + }) + .collect::>() + } + + #[cfg(test)] + pub async fn list_blocked(&self) -> Vec { + self.auth_state + .lock() + .await + .iter() + .filter_map(|(id, watchable)| { + if watchable.state() == AuthState::Blocked { + Some(*id) + } else { + None + } + }) + .collect::>() + } +} + +impl Authenticator { + async fn end_of_auth( + &self, + send: &mut iroh::endpoint::SendStream, + recv: &mut iroh::endpoint::RecvStream, + open: bool, + ) -> Result<(), AuthenticatorError> { + let start = Instant::now(); + trace!( + "[end_of_auth] starting shutdown sequence for {} side", + if open { "open" } else { "accept" } + ); + send.finish().map_err(|err| { + error!("[end_of_auth] failed to finish stream: {}", err); + if open { + AuthenticatorError::OpenFailed(format!("Failed to finish stream: {}", err)) + } else { + AuthenticatorError::AcceptFailed(format!("Failed to finish stream: {}", err)) + } + })?; + + const MAX_READ_SIZE: usize = 1024; + if let Err(err) = tokio::time::timeout(AUTH_TIMEOUT, recv.read_to_end(MAX_READ_SIZE)) + .await + .map_err(|_| { + if open { + AuthenticatorError::OpenFailed( + "Failed to wait for stream stopped: timeout".to_string(), + ) + } else { + AuthenticatorError::AcceptFailed( + "Failed to wait for stream stopped: timeout".to_string(), + ) + } + }) + .and_then(|res| { + res.map_err(|err| { + if open { + AuthenticatorError::OpenFailed(format!( + "Failed to read remaining data from stream: {}", + err + )) + } else { + AuthenticatorError::AcceptFailed(format!( + "Failed to read remaining data from stream: {}", + err + )) + } + }) + }) + { + warn!("[end_of_auth] {}", err); + } + trace!( + "[end_of_auth] shutdown sequence for {} side completed in {:?}", + if open { "open" } else { "accept" }, + start.elapsed() + ); + Ok(()) + } + + /// Accept an incoming connection and perform SPAKE2 authentication. + /// On success, adds the remote ID to the authenticated set. + /// Returns Ok(()) on success, or an AuthenticatorError on failure. + pub(crate) async fn auth_accept(&self, conn: Connection) -> Result<(), AuthenticatorError> { + let remote_id = conn.remote_id(); + let start = Instant::now(); + trace!("[auth_accept] accepting auth connection from {}", remote_id); + trace!("[auth_accept] waiting for inbound bidirectional stream from {}", remote_id); + let (mut send, mut recv) = timeout(TRANSMISSION_TIMEOUT, conn.accept_bi()) + .await + .map_err(|_| { + error!("[auth_accept] accept bidirectional stream timed out"); + AuthenticatorError::AcceptFailed( + "Accept bidirectional stream timed out".to_string(), + ) + })? + .map_err(|err| { + error!("[auth_accept] accept bidirectional stream failed: {}", err); + AuthenticatorError::AcceptFailed(format!( + "Accept bidirectional stream failed: {}", + err + )) + })?; + trace!( + "[auth_accept] bidirectional stream accepted from {} after {:?}", + remote_id, + start.elapsed() + ); + + let (spake, token_b) = Spake2::::start_b( + &Password::new(self.secret.expose_secret()), + &Identity::new(conn.remote_id().as_bytes()), + &Identity::new(self.id().await?.as_bytes()), + ); + + let mut token_a = [0u8; 33]; + trace!("[auth_accept] waiting for token_a from {}", remote_id); + recv.read_exact(&mut token_a).await.map_err(|err| { + error!("[auth_accept] failed to read token_a: {}", err); + AuthenticatorError::AcceptFailed(format!("Failed to read token_a: {}", err)) + })?; + trace!("[auth_accept] received token_a from {}", remote_id); + + trace!("[auth_accept] sending token_b to {}", remote_id); + send.write_all(&token_b).await.map_err(|err| { + error!("[auth_accept] failed to write token_b: {}", err); + AuthenticatorError::AcceptFailed(format!("Failed to write token_b: {}", err)) + })?; + trace!("[auth_accept] sent token_b to {}", remote_id); + + let shared_secret = spake.finish(&token_a).map_err(|err| { + error!("[auth_accept] SPAKE2 invalid: {}", err); + AuthenticatorError::AcceptFailedAndBlock(format!("SPAKE2 invalid: {}", err), remote_id) + })?; + trace!("[auth_accept] derived shared secret for {}", remote_id); + + let hk = Hkdf::::new(None, shared_secret.as_slice()); + let mut accept_key = [0u8; 64]; + let mut open_key = [0u8; 64]; + hk.expand(Self::ACCEPT_CONTEXT, &mut accept_key) + .map_err(|err| { + error!("[auth_accept] failed to expand accept_key: {}", err); + AuthenticatorError::AcceptFailed(format!("Failed to expand accept_key: {}", err)) + })?; + hk.expand(Self::OPEN_CONTEXT, &mut open_key) + .map_err(|err| { + error!("[auth_accept] failed to expand open_key: {}", err); + AuthenticatorError::AcceptFailed(format!("Failed to expand open_key: {}", err)) + })?; + + trace!("[auth_accept] sending accept_key to {}", remote_id); + send.write_all(&accept_key).await.map_err(|err| { + error!("[auth_accept] failed to write accept_key: {}", err); + AuthenticatorError::AcceptFailed(format!("Failed to write accept_key: {}", err)) + })?; + let mut remote_open_key = [0u8; 64]; + trace!("[auth_accept] waiting for remote_open_key from {}", remote_id); + recv.read_exact(&mut remote_open_key).await.map_err(|err| { + error!("[auth_accept] failed to read remote_open_key: {}", err); + AuthenticatorError::AcceptFailed(format!("Failed to read remote_open_key: {}", err)) + })?; + trace!("[auth_accept] received remote_open_key from {}", remote_id); + + let _ = self.end_of_auth(&mut send, &mut recv, false).await; + + if !bool::from(remote_open_key.ct_eq(&open_key)) { + error!("[auth_accept] remote open_key mismatch"); + return Err(AuthenticatorError::AcceptFailedAndBlock( + "Remote open_key mismatch".to_string(), + remote_id, + )); + } + + info!( + "[auth_accept] authenticated connection from {} in {:?}", + remote_id, + start.elapsed() + ); + + Ok(()) + } + + /// Open an outgoing connection and perform SPAKE2 authentication. + /// On success, adds the remote ID to the authenticated set. + /// Returns Ok(()) on success, or an AuthenticatorError on failure. + pub(crate) async fn auth_open(&self, conn: Connection) -> Result<(), AuthenticatorError> { + let remote_id = conn.remote_id(); + let start = Instant::now(); + trace!("[auth_open] opening auth connection to {}", remote_id); + trace!("[auth_open] waiting to open bidirectional stream to {}", remote_id); + let (mut send, mut recv) = timeout(TRANSMISSION_TIMEOUT, conn.open_bi()) + .await + .map_err(|_| { + error!("[auth_open] open bidirectional stream timed out"); + AuthenticatorError::OpenFailed("Open bidirectional stream timed out".to_string()) + })? + .map_err(|err| { + error!("[auth_open] open bidirectional stream failed: {}", err); + AuthenticatorError::OpenFailed(format!("Open bidirectional stream failed: {}", err)) + })?; + trace!( + "[auth_open] bidirectional stream opened to {} after {:?}", + remote_id, + start.elapsed() + ); + + let (spake, token_a) = Spake2::::start_a( + &Password::new(self.secret.expose_secret()), + &Identity::new(self.id().await?.as_bytes()), + &Identity::new(conn.remote_id().as_bytes()), + ); + + trace!("[auth_open] sending token_a to {}", remote_id); + send.write_all(&token_a).await.map_err(|err| { + error!("[auth_open] failed to write token_a: {}", err); + AuthenticatorError::OpenFailed(format!("Failed to write token_a: {}", err)) + })?; + trace!("[auth_open] sent token_a to {}", remote_id); + + let mut token_b = [0u8; 33]; + trace!("[auth_open] waiting for token_b from {}", remote_id); + recv.read_exact(&mut token_b).await.map_err(|err| { + error!("[auth_open] failed to read token_b: {}", err); + AuthenticatorError::OpenFailed(format!("Failed to read token_b: {}", err)) + })?; + trace!("[auth_open] received token_b from {}", remote_id); + + let shared_secret = spake.finish(&token_b).map_err(|err| { + error!("[auth_open] SPAKE2 invalid: {}", err); + AuthenticatorError::OpenFailedAndBlock(format!("SPAKE2 invalid: {}", err), remote_id) + })?; + trace!("[auth_open] derived shared secret for {}", remote_id); + + let hk = Hkdf::::new(None, shared_secret.as_slice()); + let mut accept_key = [0u8; 64]; + let mut open_key = [0u8; 64]; + hk.expand(Self::ACCEPT_CONTEXT, &mut accept_key) + .map_err(|err| { + error!("[auth_open] failed to expand accept_key: {}", err); + AuthenticatorError::OpenFailed(format!("Failed to expand accept_key: {}", err)) + })?; + hk.expand(Self::OPEN_CONTEXT, &mut open_key) + .map_err(|err| { + error!("[auth_open] failed to expand open_key: {}", err); + AuthenticatorError::OpenFailed(format!("Failed to expand open_key: {}", err)) + })?; + + let mut remote_accept_key = [0u8; 64]; + trace!("[auth_open] waiting for remote_accept_key from {}", remote_id); + recv.read_exact(&mut remote_accept_key) + .await + .map_err(|err| { + error!("[auth_open] failed to read remote_accept_key: {}", err); + AuthenticatorError::OpenFailed(format!("Failed to read remote_accept_key: {}", err)) + })?; + trace!("[auth_open] received remote_accept_key from {}", remote_id); + + if !bool::from(remote_accept_key.ct_eq(&accept_key)) { + error!("[auth_open] remote accept_key mismatch"); + + // Writing a random dummy open_key back to finishing the stream but not give away + // that the accept_key was correct to avoid leaking information to an attacker about valid accept_keys + // (probably not needed but better safe than sorry ^^) + send.write_all(&rand::random::<[u8; 64]>()).await.ok(); + let _ = self.end_of_auth(&mut send, &mut recv, true).await; + + return Err(AuthenticatorError::OpenFailedAndBlock( + "Remote accept_key mismatch".to_string(), + remote_id, + )); + } + + trace!("[auth_open] sending open_key to {}", remote_id); + send.write_all(&open_key).await.map_err(|err| { + error!("[auth_open] failed to write open_key: {}", err); + AuthenticatorError::OpenFailed(format!("Failed to write open_key: {}", err)) + })?; + let _ = self.end_of_auth(&mut send, &mut recv, true).await; + + info!( + "[auth_open] authenticated connection to {} in {:?}", + remote_id, + start.elapsed() + ); + + Ok(()) + } +} + +impl Authenticator { + pub(crate) async fn perform_auth( + &self, + remote_id: EndpointId, + endpoint: Endpoint, + ) -> Result<(), AuthenticatorError> { + let start_time = Instant::now(); + let mut attempt = 0usize; + trace!("[perform_auth] starting authentication workflow for {}", remote_id); + if let Err(err) = timeout(AUTH_TIMEOUT, endpoint.online()).await.map_err(|_| { + AuthenticatorError::OpenFailed( + "[before_connect] awaiting endpoint.online() timed out".to_string(), + ) + }) { + error!( + "[before_connect] awaiting endpoint.online() failed: {}", + err + ); + release_in_flight( + self.auth_state.clone(), + remote_id, + AuthState::Unauthenticated, + ) + .await + .map_err(|err| { + AuthenticatorError::OpenFailed(format!( + "[before_connect] failed to release in-flight state for {}: {}", + remote_id, err + )) + })?; + return Err(err); + } + trace!( + "[perform_auth] endpoint is online for {}, entering retry loop after {:?}", + remote_id, + start_time.elapsed() + ); + + while start_time.elapsed() < AUTH_TIMEOUT { + attempt += 1; + let attempt_start = Instant::now(); + trace!( + "[perform_auth] attempt {} connecting to {} with {:?} remaining", + attempt, + remote_id, + remaining_timeout(start_time, AUTH_TIMEOUT) + ); + match timeout( + remaining_timeout(start_time, AUTH_TIMEOUT), + endpoint.connect(remote_id, ALPN), + ) + .await + { + Ok(Ok(conn)) => { + trace!( + "[perform_auth] attempt {} connected to {} after {:?}, starting auth_open", + attempt, + remote_id, + attempt_start.elapsed() + ); + match timeout( + remaining_timeout(start_time, AUTH_TIMEOUT), + self.auth_open(conn), + ) + .await + { + Ok(Ok(())) => { + trace!( + "[perform_auth] attempt {} authentication successful for {} after {:?}", + attempt, + remote_id, + attempt_start.elapsed() + ); + + release_in_flight( + self.auth_state.clone(), + remote_id, + AuthState::Authenticated, + ) + .await + .map_err(|err| { + error!( + "[before_connect] failed to release in-flight state for {}: {}", + remote_id, err + ); + AuthenticatorError::OpenFailed(format!( + "[before_connect] failed to release in-flight state for {}: {}", + remote_id, err + )) + })?; + info!( + "[perform_auth] authentication workflow for {} completed successfully in {:?}", + remote_id, + start_time.elapsed() + ); + return Ok(()); + } + Ok(Err(err)) => match &err { + AuthenticatorError::OpenFailedAndBlock(msg, public_key) => { + warn!( + "[perform_auth] attempt {} authentication failed and blocking {} after {:?}: {}", + attempt, + public_key, + attempt_start.elapsed(), + msg + ); + release_in_flight( + self.auth_state.clone(), + remote_id, + AuthState::Blocked, + ) + .await + .map_err(|err| { + AuthenticatorError::OpenFailedAndBlock(format!( + "[before_connect] failed to release in-flight state for {}: {}", + public_key, err + ), *public_key) + })?; + return Err(AuthenticatorError::OpenFailedAndBlock( + msg.clone(), + *public_key, + )); + } + _ => { + warn!( + "[perform_auth] attempt {} authentication failed for {} after {:?}: {}", + attempt, + remote_id, + attempt_start.elapsed(), + err + ); + } + }, + Err(_) => { + warn!( + "[perform_auth] attempt {} auth_open timed out for {} after {:?}, retrying", + attempt, + remote_id, + attempt_start.elapsed() + ); + } + } + } + Ok(Err(e)) => { + warn!( + "[perform_auth] attempt {} failed to connect auth channel to {} after {:?}: {}, retrying", + attempt, remote_id, attempt_start.elapsed(), e + ); + } + Err(e) => { + warn!( + "[perform_auth] attempt {} connection timed out for {} after {:?}: {}, retrying", + attempt, remote_id, attempt_start.elapsed(), e + ); + } + }; + trace!( + "[perform_auth] attempt {} for {} sleeping before retry with {:?} remaining", + attempt, + remote_id, + remaining_timeout(start_time, AUTH_TIMEOUT) + ); + tokio::time::sleep(Duration::from_secs(1)).await; + } + + warn!( + "[perform_auth] authentication workflow timed out for {} after {} attempts and {:?}", + remote_id, + attempt, + start_time.elapsed() + ); + + // Timeout no more retries, clean up in-flight state + release_in_flight( + self.auth_state.clone(), + remote_id, + AuthState::Unauthenticated, + ) + .await + .map_err(|err| { + AuthenticatorError::OpenFailed(format!( + "[before_connect] failed to release in-flight state for {}: {}", + remote_id, err + )) + })?; + Err(AuthenticatorError::OpenFailed(format!( + "Authentication timed out for {}", + remote_id + ))) + } +} + +fn remaining_timeout(start: Instant, timeout_duration: Duration) -> Duration { + timeout_duration.saturating_sub(Instant::now().saturating_duration_since(start)) +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..a64fdfc --- /dev/null +++ b/src/error.rs @@ -0,0 +1,48 @@ +use iroh::EndpointId; + +// Errors +#[derive(Debug)] +pub enum AuthenticatorError { + AddFailed, + AcceptFailed(String), + OpenFailed(String), + AcceptFailedAndBlock(String, EndpointId), + OpenFailedAndBlock(String, EndpointId), + EndpointNotSet, +} + +#[derive(Debug)] +pub enum InFlightError { + PromotionNotAllowed(String), +} + +impl std::fmt::Display for AuthenticatorError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AuthenticatorError::AddFailed => write!(f, "Failed to add authenticated ID"), + AuthenticatorError::AcceptFailed(msg) => write!(f, "Accept failed: {}", msg), + AuthenticatorError::OpenFailed(msg) => write!(f, "Open failed: {}", msg), + AuthenticatorError::EndpointNotSet => write!( + f, + "Authenticator endpoint not set: missing authenticator.set_endpoint(endpoint).await" + ), + AuthenticatorError::AcceptFailedAndBlock(msg, id) => { + write!(f, "Accept blocked endpoint ID: {}: {}", msg, id) + } + AuthenticatorError::OpenFailedAndBlock(msg, id) => { + write!(f, "Open blocked endpoint ID: {}: {}", msg, id) + } + } + } +} + +impl std::fmt::Display for InFlightError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + InFlightError::PromotionNotAllowed(msg) => write!(f, "Promotion not allowed: {}", msg), + } + } +} + +impl std::error::Error for InFlightError {} +impl std::error::Error for AuthenticatorError {} diff --git a/src/lib.rs b/src/lib.rs index af16a8c..bd2ec89 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,705 +1,18 @@ -use n0_watcher::Watchable; -use std::{ - collections::BTreeSet, - sync::{Arc, Mutex}, - time::Duration, -}; -use tracing::{debug, error, info, trace, warn}; +mod auth; +mod error; +mod protocol; +mod secret; -use hkdf::Hkdf; -use iroh::{ - endpoint::{AfterHandshakeOutcome, Connection, EndpointHooks, VarInt}, - protocol::ProtocolHandler, - Endpoint, EndpointId, PublicKey, Watcher, -}; -use n0_future::{task::spawn, time::timeout, StreamExt}; -use secrecy::{ExposeSecret, SecretSlice}; -use sha2::Sha512; -use spake2::{Ed25519Group, Identity, Password, Spake2}; -use subtle::ConstantTimeEq; +use std::time::Duration; -// Errors -#[derive(Debug)] -pub enum AuthenticatorError { - AddFailed, - AcceptFailed(String), - OpenFailed(String), - AcceptFailedAndBlock(String, EndpointId), - OpenFailedAndBlock(String, EndpointId), - EndpointNotSet, -} - -impl std::fmt::Display for AuthenticatorError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - AuthenticatorError::AddFailed => write!(f, "Failed to add authenticated ID"), - AuthenticatorError::AcceptFailed(msg) => write!(f, "Accept failed: {}", msg), - AuthenticatorError::OpenFailed(msg) => write!(f, "Open failed: {}", msg), - AuthenticatorError::EndpointNotSet => write!( - f, - "Authenticator endpoint not set: missing authenticator.start(endpoint)" - ), - AuthenticatorError::AcceptFailedAndBlock(msg, id) => { - write!(f, "Blocked endpoint ID: {}: {}", msg, id) - } - AuthenticatorError::OpenFailedAndBlock(msg, id) => { - write!(f, "Blocked endpoint ID: {}: {}", msg, id) - } - } - } -} - -impl std::error::Error for AuthenticatorError {} - -pub trait IntoSecret { - fn into_secret(self) -> SecretSlice; -} - -impl IntoSecret for SecretSlice { - fn into_secret(self) -> SecretSlice { - self - } -} - -impl IntoSecret for String { - fn into_secret(self) -> SecretSlice { - SecretSlice::new(self.into_bytes().into_boxed_slice()) - } -} - -impl IntoSecret for &str { - fn into_secret(self) -> SecretSlice { - SecretSlice::new(self.as_bytes().to_vec().into_boxed_slice()) - } -} - -impl IntoSecret for Vec { - fn into_secret(self) -> SecretSlice { - SecretSlice::new(self.into_boxed_slice()) - } -} - -impl IntoSecret for &[u8] { - fn into_secret(self) -> SecretSlice { - SecretSlice::new(self.to_vec().into_boxed_slice()) - } -} - -impl IntoSecret for &[u8; N] { - fn into_secret(self) -> SecretSlice { - SecretSlice::new(self.as_slice().to_vec().into_boxed_slice()) - } -} - -impl IntoSecret for Box<[u8]> { - fn into_secret(self) -> SecretSlice { - SecretSlice::new(self) - } -} - -#[derive(Debug, Clone, Default, PartialEq, Eq)] -struct WatchableCounter { - authenticated: usize, - blocked: usize, -} - -#[derive(Debug, Clone)] -pub struct Authenticator { - secret: SecretSlice, - authenticated: Arc>>, - watcher: Watchable, - endpoint: Arc>>, -} +pub use secret::IntoSecret; +pub use error::AuthenticatorError; +pub use auth::Authenticator; pub const ALPN: &[u8] = b"/iroh/auth/0.1"; -pub const AUTH_TIMEOUT: Duration = Duration::from_secs(10); - -impl Authenticator { - pub const ALPN: &'static [u8] = ALPN; - const ACCEPT_CONTEXT: &'static [u8] = b"iroh-auth-accept"; - const OPEN_CONTEXT: &'static [u8] = b"iroh-auth-open"; - - pub fn new(secret: S) -> Self { - Self { - secret: secret.into_secret(), - authenticated: Arc::new(Mutex::new(BTreeSet::new())), - watcher: Watchable::new(WatchableCounter::default()), - endpoint: Arc::new(Mutex::new(None)), - } - } - - pub fn set_endpoint(&self, endpoint: &Endpoint) { - if let Ok(mut guard) = self.endpoint.lock() { - if guard.is_none() { - *guard = Some(endpoint.clone()); - trace!("Authenticator endpoint set to {}", endpoint.id()); - } - } - } - - fn id(&self) -> Result { - self.endpoint - .lock() - .map_err(|_| AuthenticatorError::EndpointNotSet)? - .as_ref() - .map(|ep| ep.id()) - .ok_or(AuthenticatorError::EndpointNotSet) - } - - fn endpoint(&self) -> Result { - self.endpoint - .lock() - .map_err(|_| AuthenticatorError::EndpointNotSet)? - .as_ref() - .cloned() - .ok_or(AuthenticatorError::EndpointNotSet) - } - - fn is_authenticated(&self, id: &PublicKey) -> bool { - self.authenticated - .lock() - .map(|set| set.contains(id)) - .unwrap_or(false) - } - - fn add_authenticated(&self, id: PublicKey) -> Result<(), AuthenticatorError> { - self.authenticated - .lock() - .map_err(|_| AuthenticatorError::AddFailed)? - .insert(id); - let mut counter = self.watcher.get(); - counter.authenticated += 1; - self.watcher - .set(counter) - .map_err(|_| AuthenticatorError::AddFailed)?; - Ok(()) - } - - fn add_blocked(&self) -> Result<(), AuthenticatorError> { - let mut counter = self.watcher.get(); - counter.blocked += 1; - self.watcher - .set(counter) - .map_err(|_| AuthenticatorError::AddFailed)?; - Ok(()) - } - - #[doc(hidden)] - pub fn list_authenticated(&self) -> Vec { - self.authenticated - .lock() - .map(|set| set.iter().cloned().collect()) - .unwrap_or_default() - } - - async fn end_of_auth(&self, send: &mut iroh::endpoint::SendStream, open: bool) -> Result<(), AuthenticatorError> { - send.finish().map_err(|err| { - error!("[end_of_auth] failed to finish stream: {}", err); - if open { - AuthenticatorError::OpenFailed(format!("Failed to finish stream: {}", err)) - } else { - AuthenticatorError::AcceptFailed(format!("Failed to finish stream: {}", err)) - } - })?; - send.stopped().await.map_err(|err| { - error!("[end_of_auth] failed to wait for stream stopped: {}", err); - if open { - AuthenticatorError::OpenFailed(format!("Failed to wait for stream stopped: {}", err)) - } else { - AuthenticatorError::AcceptFailed(format!("Failed to wait for stream stopped: {}", err)) - } - })?; - Ok(()) - } - - /// Accept an incoming connection and perform SPAKE2 authentication. - /// On success, adds the remote ID to the authenticated set. - /// Returns Ok(()) on success, or an AuthenticatorError on failure. - async fn auth_accept(&self, conn: Connection) -> Result<(), AuthenticatorError> { - let remote_id = conn.remote_id(); - debug!("[auth_accept] accepting auth connection from {}", remote_id); - let (mut send, mut recv) = conn.accept_bi().await.map_err(|err| { - error!("[auth_accept] accept bidirectional stream failed: {}", err); - AuthenticatorError::AcceptFailed(format!("Accept bidirectional stream failed: {}", err)) - })?; - - let (spake, token_b) = Spake2::::start_b( - &Password::new(self.secret.expose_secret()), - &Identity::new(conn.remote_id().as_bytes()), - &Identity::new(self.id()?.as_bytes()), - ); - - let mut token_a = [0u8; 33]; - recv.read_exact(&mut token_a).await.map_err(|err| { - error!("[auth_accept] failed to read token_a: {}", err); - AuthenticatorError::AcceptFailed(format!("Failed to read token_a: {}", err)) - })?; - - send.write_all(&token_b).await.map_err(|err| { - error!("[auth_accept] failed to write token_b: {}", err); - AuthenticatorError::AcceptFailed(format!("Failed to write token_b: {}", err)) - })?; - - let shared_secret = spake.finish(&token_a).map_err(|err| { - error!("[auth_accept] SPAKE2 invalid: {}", err); - AuthenticatorError::AcceptFailedAndBlock(format!("SPAKE2 invalid: {}", err), remote_id) - })?; - - let hk = Hkdf::::new(None, shared_secret.as_slice()); - let mut accept_key = [0u8; 64]; - let mut open_key = [0u8; 64]; - hk.expand(Self::ACCEPT_CONTEXT, &mut accept_key) - .map_err(|err| { - error!("[auth_accept] failed to expand accept_key: {}", err); - AuthenticatorError::AcceptFailed(format!("Failed to expand accept_key: {}", err)) - })?; - hk.expand(Self::OPEN_CONTEXT, &mut open_key) - .map_err(|err| { - error!("[auth_accept] failed to expand open_key: {}", err); - AuthenticatorError::AcceptFailed(format!("Failed to expand open_key: {}", err)) - })?; - - send.write_all(&accept_key).await.map_err(|err| { - error!("[auth_accept] failed to write accept_key: {}", err); - AuthenticatorError::AcceptFailed(format!("Failed to write accept_key: {}", err)) - })?; - let mut remote_open_key = [0u8; 64]; - recv.read_exact(&mut remote_open_key).await.map_err(|err| { - error!("[auth_accept] failed to read remote_open_key: {}", err); - AuthenticatorError::AcceptFailed(format!("Failed to read remote_open_key: {}", err)) - })?; - - self.end_of_auth(&mut send, false).await?; - - if !bool::from(remote_open_key.ct_eq(&open_key)) { - error!("[auth_accept] remote open_key mismatch"); - return Err(AuthenticatorError::AcceptFailedAndBlock( - "Remote open_key mismatch".to_string(), - remote_id, - )); - } - - self.add_authenticated(conn.remote_id())?; - info!("[auth_accept] authenticated connection from {}", remote_id); - - Ok(()) - } - - /// Open an outgoing connection and perform SPAKE2 authentication. - /// On success, adds the remote ID to the authenticated set. - /// Returns Ok(()) on success, or an AuthenticatorError on failure. - async fn auth_open(&self, conn: Connection) -> Result<(), AuthenticatorError> { - let remote_id = conn.remote_id(); - debug!("[auth_open] opening auth connection to {}", remote_id); - let (mut send, mut recv) = conn.open_bi().await.map_err(|err| { - error!("[auth_open] open bidirectional stream failed: {}", err); - AuthenticatorError::OpenFailed(format!("Open bidirectional stream failed: {}", err)) - })?; - - let (spake, token_a) = Spake2::::start_a( - &Password::new(self.secret.expose_secret()), - &Identity::new(self.id()?.as_bytes()), - &Identity::new(conn.remote_id().as_bytes()), - ); - - send.write_all(&token_a).await.map_err(|err| { - error!("[auth_open] failed to write token_a: {}", err); - AuthenticatorError::OpenFailed(format!("Failed to write token_a: {}", err)) - })?; - - let mut token_b = [0u8; 33]; - recv.read_exact(&mut token_b).await.map_err(|err| { - error!("[auth_open] failed to read token_b: {}", err); - AuthenticatorError::OpenFailed(format!("Failed to read token_b: {}", err)) - })?; - - let shared_secret = spake.finish(&token_b).map_err(|err| { - error!("[auth_open] SPAKE2 invalid: {}", err); - AuthenticatorError::OpenFailedAndBlock(format!("SPAKE2 invalid: {}", err), remote_id) - })?; - - let hk = Hkdf::::new(None, shared_secret.as_slice()); - let mut accept_key = [0u8; 64]; - let mut open_key = [0u8; 64]; - hk.expand(Self::ACCEPT_CONTEXT, &mut accept_key) - .map_err(|err| { - error!("[auth_open] failed to expand accept_key: {}", err); - AuthenticatorError::OpenFailed(format!("Failed to expand accept_key: {}", err)) - })?; - hk.expand(Self::OPEN_CONTEXT, &mut open_key) - .map_err(|err| { - error!("[auth_open] failed to expand open_key: {}", err); - AuthenticatorError::OpenFailed(format!("Failed to expand open_key: {}", err)) - })?; - - let mut remote_accept_key = [0u8; 64]; - recv.read_exact(&mut remote_accept_key) - .await - .map_err(|err| { - error!("[auth_open] failed to read remote_accept_key: {}", err); - AuthenticatorError::OpenFailed(format!("Failed to read remote_accept_key: {}", err)) - })?; - - if !bool::from(remote_accept_key.ct_eq(&accept_key)) { - error!("[auth_open] remote accept_key mismatch"); - - // Writing a random dummy open_key back to finishing the stream but not give away - // that the accept_key was correct to avoid leaking information to an attacker about valid accept_keys - // (probably not needed but better safe than sorry ^^) - send.write_all(&rand::random::<[u8; 64]>()).await.ok(); - self.end_of_auth(&mut send, true).await?; - - return Err(AuthenticatorError::OpenFailedAndBlock( - "Remote accept_key mismatch".to_string(), - remote_id, - )); - } - - send.write_all(&open_key).await.map_err(|err| { - error!("[auth_open] failed to write open_key: {}", err); - AuthenticatorError::OpenFailed(format!("Failed to write open_key: {}", err)) - })?; - self.end_of_auth(&mut send, true).await?; - - self.add_authenticated(conn.remote_id())?; - info!("[auth_open] authenticated connection to {}", remote_id); - - Ok(()) - } -} - -impl ProtocolHandler for Authenticator { - async fn accept( - &self, - connection: iroh::endpoint::Connection, - ) -> Result<(), iroh::protocol::AcceptError> { - match timeout(AUTH_TIMEOUT, self.auth_accept(connection)).await { - Ok(Ok(())) => Ok(()), - Ok(Err(err)) => match &err { - AuthenticatorError::AcceptFailedAndBlock(msg, public_key) => { - warn!("[accept] authentication failed and blocking {}: {}", public_key, msg); - self.add_blocked().ok(); - Err(iroh::protocol::AcceptError::from_err(err)) - } - _ => { - warn!("[accept] authentication failed: {}", err); - Err(iroh::protocol::AcceptError::from_err(err)) - } - }, - Err(_) => { - warn!("[accept] authentication failed: timed out"); - Err(iroh::protocol::AcceptError::from_err( - AuthenticatorError::AcceptFailed("Authentication timed out".into()), - )) - } - } - } -} - -impl EndpointHooks for Authenticator { - async fn after_handshake<'a>( - &'a self, - conn_info: &'a iroh::endpoint::ConnectionInfo, - ) -> iroh::endpoint::AfterHandshakeOutcome { - if self.is_authenticated(&conn_info.remote_id()) { - debug!("[after_handshake] already authenticated: {}", conn_info.remote_id()); - return AfterHandshakeOutcome::accept(); - } - - if conn_info.alpn() == Self::ALPN { - debug!( - "[after_handshake] skipping auth for connection with alpn {}", - String::from_utf8_lossy(conn_info.alpn()) - ); - return AfterHandshakeOutcome::accept(); - } - - let remote_id = conn_info.remote_id(); - let counter = self.watcher.get(); - - let wait_for_auth = async { - let mut stream = self.watcher.watch().stream(); - while let Some(next_counter) = stream.next().await { - if next_counter != counter && self.is_authenticated(&remote_id) { - return Ok(()) as Result<(), AuthenticatorError>; - } - } - Err(AuthenticatorError::AcceptFailed( - "Watcher stream ended unexpectedly".to_string(), - )) - }; - - match timeout(AUTH_TIMEOUT, wait_for_auth).await { - Ok(_) => AfterHandshakeOutcome::accept(), - Err(_) => { - warn!("[after_handshake] authentication timed out for {}", remote_id); - AfterHandshakeOutcome::Reject { - error_code: VarInt::from_u32(401), - reason: b"Authentication timed out".to_vec(), - } - } - } - } - - async fn before_connect<'a>( - &'a self, - remote_addr: &'a iroh::EndpointAddr, - alpn: &'a [u8], - ) -> iroh::endpoint::BeforeConnectOutcome { - if self.is_authenticated(&remote_addr.id) { - debug!("[before_connect] already authenticated: {}", remote_addr.id); - return iroh::endpoint::BeforeConnectOutcome::Accept; - } - - if alpn == Self::ALPN { - debug!( - "[before_connect] skipping auth for connection to {} with alpn {:?}", - remote_addr.id, alpn - ); - return iroh::endpoint::BeforeConnectOutcome::Accept; - } - - debug!( - "[before_connect] initiating auth for client connection with alpn {} to {}", - String::from_utf8_lossy(alpn), - remote_addr.id - ); - let endpoint = match self.endpoint() { - Ok(ep) => ep, - Err(_) => { - warn!("[before_connect] authenticator endpoint not set"); - return iroh::endpoint::BeforeConnectOutcome::Reject; - } - }; - spawn({ - let auth = self.clone(); - let remote_id = remote_addr.id; - - async move { - debug!("[before_connect] background: connecting to {} for auth", remote_id); - let start = std::time::Instant::now(); - while start.elapsed() < AUTH_TIMEOUT { - match endpoint.connect(remote_id, Self::ALPN).await { - Ok(conn) => { - debug!("[before_connect] background: connected to {}, performing auth", remote_id); - match timeout(AUTH_TIMEOUT, auth.auth_open(conn)).await { - Ok(Ok(())) => { - debug!( - "[before_connect] background: authentication successful for {}", - remote_id - ); - return; - } - Ok(Err(err)) => match &err { - AuthenticatorError::OpenFailedAndBlock(msg, public_key) => { - warn!( - "[before_connect] authentication failed and blocking {}: {}", - public_key, msg - ); - auth.add_blocked().ok(); - return; - } - _ => { - warn!("[before_connect] authentication failed for {}: {}", remote_id, err); - } - }, - Err(_) => { - warn!( - "[before_connect] background: authentication timed out for {}, retrying...", - remote_id - ); - } - } - } - Err(e) => { - warn!( - "[before_connect] background: failed to open connection for authentication to {}: {}, retrying...", - remote_id, e - ); - } - }; - - tokio::time::sleep(Duration::from_millis(500)).await; - } - warn!("[before_connect] background: authentication timed out for {}", remote_id); - } - }); - iroh::endpoint::BeforeConnectOutcome::Accept - } -} +const AUTH_TIMEOUT: Duration = Duration::from_secs(30); +const TRANSMISSION_TIMEOUT: Duration = Duration::from_millis(5000); +const LRU_CACHE_SIZE: usize = 10_000; #[cfg(test)] -mod tests { - use iroh::Watcher; - - use super::*; - #[test] - fn test_token_different() { - let password = b"testpassword"; - let id_a = b"identityA"; - let id_b = b"identityB"; - - let (spake_a, token_a) = Spake2::::start_a( - &Password::new(password), - &Identity::new(id_a), - &Identity::new(id_b), - ); - - let (spake_b, token_b) = Spake2::::start_b( - &Password::new(password), - &Identity::new(id_a), - &Identity::new(id_b), - ); - - assert_ne!(token_a, token_b); - - let key_a = spake_a.finish(&token_b).unwrap(); - let key_b = spake_b.finish(&token_a).unwrap(); - - assert_eq!(key_a, key_b); - } - - #[derive(Debug, Clone)] - struct DummyProtocol; - impl ProtocolHandler for DummyProtocol { - async fn accept(&self, _conn: Connection) -> Result<(), iroh::protocol::AcceptError> { - Ok(()) - } - } - - #[tokio::test(flavor = "multi_thread")] - async fn test_auth_success() { - let secret = b"supersecrettoken1234567890123456"; - assert!(run_auth_test(secret, secret).await.unwrap()); - } - - #[tokio::test(flavor = "multi_thread")] - async fn test_auth_failure() { - let secret_a = b"supersecrettoken1234567890123456"; - let secret_b = b"differentsecrettoken123456789012"; - assert!(!run_auth_test(secret_a, secret_b).await.unwrap()); - } - - async fn run_auth_test( - secret_a: &'static [u8], - secret_b: &'static [u8], - ) -> Result { - let auth_a = Authenticator::new(secret_a); - let endpoint_a = iroh::Endpoint::builder(iroh::endpoint::presets::N0) - .hooks(auth_a.clone()) - .bind() - .await - .map_err(|e| e.to_string())?; - auth_a.set_endpoint(&endpoint_a); - let dummy_a = DummyProtocol; - - let auth_b = Authenticator::new(secret_b); - let endpoint_b = iroh::Endpoint::builder(iroh::endpoint::presets::N0) - .hooks(auth_b.clone()) - .bind() - .await - .map_err(|e| e.to_string())?; - auth_b.set_endpoint(&endpoint_b); - let dummy_b = DummyProtocol; - - let router_a = iroh::protocol::Router::builder(endpoint_a.clone()) - .accept(Authenticator::ALPN, auth_a.clone()) - .accept(b"/dummy/1", dummy_a) - .spawn(); - - let router_b = iroh::protocol::Router::builder(endpoint_b.clone()) - .accept(Authenticator::ALPN, auth_b.clone()) - .accept(b"/dummy/1", dummy_b) - .spawn(); - - spawn({ - let endpoint_a = endpoint_a.clone(); - let endpoint_b = endpoint_b.clone(); - async move { - endpoint_a - .connect(endpoint_b.addr(), b"/dummy/1") - .await - .ok(); - } - }); - - let wait_loop = async { - use n0_future::StreamExt; - - let wait_a = async { - let mut stream = auth_a.watcher.watch().stream(); - while let Some(counter) = stream.next().await { - debug!( - "auth_a watcher: authenticated={}, blocked={}", - counter.authenticated, counter.blocked - ); - if counter.authenticated >= 1 || counter.blocked >= 1 { - break; - } - } - }; - let wait_b = async { - let mut stream = auth_b.watcher.watch().stream(); - while let Some(counter) = stream.next().await { - debug!( - "auth_b watcher: authenticated={}, blocked={}", - counter.authenticated, counter.blocked - ); - if counter.authenticated >= 1 || counter.blocked >= 1 { - break; - } - } - }; - tokio::join!(wait_a, wait_b); - }; - - if timeout(AUTH_TIMEOUT * 2, wait_loop).await.is_err() { - router_a.shutdown().await.ok(); - router_b.shutdown().await.ok(); - return Err("Authentication did not complete in time".to_string()); - } - - router_a.shutdown().await.ok(); - router_b.shutdown().await.ok(); - - Ok(auth_a.is_authenticated(&endpoint_b.id()) && auth_b.is_authenticated(&endpoint_a.id())) - } - - #[test] - fn test_into_secret_impls() { - use secrecy::ExposeSecret; - - let expected_bytes = b"my-secret-key"; - - // &str - let secret = "my-secret-key".into_secret(); - assert_eq!(secret.expose_secret(), expected_bytes); - - // String - let secret = String::from("my-secret-key").into_secret(); - assert_eq!(secret.expose_secret(), expected_bytes); - // Vec - let secret = b"my-secret-key".to_vec().into_secret(); - assert_eq!(secret.expose_secret(), expected_bytes); - - // &[u8] - let bytes: &[u8] = b"my-secret-key"; - let secret = bytes.into_secret(); - assert_eq!(secret.expose_secret(), expected_bytes); - - // &[u8; N] - let bytes: &[u8; 13] = b"my-secret-key"; - let secret = bytes.into_secret(); - assert_eq!(secret.expose_secret(), expected_bytes); - - // Box<[u8]> - let bytes: Box<[u8]> = Box::new(*b"my-secret-key"); - let secret = bytes.into_secret(); - assert_eq!(secret.expose_secret(), expected_bytes); - - // SecretSlice - let ps = SecretSlice::new(Box::new(*b"my-secret-key")); - let secret = ps.into_secret(); - assert_eq!(secret.expose_secret(), expected_bytes); - } -} +mod tests; \ No newline at end of file diff --git a/src/protocol.rs b/src/protocol.rs new file mode 100644 index 0000000..54c77c7 --- /dev/null +++ b/src/protocol.rs @@ -0,0 +1,582 @@ +use std::sync::Arc; + +use iroh::{ + endpoint::{AfterHandshakeOutcome, EndpointHooks, VarInt}, + protocol::ProtocolHandler, + EndpointId, PublicKey, +}; +use lru::LruCache; +use n0_future::StreamExt; +use n0_watcher::Watcher; +use tokio::{sync::Mutex, time::timeout}; +use tracing::{debug, error, info, trace, warn}; + +use crate::{ + auth::{AuthState, RegisterResponse, WatchableRemote}, + error::InFlightError, + Authenticator, AuthenticatorError, ALPN, AUTH_TIMEOUT, +}; + +impl ProtocolHandler for Authenticator { + async fn accept( + &self, + connection: iroh::endpoint::Connection, + ) -> Result<(), iroh::protocol::AcceptError> { + let remote_id = connection.remote_id(); + trace!("[accept] starting auth protocol accept for {}", remote_id); + let res = match timeout(AUTH_TIMEOUT, self.auth_accept(connection)).await { + Ok(Ok(())) => { + trace!( + "[accept] auth_accept succeeded for {}, releasing as Authenticated", + remote_id + ); + release_in_flight(self.auth_state.clone(), remote_id, AuthState::Authenticated) + .await + .ok(); + Ok(()) + } + Ok(Err(err)) => match &err { + AuthenticatorError::AcceptFailedAndBlock(msg, public_key) => { + warn!( + "[accept] authentication failed and blocking {}: {}", + public_key, msg + ); + trace!( + "[accept] releasing {} as Blocked after accept failure", + remote_id + ); + release_in_flight(self.auth_state.clone(), remote_id, AuthState::Blocked) + .await + .ok(); + Err(iroh::protocol::AcceptError::from_err(err)) + } + _ => { + warn!("[accept] authentication failed: {}", err); + trace!( + "[accept] releasing {} as Unauthenticated after accept failure", + remote_id + ); + release_in_flight( + self.auth_state.clone(), + remote_id, + AuthState::Unauthenticated, + ) + .await + .ok(); + Err(iroh::protocol::AcceptError::from_err(err)) + } + }, + Err(_) => { + warn!("[accept] authentication failed: timed out"); + trace!( + "[accept] releasing {} as Unauthenticated after accept timeout", + remote_id + ); + release_in_flight( + self.auth_state.clone(), + remote_id, + AuthState::Unauthenticated, + ) + .await + .ok(); + Err(iroh::protocol::AcceptError::from_err( + AuthenticatorError::AcceptFailed("Authentication timed out".into()), + )) + } + }; + + res + } +} + +impl EndpointHooks for Authenticator { + async fn after_handshake<'a>( + &'a self, + conn: &'a iroh::endpoint::Connection, + ) -> iroh::endpoint::AfterHandshakeOutcome { + let endpoint_id = conn.remote_id(); + trace!( + "[after_handshake] entered for {} with alpn {}", + endpoint_id, + String::from_utf8_lossy(conn.alpn()) + ); + if self.is_authenticated(&endpoint_id).await { + debug!("[after_handshake] already authenticated: {}", endpoint_id); + return AfterHandshakeOutcome::accept(); + } + + if conn.alpn() == ALPN { + debug!( + "[after_handshake] accepting auth connection: {}", + String::from_utf8_lossy(conn.alpn()) + ); + return AfterHandshakeOutcome::accept(); + } + + // wait for authentication to complete + let in_flight_watcher = if let Some(watchable) = + get_auth_state(self.auth_state.clone(), endpoint_id).await + { + trace!( + "[after_handshake] found auth state for {}: {}", + endpoint_id, + watchable.state() + ); + match watchable.state() { + AuthState::Unauthenticated => { + debug!("[after_handshake] no in-flight auth for {}, we are asymetric (the other node successfully authed but we didn't), initiating auth ourself",endpoint_id); + match register_in_flight(self.auth_state.clone(), endpoint_id).await { + Ok(RegisterResponse::AlreadyInFlight) => { + debug!( + "[after_handshake] already in-flight auth for {}, waiting for it to complete", + endpoint_id + ); + watchable.watcher() + } + Ok(RegisterResponse::InFlightRegistered) => { + debug!( + "[after_handshake] registered in-flight auth for {}, performing auth", + endpoint_id + ); + let endpoint = match self.endpoint().await { + Ok(ep) => ep, + Err(_) => { + error!("[after_handshake] authenticator endpoint not set"); + return AfterHandshakeOutcome::Reject { + error_code: VarInt::from_u32(500), + reason: b"Internal server error".to_vec(), + }; + } + }; + if let Err(err) = self.perform_auth(endpoint_id, endpoint).await { + error!( + "[after_handshake] authentication failed for {}, rejecting connection with error: {}", + endpoint_id, err + ); + return AfterHandshakeOutcome::Reject { + error_code: VarInt::from_u32(401), + reason: b"Authentication failed".to_vec(), + }; + } else { + info!( + "[after_handshake] authentication succeeded for {}", + endpoint_id + ); + debug!( + "[after_handshake] authentication succeeded for {}, waiting for state update", + endpoint_id + ); + return iroh::endpoint::AfterHandshakeOutcome::accept(); + } + } + _ => { + debug!( + "[after_handshake] failed to register in-flight auth for {}, rejecting connection", + endpoint_id + ); + return AfterHandshakeOutcome::Reject { + error_code: VarInt::from_u32(401), + reason: b"Authentication failed".to_vec(), + }; + } + } + } + AuthState::InFlight => { + debug!( + "[after_handshake] waiting for in-flight auth for {}", + endpoint_id + ); + watchable.watcher() + } + AuthState::Authenticated => { + debug!( + "[after_handshake] already authenticated: {}", + conn.remote_id() + ); + return AfterHandshakeOutcome::accept(); + } + AuthState::Blocked => { + debug!( + "[after_handshake] endpoint {} is blocked, rejecting connection", + endpoint_id + ); + return AfterHandshakeOutcome::Reject { + error_code: VarInt::from_u32(403), + reason: b"Endpoint is blocked".to_vec(), + }; + } + } + } else { + debug!( + "[after_handshake] no in-flight auth for {}, rejecting connection", + endpoint_id + ); + return AfterHandshakeOutcome::Reject { + error_code: VarInt::from_u32(401), + reason: b"No authentication in progress".to_vec(), + }; + }; + + let wait_for_auth = async { + trace!( + "[after_handshake] subscribing to auth state updates for {}", + endpoint_id + ); + let mut stream = in_flight_watcher.watch().stream(); + while let Some(in_flight) = stream.next().await { + trace!( + "[after_handshake] observed auth state update for {} -> {}", + endpoint_id, + in_flight + ); + if matches!( + in_flight, + AuthState::Unauthenticated | AuthState::Authenticated | AuthState::Blocked + ) { + trace!( + "[after_handshake] terminal auth state {} reached for {}", + in_flight, + endpoint_id + ); + return; + } + } + warn!( + "[after_handshake] auth state watch stream ended unexpectedly for {}", + endpoint_id + ); + }; + + match timeout(AUTH_TIMEOUT, wait_for_auth).await { + Ok(_) => { + if self.is_authenticated(&endpoint_id).await { + trace!( + "[after_handshake] auth completed successfully for {}", + endpoint_id + ); + AfterHandshakeOutcome::accept() + } else { + warn!( + "[after_handshake] auth wait finished for {} but endpoint is not authenticated", + endpoint_id + ); + AfterHandshakeOutcome::Reject { + error_code: VarInt::from_u32(401), + reason: b"Authentication failed".to_vec(), + } + } + } + Err(_) => { + warn!( + "[after_handshake] authentication timed out for {}", + endpoint_id + ); + AfterHandshakeOutcome::Reject { + error_code: VarInt::from_u32(401), + reason: b"Authentication timed out".to_vec(), + } + } + } + } + + async fn before_connect<'a>( + &'a self, + remote_addr: &'a iroh::EndpointAddr, + alpn: &'a [u8], + ) -> iroh::endpoint::BeforeConnectOutcome { + let remote_id = remote_addr.id; + trace!( + "[before_connect] entered for {} with alpn {}", + remote_id, + String::from_utf8_lossy(alpn) + ); + if self.is_authenticated(&remote_id).await { + debug!("[before_connect] already authenticated: {}", remote_id); + return iroh::endpoint::BeforeConnectOutcome::Accept; + } + + if alpn == ALPN { + debug!( + "[before_connect] initiating auth for client connection with alpn {} to {}", + String::from_utf8_lossy(alpn), + remote_id + ); + return iroh::endpoint::BeforeConnectOutcome::Accept; + } + + match register_in_flight(self.auth_state.clone(), remote_id).await { + Ok(RegisterResponse::InFlightRegistered) | Ok(RegisterResponse::AlreadyInFlight) => { + debug!( + "[before_connect] registered in-flight auth for {}, performing auth", + remote_id + ); + + let endpoint = match self.endpoint().await { + Ok(ep) => ep, + Err(_) => { + error!("[before_connect] authenticator endpoint not set"); + return iroh::endpoint::BeforeConnectOutcome::Reject; + } + }; + if let Err(err) = self.perform_auth(remote_id, endpoint).await { + error!( + "[before_connect] authentication failed for {}, rejecting connection with error: {}", + remote_id, err + ); + iroh::endpoint::BeforeConnectOutcome::Reject + } else { + info!( + "[before_connect] authentication succeeded for {}", + remote_id + ); + iroh::endpoint::BeforeConnectOutcome::Accept + } + } + Ok(RegisterResponse::AlreadyAuthenticated) => { + trace!( + "[before_connect] auth already in progress or complete for {}, allowing connect to proceed", + remote_id + ); + if self.is_authenticated(&remote_id).await { + debug!( + "[before_connect] already authenticated (in flight), accepting connection to {}", + remote_id + ); + } + iroh::endpoint::BeforeConnectOutcome::Accept + } + Ok(RegisterResponse::AlreadyBlocked) => { + debug!( + "[before_connect] endpoint {} is blocked, rejecting connection", + remote_id + ); + iroh::endpoint::BeforeConnectOutcome::Reject + } + Err(err) => { + warn!( + "[before_connect] failed to register in-flight auth for {}: {}", + remote_id, err + ); + iroh::endpoint::BeforeConnectOutcome::Reject + } + } + } +} + +pub(crate) async fn register_in_flight( + in_flight: Arc>>, + endpoint_id: PublicKey, +) -> Result { + trace!( + "[register_in_flight] locking auth cache for {}", + endpoint_id + ); + let mut guard = in_flight.lock().await; + trace!( + "[register_in_flight] auth cache locked for {}, current size {}", + endpoint_id, + guard.len() + ); + if let Some(entry) = guard.get(&endpoint_id) { + let current_state = entry.state(); + trace!( + "[register_in_flight] existing state for {} is {}", + endpoint_id, + current_state + ); + return match current_state { + AuthState::Unauthenticated => { + entry.set_state(AuthState::InFlight); + trace!( + "[register_in_flight] endpoint {} promoted from Unauthenticated to InFlight", + endpoint_id + ); + Ok(RegisterResponse::InFlightRegistered) + } + AuthState::Authenticated => { + trace!( + "[register_in_flight] endpoint {} already authenticated", + endpoint_id + ); + Ok(RegisterResponse::AlreadyAuthenticated) + } + AuthState::InFlight => { + trace!( + "[register_in_flight] endpoint {} already has auth in flight", + endpoint_id + ); + Ok(RegisterResponse::AlreadyInFlight) + } + AuthState::Blocked => { + trace!("[register_in_flight] endpoint {} is blocked", endpoint_id); + Ok(RegisterResponse::AlreadyBlocked) + } + }; + } + + let watchable = WatchableRemote::new(endpoint_id); + watchable.set_state(AuthState::InFlight); + trace!( + "[register_in_flight] inserting new auth state entry for {} as InFlight", + endpoint_id + ); + + if let Some(evicted) = guard.put(endpoint_id, watchable) { + debug!( + "evicting endpoint {} from auth cache due to capacity limit", + evicted.id() + ); + } + + Ok(RegisterResponse::InFlightRegistered) +} + +pub(crate) async fn release_in_flight( + in_flight: Arc>>, + endpoint_id: PublicKey, + target_state: AuthState, +) -> Result<(), InFlightError> { + trace!( + "[release_in_flight] requested state release for {} -> {}", + endpoint_id, + target_state + ); + if target_state == AuthState::InFlight { + return Err(InFlightError::PromotionNotAllowed( + "cannot release by promoting to InFlight".to_string(), + )); + } + trace!("[release_in_flight] locking auth cache for {}", endpoint_id); + let mut guard = in_flight.lock().await; + trace!( + "[release_in_flight] auth cache locked for {}, current size {}", + endpoint_id, + guard.len() + ); + + // occupied + if let Some(entry) = guard.get(&endpoint_id) { + let current_state = entry.state(); + let target_state_for_logs = target_state.clone(); + trace!( + "[release_in_flight] current state for {} is {}, target {}", + endpoint_id, + current_state, + target_state_for_logs + ); + return match current_state { + AuthState::InFlight => { + entry.set_state(target_state); + trace!( + "[release_in_flight] endpoint {} released from InFlight to {}", + endpoint_id, + target_state_for_logs + ); + Ok(()) + } + AuthState::Authenticated => { + if target_state == AuthState::Blocked { + entry.set_state(AuthState::Blocked); + debug!( + "endpoint {} was authenticated but is now blocked, updating state to Blocked", + endpoint_id + ); + Ok(()) + } else { + trace!("endpoint {} is already authenticated, no-op", endpoint_id); + Ok(()) + } + } + AuthState::Unauthenticated => match target_state { + AuthState::Blocked => { + entry.set_state(AuthState::Blocked); + debug!( + "endpoint {} was unauthenticated but is now blocked, updating state to Blocked", + endpoint_id + ); + Ok(()) + } + AuthState::Authenticated => { + trace!("promoting endpoint {} from Unauthenticated to Authenticated (this is required because we can have asymetric failures that lead to this state transition)", endpoint_id); + + entry.set_state(AuthState::Authenticated); + Ok(()) + } + AuthState::Unauthenticated => { + trace!("endpoint {} is already unauthenticated, no-op", endpoint_id); + Ok(()) + } + AuthState::InFlight => { + trace!( + "cannot promote endpoint {} from Unauthenticated back to InFlight", + endpoint_id + ); + Err(InFlightError::PromotionNotAllowed( + "cannot promote to InFlight".to_string(), + )) + } + }, + current_state => { + if current_state == target_state { + debug!( + "endpoint {} is already in target state {}, no state change needed", + endpoint_id, target_state + ); + Ok(()) + } else { + warn!( + "[release_in_flight] refusing state overwrite for {} from {} to {}", + endpoint_id, current_state, target_state + ); + Err(InFlightError::PromotionNotAllowed(format!( + "only promote to {} from {} not from {}", + target_state, + AuthState::InFlight, + entry.state() + ))) + } + } + }; + } + + // vacant + let watchable = WatchableRemote::new(endpoint_id); + let target_state_for_logs = target_state.clone(); + watchable.set_state(target_state); + trace!( + "[release_in_flight] no auth state entry existed for {}, inserting {}", + endpoint_id, + target_state_for_logs + ); + + if let Some(evicted) = guard.put(endpoint_id, watchable) { + debug!( + "evicting endpoint {} from auth cache due to capacity limit", + evicted.id() + ); + } + + Ok(()) +} + +pub(crate) async fn get_auth_state( + auth_state: Arc>>, + endpoint_id: PublicKey, +) -> Option { + trace!("[get_auth_state] locking auth cache for {}", endpoint_id); + let mut guard = auth_state.lock().await; + let result = guard.get(&endpoint_id).cloned(); + match result.as_ref() { + Some(watchable) => { + trace!( + "[get_auth_state] found auth state for {}: {}", + endpoint_id, + watchable.state() + ); + } + None => { + trace!("[get_auth_state] no auth state found for {}", endpoint_id); + } + } + result +} diff --git a/src/secret.rs b/src/secret.rs new file mode 100644 index 0000000..c19a659 --- /dev/null +++ b/src/secret.rs @@ -0,0 +1,49 @@ +use secrecy::SecretSlice; + + +pub trait IntoSecret { + fn into_secret(self) -> SecretSlice; +} + +impl IntoSecret for SecretSlice { + fn into_secret(self) -> SecretSlice { + self + } +} + +impl IntoSecret for String { + fn into_secret(self) -> SecretSlice { + SecretSlice::new(self.into_bytes().into_boxed_slice()) + } +} + +impl IntoSecret for &str { + fn into_secret(self) -> SecretSlice { + SecretSlice::new(self.as_bytes().to_vec().into_boxed_slice()) + } +} + +impl IntoSecret for Vec { + fn into_secret(self) -> SecretSlice { + SecretSlice::new(self.into_boxed_slice()) + } +} + +impl IntoSecret for &[u8] { + fn into_secret(self) -> SecretSlice { + SecretSlice::new(self.to_vec().into_boxed_slice()) + } +} + +impl IntoSecret for &[u8; N] { + fn into_secret(self) -> SecretSlice { + SecretSlice::new(self.as_slice().to_vec().into_boxed_slice()) + } +} + +impl IntoSecret for Box<[u8]> { + fn into_secret(self) -> SecretSlice { + SecretSlice::new(self) + } +} + diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..54e4d7e --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,448 @@ +#[cfg(test)] +use std::{ + sync::{atomic::AtomicUsize, Arc}, + time::Duration, +}; + +use crate::{ + auth::AuthState, + protocol::{register_in_flight, release_in_flight}, + Authenticator, IntoSecret, AUTH_TIMEOUT, +}; +use iroh::{endpoint::Connection, protocol::ProtocolHandler}; +use secrecy::SecretSlice; +use spake2::{Ed25519Group, Identity, Password, Spake2}; +use tokio::time::{self, timeout}; + +#[test] +fn test_token_different() { + let password = b"testpassword"; + let id_a = b"identityA"; + let id_b = b"identityB"; + + let (spake_a, token_a) = Spake2::::start_a( + &Password::new(password), + &Identity::new(id_a), + &Identity::new(id_b), + ); + + let (spake_b, token_b) = Spake2::::start_b( + &Password::new(password), + &Identity::new(id_a), + &Identity::new(id_b), + ); + + assert_ne!(token_a, token_b); + + let key_a = spake_a.finish(&token_b).expect("SPAKE2 A failed to finish"); + let key_b = spake_b.finish(&token_a).expect("SPAKE2 B failed to finish"); + + assert_eq!(key_a, key_b); +} + +#[derive(Debug, Clone)] +#[allow(dead_code)] +struct DummyProtocol; +impl ProtocolHandler for DummyProtocol { + async fn accept(&self, _conn: Connection) -> Result<(), iroh::protocol::AcceptError> { + Ok(()) + } +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_auth_success() { + let secret = b"supersecrettoken1234567890123456"; + assert!(run_auth_test(secret, secret) + .await + .expect("Auth test failed")); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_auth_parallel() { + let secret = b"supersecrettoken1234567890123456"; + let worked = Arc::new(AtomicUsize::new(0)); + let count = 10; + for _ in 0..count { + let worked = worked.clone(); + tokio::spawn(async move { + assert!(run_auth_parallel_test(secret, secret, count) + .await + .expect("auth_parallel test failed")); + worked.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + }); + } + + while worked.load(std::sync::atomic::Ordering::SeqCst) < count { + tokio::time::sleep(Duration::from_millis(100)).await; + } +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_auth_failure() { + let secret_a = b"supersecrettoken1234567890123456"; + let secret_b = b"differentsecrettoken123456789012"; + assert!(!run_auth_test(secret_a, secret_b) + .await + .expect("Auth failure test failed")); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_auth_asymmetric_retry() { + let secret = b"supersecrettoken1234567890123456"; + assert!(run_auth_asymmetric_retry_test(secret) + .await + .expect("Auth asymmetric retry test failed")); +} + +async fn run_auth_test( + secret_a: &'static [u8], + secret_b: &'static [u8], +) -> Result { + let auth_a = Authenticator::new(secret_a); + let endpoint_a = iroh::Endpoint::builder(iroh::endpoint::presets::N0) + .alpns(vec![b"/dummy/1".to_vec()]) + .hooks(auth_a.clone()) + .bind() + .await + .map_err(|e| e.to_string())?; + auth_a.set_endpoint(&endpoint_a).await; + + let auth_b = Authenticator::new(secret_b); + let endpoint_b = iroh::Endpoint::builder(iroh::endpoint::presets::N0) + .alpns(vec![b"/dummy/1".to_vec()]) + .hooks(auth_b.clone()) + .bind() + .await + .map_err(|e| e.to_string())?; + auth_b.set_endpoint(&endpoint_b).await; + + let router_a = iroh::protocol::Router::builder(endpoint_a.clone()) + .accept(Authenticator::ALPN, auth_a.clone()) + .spawn(); + + let router_b = iroh::protocol::Router::builder(endpoint_b.clone()) + .accept(Authenticator::ALPN, auth_b.clone()) + .spawn(); + + tokio::spawn({ + let endpoint_a = endpoint_a.clone(); + let endpoint_b = endpoint_b.clone(); + async move { + endpoint_a + .connect(endpoint_b.addr(), b"/dummy/1") + .await + .ok(); + } + }); + + let wait_loop = async { + let wait_a = async { + while auth_a.list_authenticated().await.is_empty() + && auth_a.list_blocked().await.is_empty() + { + time::sleep(Duration::from_millis(100)).await; + } + }; + let wait_b = async { + while auth_b.list_authenticated().await.is_empty() + && auth_b.list_blocked().await.is_empty() + { + time::sleep(Duration::from_millis(100)).await; + } + }; + tokio::join!(wait_a, wait_b); + }; + + if timeout(AUTH_TIMEOUT * 2, wait_loop).await.is_err() { + router_a.shutdown().await.ok(); + router_b.shutdown().await.ok(); + return Err("Authentication did not complete in time".to_string()); + } + + router_a.shutdown().await.ok(); + router_b.shutdown().await.ok(); + + Ok(auth_a.is_authenticated(&endpoint_b.id()).await + && auth_b.is_authenticated(&endpoint_a.id()).await) +} + +async fn run_auth_parallel_test( + secret_a: &'static [u8], + secret_b: &'static [u8], + parallel_count: usize, +) -> Result { + let auth_a = Authenticator::new(secret_a); + let mut endpoint_a_builder = iroh::Endpoint::builder(iroh::endpoint::presets::N0); + let alpns = (0..parallel_count) + .map(|i| format!("/dummy/{}", i).into_bytes()) + .collect(); + endpoint_a_builder = endpoint_a_builder.alpns(alpns); + + let endpoint_a = endpoint_a_builder + .hooks(auth_a.clone()) + .bind() + .await + .map_err(|e| e.to_string())?; + auth_a.set_endpoint(&endpoint_a).await; + + let auth_b = Authenticator::new(secret_b); + let mut endpoint_b_builder = iroh::Endpoint::builder(iroh::endpoint::presets::N0); + let alpns = (0..parallel_count) + .map(|i| format!("/dummy/{}", i).into_bytes()) + .collect(); + endpoint_b_builder = endpoint_b_builder.alpns(alpns); + let endpoint_b = endpoint_b_builder + .hooks(auth_b.clone()) + .bind() + .await + .map_err(|e| e.to_string())?; + auth_b.set_endpoint(&endpoint_b).await; + + let mut router_a_builder = iroh::protocol::Router::builder(endpoint_a.clone()); + for i in 0..parallel_count { + router_a_builder = + router_a_builder.accept(format!("/dummy/{}", i).into_bytes(), DummyProtocol); + } + let router_a = router_a_builder + .accept(Authenticator::ALPN, auth_a.clone()) + .spawn(); + + let mut router_b_builder = iroh::protocol::Router::builder(endpoint_b.clone()); + for i in 0..parallel_count { + router_b_builder = + router_b_builder.accept(format!("/dummy/{}", i).into_bytes(), DummyProtocol); + } + let router_b = router_b_builder + .accept(Authenticator::ALPN, auth_b.clone()) + .spawn(); + + let success = Arc::new(AtomicUsize::new(0)); + for i in 0..parallel_count { + tokio::time::sleep(Duration::from_millis(100)).await; + tokio::spawn({ + let endpoint_a = endpoint_a.clone(); + let endpoint_b = endpoint_b.clone(); + let success = success.clone(); + async move { + if endpoint_a + .connect( + endpoint_b.addr(), + format!("/dummy/{}", i).into_bytes().as_slice(), + ) + .await + .is_ok() + { + success.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + } + } + }); + } + + let wait_loop = async { + let wait_a = async { + while auth_a.list_authenticated().await.len() + auth_a.list_blocked().await.len() + < 1 + || success.load(std::sync::atomic::Ordering::SeqCst) < parallel_count + { + time::sleep(Duration::from_millis(1000)).await; + } + }; + let wait_b = async { + while auth_b.list_authenticated().await.len() + auth_b.list_blocked().await.len() + < 1 + || success.load(std::sync::atomic::Ordering::SeqCst) < parallel_count + { + time::sleep(Duration::from_millis(1000)).await; + } + }; + tokio::join!(wait_a, wait_b); + }; + + if timeout(AUTH_TIMEOUT * 2, wait_loop).await.is_err() { + router_a.shutdown().await.ok(); + router_b.shutdown().await.ok(); + return Err("Authentication did not complete in time".to_string()); + } + + router_a.shutdown().await.ok(); + router_b.shutdown().await.ok(); + + Ok(auth_a.is_authenticated(&endpoint_b.id()).await + && auth_b.is_authenticated(&endpoint_a.id()).await) +} + +async fn run_auth_asymmetric_retry_test(secret: &'static [u8]) -> Result { + let auth_a = Authenticator::new(secret); + let endpoint_a = iroh::Endpoint::builder(iroh::endpoint::presets::N0) + .alpns(vec![b"/dummy/1".to_vec()]) + .hooks(auth_a.clone()) + .bind() + .await + .map_err(|e| e.to_string())?; + auth_a.set_endpoint(&endpoint_a).await; + + let auth_b = Authenticator::new(secret); + let endpoint_b = iroh::Endpoint::builder(iroh::endpoint::presets::N0) + .alpns(vec![b"/dummy/1".to_vec()]) + .hooks(auth_b.clone()) + .bind() + .await + .map_err(|e| e.to_string())?; + auth_b.set_endpoint(&endpoint_b).await; + + let router_a = iroh::protocol::Router::builder(endpoint_a.clone()) + .accept(Authenticator::ALPN, auth_a.clone()) + .accept(b"/dummy/1", DummyProtocol) + .spawn(); + + let router_b = iroh::protocol::Router::builder(endpoint_b.clone()) + .accept(Authenticator::ALPN, auth_b.clone()) + .accept(b"/dummy/1", DummyProtocol) + .spawn(); + + register_in_flight(auth_a.auth_state.clone(), endpoint_b.id()) + .await + .map_err(|e| e.to_string())?; + release_in_flight( + auth_a.auth_state.clone(), + endpoint_b.id(), + AuthState::Authenticated, + ) + .await + .map_err(|e| e.to_string())?; + + register_in_flight(auth_b.auth_state.clone(), endpoint_a.id()) + .await + .map_err(|e| e.to_string())?; + release_in_flight( + auth_b.auth_state.clone(), + endpoint_a.id(), + AuthState::Unauthenticated, + ) + .await + .map_err(|e| e.to_string())?; + + endpoint_a.online().await; + endpoint_b.online().await; + + let retry = tokio::spawn({ + let endpoint_a = endpoint_a.clone(); + let endpoint_b = endpoint_b.clone(); + async move { endpoint_a.connect(endpoint_b.addr(), b"/dummy/1").await } + }); + + let retry_result = timeout(AUTH_TIMEOUT, retry) + .await + .map_err(|_| "Authentication retry did not complete in time".to_string())? + .map_err(|e| e.to_string())?; + + time::sleep(Duration::from_millis(250)).await; + + let a_authenticated = auth_a.is_authenticated(&endpoint_b.id()).await; + let b_authenticated = auth_b.is_authenticated(&endpoint_a.id()).await; + + router_a.shutdown().await.ok(); + router_b.shutdown().await.ok(); + + if retry_result.is_err() { + return Err("Authentication retry failed".to_string()); + } + + if !a_authenticated { + return Err("Node A lost authenticated state".to_string()); + } + + Ok(a_authenticated && b_authenticated) +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_auth_late_failure_does_not_clear_authenticated() { + let secret = b"supersecrettoken1234567890123456"; + assert!(run_auth_late_failure_does_not_clear_authenticated_test(secret) + .await + .expect("late failure regression test failed")); +} + +async fn run_auth_late_failure_does_not_clear_authenticated_test( + secret: &'static [u8], +) -> Result { + let auth_a = Authenticator::new(secret); + let endpoint_a = iroh::Endpoint::builder(iroh::endpoint::presets::N0) + .alpns(vec![b"/dummy/1".to_vec()]) + .hooks(auth_a.clone()) + .bind() + .await + .map_err(|e| e.to_string())?; + auth_a.set_endpoint(&endpoint_a).await; + + let auth_b = Authenticator::new(secret); + let endpoint_b = iroh::Endpoint::builder(iroh::endpoint::presets::N0) + .alpns(vec![b"/dummy/1".to_vec()]) + .hooks(auth_b.clone()) + .bind() + .await + .map_err(|e| e.to_string())?; + auth_b.set_endpoint(&endpoint_b).await; + + register_in_flight(auth_a.auth_state.clone(), endpoint_b.id()) + .await + .map_err(|e| e.to_string())?; + + // Simulate two overlapping auth attempts for the same remote id: + // one wins and marks Authenticated, the other finishes later with failure. + release_in_flight( + auth_a.auth_state.clone(), + endpoint_b.id(), + AuthState::Authenticated, + ) + .await + .map_err(|e| e.to_string())?; + + release_in_flight( + auth_a.auth_state.clone(), + endpoint_b.id(), + AuthState::Unauthenticated, + ) + .await + .map_err(|e| e.to_string())?; + + Ok(auth_a.is_authenticated(&endpoint_b.id()).await) +} + +#[test] +fn test_into_secret_impls() { + use secrecy::ExposeSecret; + + let expected_bytes = b"my-secret-key"; + + // &str + let secret = "my-secret-key".into_secret(); + assert_eq!(secret.expose_secret(), expected_bytes); + + // String + let secret = String::from("my-secret-key").into_secret(); + assert_eq!(secret.expose_secret(), expected_bytes); + // Vec + let secret = b"my-secret-key".to_vec().into_secret(); + assert_eq!(secret.expose_secret(), expected_bytes); + + // &[u8] + let bytes: &[u8] = b"my-secret-key"; + let secret = bytes.into_secret(); + assert_eq!(secret.expose_secret(), expected_bytes); + + // &[u8; N] + let bytes: &[u8; 13] = b"my-secret-key"; + let secret = bytes.into_secret(); + assert_eq!(secret.expose_secret(), expected_bytes); + + // Box<[u8]> + let bytes: Box<[u8]> = Box::new(*b"my-secret-key"); + let secret = bytes.into_secret(); + assert_eq!(secret.expose_secret(), expected_bytes); + + // SecretSlice + let ps = SecretSlice::new(Box::new(*b"my-secret-key")); + let secret = ps.into_secret(); + assert_eq!(secret.expose_secret(), expected_bytes); +} \ No newline at end of file