From 6e680980c3fee59a224aa2757b80a4fc037c2df5 Mon Sep 17 00:00:00 2001 From: rustonbsd Date: Sat, 31 Jan 2026 18:01:29 +0100 Subject: [PATCH 1/4] add: const time comparison --- Cargo.lock | 137 +++++++++++++++++++++++++++-------------------------- Cargo.toml | 13 ++--- src/lib.rs | 5 +- 3 files changed, 79 insertions(+), 76 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 03599ab..b421bf5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -211,9 +211,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.53" +version = "1.2.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" dependencies = [ "find-msvc-tools", "shlex", @@ -340,9 +340,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.2.0-rc.12" +version = "0.2.0-rc.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6dcdb44f2c3ee25689ca12a4c19e664fd09f97aeae0bc5043b2dbab6389e308" +checksum = "c7722afd27468475c9b6063dc03a57ef2ca833816981619f8ebe64d38d207eef" dependencies = [ "hybrid-array", ] @@ -371,7 +371,7 @@ dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", - "digest 0.11.0-rc.7", + "digest 0.11.0-rc.9", "fiat-crypto 0.3.0", "rand_core 0.9.5", "rustc_version", @@ -525,12 +525,12 @@ dependencies = [ [[package]] name = "digest" -version = "0.11.0-rc.7" +version = "0.11.0-rc.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca14c221bd9052fd2da7c34a2eeb5ae54732db28be47c35937be71793d675422" +checksum = "bff8de092798697546237a3a701e4174fe021579faec9b854379af9bf1e31962" dependencies = [ "block-buffer 0.11.0", - "crypto-common 0.2.0-rc.12", + "crypto-common 0.2.0-rc.13", ] [[package]] @@ -584,9 +584,9 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "ed25519" -version = "3.0.0-rc.2" +version = "3.0.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "594435fe09e345ee388e4e8422072ff7dfeca8729389fbd997b3f5504c44cd47" +checksum = "3d058004dae83c9cf58f3d81612d0296bbf0a52dd7d7b6afa30ab7228bb6119f" dependencies = [ "pkcs8", "serde", @@ -603,7 +603,7 @@ dependencies = [ "ed25519", "rand_core 0.9.5", "serde", - "sha2 0.11.0-rc.3", + "sha2 0.11.0-rc.4", "signature", "subtle", "zeroize", @@ -692,9 +692,9 @@ checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" [[package]] name = "find-msvc-tools" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "fixedbitset" @@ -1163,7 +1163,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.1", + "socket2 0.6.2", "tokio", "tower-service", "tracing", @@ -1171,9 +1171,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.64" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1433,6 +1433,7 @@ dependencies = [ "secrecy", "sha2 0.10.9", "spake2", + "subtle", "tokio", "tracing", "tracing-subscriber", @@ -1488,9 +1489,9 @@ dependencies = [ [[package]] name = "iroh-metrics" -version = "0.38.1" +version = "0.38.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5828152c482cf9d95f3039848ac2be5e6e47c41dbf3695a453e6c02739c50d2c" +checksum = "c946095f060e6e59b9ff30cc26c75cdb758e7fb0cde8312c89e2144654989fcb" dependencies = [ "iroh-metrics-derive", "itoa", @@ -1503,9 +1504,9 @@ dependencies = [ [[package]] name = "iroh-metrics-derive" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e12bd0763fd16062f5cc5e8db15dd52d26e75a8af4c7fb57ccee3589b344b8" +checksum = "cab063c2bfd6c3d5a33a913d4fdb5252f140db29ec67c704f20f3da7e8f92dbf" dependencies = [ "heck", "proc-macro2", @@ -1526,7 +1527,7 @@ dependencies = [ "pin-project-lite", "rustc-hash", "rustls", - "socket2 0.6.1", + "socket2 0.6.2", "thiserror", "tokio", "tokio-stream", @@ -1568,7 +1569,7 @@ checksum = "a91fe9ec3db6615d7ab1b303717f3b98fc40b96955a4ea25b113b1b879f7481f" dependencies = [ "cfg_aliases", "libc", - "socket2 0.6.1", + "socket2 0.6.2", "tracing", "windows-sys 0.61.2", ] @@ -1581,7 +1582,7 @@ checksum = "f981dadd5a072a9e0efcd24bdcc388e570073f7e51b33505ceb1ef4668c80c86" dependencies = [ "cfg_aliases", "libc", - "socket2 0.6.1", + "socket2 0.6.2", "tracing", "windows-sys 0.61.2", ] @@ -1690,9 +1691,9 @@ checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libm" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "litemap" @@ -1783,9 +1784,9 @@ dependencies = [ [[package]] name = "moka" -version = "0.12.12" +version = "0.12.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3dec6bd31b08944e08b58fd99373893a6c17054d6f3ea5006cc894f4f4eee2a" +checksum = "b4ac832c50ced444ef6be0767a008b02c106a909ba79d1d830501e94b96f6b7e" dependencies = [ "crossbeam-channel", "crossbeam-epoch", @@ -1959,7 +1960,7 @@ dependencies = [ "objc2-system-configuration", "pin-project-lite", "serde", - "socket2 0.6.1", + "socket2 0.6.2", "time", "tokio", "tokio-util", @@ -1995,7 +1996,7 @@ dependencies = [ "objc2-system-configuration", "pin-project-lite", "serde", - "socket2 0.6.1", + "socket2 0.6.2", "time", "tokio", "tokio-util", @@ -2032,9 +2033,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" [[package]] name = "num-traits" @@ -2274,9 +2275,9 @@ dependencies = [ [[package]] name = "pkcs8" -version = "0.11.0-rc.9" +version = "0.11.0-rc.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80f8fa6196ede5a9f9ee95b44ca134bddc9b70e8913f9297bd58c909f5889a09" +checksum = "b226d2cc389763951db8869584fd800cbbe2962bf454e2edeb5172b31ee99774" dependencies = [ "der", "spki", @@ -2297,9 +2298,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portmapper" @@ -2322,7 +2323,7 @@ dependencies = [ "rand", "serde", "smallvec", - "socket2 0.6.1", + "socket2 0.6.2", "time", "tokio", "tokio-util", @@ -2420,7 +2421,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.6.1", + "socket2 0.6.2", "thiserror", "tokio", "tracing", @@ -2457,16 +2458,16 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.1", + "socket2 0.6.2", "tracing", "windows-sys 0.60.2", ] [[package]] name = "quote" -version = "1.0.43" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -2798,13 +2799,13 @@ dependencies = [ [[package]] name = "sha2" -version = "0.11.0-rc.3" +version = "0.11.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d43dc0354d88b791216bb5c1bfbb60c0814460cc653ae0ebd71f286d0bd927" +checksum = "7535f94fa3339fe9e5e9be6260a909e62af97f6e14b32345ccf79b92b8b81233" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.11.0-rc.7", + "digest 0.11.0-rc.9", ] [[package]] @@ -2834,9 +2835,9 @@ dependencies = [ [[package]] name = "signature" -version = "3.0.0-rc.8" +version = "3.0.0-rc.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c04b70a14ee5f15e2e0c785a5fdb2e9a51138dfe13ba3cf8eab037a9e60b1879" +checksum = "0ad0ce3b3f8efd7406f22e2ca5d02be21cdf3b3d1d53ab141f784de8965c7c7e" [[package]] name = "simdutf8" @@ -2855,15 +2856,15 @@ dependencies = [ [[package]] name = "siphasher" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" @@ -2883,9 +2884,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" dependencies = [ "libc", "windows-sys 0.60.2", @@ -3052,9 +3053,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.45" +version = "0.3.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" +checksum = "9da98b7d9b7dad93488a84b8248efc35352b0b2657397d4167e7ad67e5d535e5" dependencies = [ "deranged", "itoa", @@ -3070,15 +3071,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.25" +version = "0.2.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd" +checksum = "78cc610bac2dcee56805c99642447d4c5dbde4d01f752ffea0199aee1f601dc4" dependencies = [ "num-conv", "time-core", @@ -3120,7 +3121,7 @@ dependencies = [ "mio", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.1", + "socket2 0.6.2", "tokio-macros", "windows-sys 0.61.2", ] @@ -3388,9 +3389,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" dependencies = [ "getrandom 0.3.4", "js-sys", @@ -3978,9 +3979,9 @@ checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "wmi" -version = "0.18.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d1d435f7745ba9ed55c43049d47b5fbd1104449beaa2afbc80a1e10a4a018" +checksum = "746791db82f029aaefc774ccbb8e61306edba18ef2c8998337cadccc0b8067f7" dependencies = [ "chrono", "futures", @@ -4062,18 +4063,18 @@ checksum = "2164e798d9e3d84ee2c91139ace54638059a3b23e361f5c11781c2c6459bde0f" [[package]] name = "zerocopy" -version = "0.8.33" +version = "0.8.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" +checksum = "7456cf00f0685ad319c5b1693f291a650eaf345e941d082fc4e03df8a03996ac" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.33" +version = "0.8.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" +checksum = "1328722bbf2115db7e19d69ebcc15e795719e2d66b60827c6a69a117365e37a0" dependencies = [ "proc-macro2", "quote", @@ -4156,6 +4157,6 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.16" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65" +checksum = "1966f8ac2c1f76987d69a74d0e0f929241c10e78136434e3be70ff7f58f64214" diff --git a/Cargo.toml b/Cargo.toml index 8bc6417..a34e112 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,22 +10,23 @@ license = "MIT OR Apache-2.0" [dependencies] iroh = { version = "0.96" } spake2 = { version = "0.4" } -hkdf = { version = "0.12.4" } -secrecy = { version = "0.10.3" } -sha2 = { version = "0.10.9" } +hkdf = { version = "0.12" } +secrecy = { version = "0.10" } +sha2 = { version = "0.10" } +subtle = { version = "2.6" } rand = { version = "0.9" } n0-future = { version = "0.3" } n0-watcher = { version = "0.6" } -tracing = { version = "0.1.44" } +tracing = { version = "0.1" } tokio = { version = "1" } iroh-gossip = { version = "0.96", optional = true } anyhow = { version = "1", optional = true} -tracing-subscriber = { version = "0.3.22", features = ["env-filter"], optional = true } +tracing-subscriber = { version = "0.3", features = ["env-filter"], optional = true } [features] gossip = ["iroh-gossip", "anyhow", "tracing-subscriber"] -default = ["gossip"] +default = [] [[example]] name = "gossip" diff --git a/src/lib.rs b/src/lib.rs index 5619e54..8130504 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,7 @@ use n0_future::{task::spawn, time::timeout, StreamExt}; use secrecy::{ExposeSecret, SecretSlice}; use sha2::Sha512; use spake2::{Ed25519Group, Identity, Password, Spake2}; +use subtle::ConstantTimeEq; // Errors #[derive(Debug)] @@ -237,7 +238,7 @@ impl Authenticator { AuthenticatorError::AcceptFailed(format!("Failed to read remote_open_key: {}", err)) })?; - if remote_open_key != open_key { + if !bool::from(remote_open_key.ct_eq(&open_key)) { error!("remote open_key mismatch"); return Err(AuthenticatorError::AcceptFailed( "Remote open_key mismatch".to_string(), @@ -308,7 +309,7 @@ impl Authenticator { )) })?; - if remote_accept_key != accept_key { + if !bool::from(remote_accept_key.ct_eq(&accept_key)) { error!("remote accept_key mismatch"); return Err(AuthenticatorError::AcceptFailed( "Remote accept_key mismatch".to_string(), From 8e01e39dd267d587b3b56e5c6009d81b35f0063b Mon Sep 17 00:00:00 2001 From: rustonbsd Date: Sat, 31 Jan 2026 18:15:00 +0100 Subject: [PATCH 2/4] fix: renamed feature for clarity --- Cargo.toml | 4 ++-- examples/gossip.rs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a34e112..6b878ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,10 +25,10 @@ anyhow = { version = "1", optional = true} tracing-subscriber = { version = "0.3", features = ["env-filter"], optional = true } [features] -gossip = ["iroh-gossip", "anyhow", "tracing-subscriber"] +gossip-example = ["iroh-gossip", "anyhow", "tracing-subscriber"] default = [] [[example]] name = "gossip" path = "examples/gossip.rs" -required-features = ["gossip"] \ No newline at end of file +required-features = ["gossip-example"] \ No newline at end of file diff --git a/examples/gossip.rs b/examples/gossip.rs index ca54ea6..ac5822c 100644 --- a/examples/gossip.rs +++ b/examples/gossip.rs @@ -1,6 +1,6 @@ #[tokio::main] async fn main() -> Result<(), String> { - #[cfg(feature = "gossip")] + #[cfg(feature = "gossip-example")] { use iroh::PublicKey; use iroh::{protocol::Router, Endpoint}; @@ -42,7 +42,7 @@ async fn main() -> Result<(), String> { .spawn(); println!( - "cargo run --example gossip --features=gossip -- {}", + "cargo run --example gossip --features=gossip-example -- {}", router.endpoint().id() ); @@ -121,6 +121,6 @@ async fn main() -> Result<(), String> { println!("Shutting down..."); std::process::exit(0); } - #[cfg(not(feature = "gossip"))] - Err("gossip feature not enabled".to_string()) + #[cfg(not(feature = "gossip-example"))] + Err("gossip-example feature not enabled".to_string()) } From 404b03209044b164446667dc76e99690d82048f7 Mon Sep 17 00:00:00 2001 From: rustonbsd Date: Sat, 31 Jan 2026 18:15:27 +0100 Subject: [PATCH 3/4] add: readme + basic example (from readme) --- README.md | 117 +++++++++++++++++++++++++++++++++++++++++++++- examples/basic.rs | 43 +++++++++++++++++ 2 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 examples/basic.rs diff --git a/README.md b/README.md index 4fda73f..47441a6 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,117 @@ # iroh-auth -auth middleware for iroh + +[![Crates.io](https://img.shields.io/crates/v/iroh-auth.svg)](https://crates.io/crates/iroh-auth) +[![Docs.rs](https://docs.rs/iroh-auth/badge.svg)](https://docs.rs/iroh-auth) +[![Build Status](https://github.com/rustonbsd/iroh-auth/actions/workflows/test.yaml/badge.svg)](https://github.com/rustonbsd/iroh-auth/actions) + +Pre-Shared Key (PSK) authentication middleware for [iroh](https://github.com/n0-computer/iroh). + +Ensure that only peers knowing a shared secret can connect to your iroh nodes. This crate implements `EndpointHooks` to intercept connection attempts, perform a SPAKE2 password authenticated key exchange, and gate access to your application protocols. + +## Features + +- **Integration**: Works via standard `iroh::EndpointHooks` +- **Security**: Uses [SPAKE2](https://github.com/brycx/spake2-rs) (Simple Password Authenticated Key Exchange) to verify secrets +- **Side-channel Resistant**: Constant time comparison for sensitive operations +- **Zero-Config**: No certificates or complex PKI required, just a shared secret + +## Installation + +Add this to your `Cargo.toml`: + +```toml +[dependencies] +iroh = "0.96" +iroh-auth = "0.1" +``` + +## Usage + +Integrating `iroh-auth` involves four steps: +1. Initialize the `Authenticator` with your secret +2. Register the authenticator as an endpoint hook +3. Set the bound endpoint on the authenticator +4. Add the authenticator's protocol handler to your router + +```rust +// See /examples/basic.rs for a complete example +use iroh::{Endpoint, protocol::Router}; +use iroh_auth::Authenticator; + +#[tokio::main] +async fn main() -> Result<(), String> { + // 1. Create the authenticator with a shared secret + let auth = Authenticator::new("my-super-secret-password"); + + // 2. Build the endpoint with the auth hooks + let endpoint = Endpoint::builder() + .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); + + // 4. Register the auth protocol handler + let router = Router::builder(endpoint) + .accept(Authenticator::ALPN, auth.clone()) + + // Register your actual application protocols here + .accept(b"/my-app/1.0", MyProtocolHandler) + .spawn(); + + // ... run your application + router.shutdown().await.map_err(|e| e.to_string())?; + Ok(()) +} +``` + +## How It Works + +When a connection is attempted, `iroh-auth` pauses the application handshake and initiates a parallel authentication channel. + +```mermaid +sequenceDiagram + participant Client + participant Server + + Client->>Server: 1. App Connection Request + activate Server + Note right of Server: ✋ Paused (Waiting for Auth) + + rect rgb(225, 255, 225) + Note over Client, Server: 2. Authentication (Background) + Client->>Server: Auth Handshake (SPAKE2) + end + + alt Success + Server->>Server: Verify Secret (OK) + Note right of Server: ✅ Resumed + Server-->>Client: 3. App Connection Accepted + else Failure + Server->>Server: Verify Secret (Fail) + Note right of Server: ❌ Rejected + Server-->>Client: 3. Connection Closed + end + deactivate Server +``` + +1. **Interception**: The `before_connect` hook on the initiator spawns a background task to establish a dedicated authentication connection on `Authenticator::ALPN` +2. **Handshake**: The peers perform a SPAKE2 handshake. This proves possession of the shared password without sending the password itself +3. **Gating**: The `after_handshake` hook on the receiver blocks the application connection until the authentication handshake completes successfully. If it times out or fails, the connection is rejected + +## Protocol Details + +- **ALPN**: `/iroh/auth/0.1` +- **Cipher Suite**: ed25519-dalek group operations with SHA-512 HKDF +- **Context Separation**: distinct keys derived for `accept` and `open` confirmation steps to prevent replay attacks + +## License + +This project is licensed under either of + + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. diff --git a/examples/basic.rs b/examples/basic.rs new file mode 100644 index 0000000..e9717f1 --- /dev/null +++ b/examples/basic.rs @@ -0,0 +1,43 @@ +use iroh::{Endpoint, protocol::Router}; +use iroh_auth::Authenticator; + +#[derive(Debug)] +struct MyProtocolHandler; + +impl iroh::protocol::ProtocolHandler for MyProtocolHandler { + async fn accept( + &self, + _connection: iroh::endpoint::Connection, + ) -> Result<(), iroh::protocol::AcceptError> { + // Handle the protocol-specific logic here + Ok(()) + } +} + +#[tokio::main] +async fn main() -> Result<(), String> { + // 1. Create the authenticator with a shared secret + let auth = Authenticator::new("my-super-secret-password"); + + // 2. Build the endpoint with the auth hooks + let endpoint = Endpoint::builder() + .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); + + // 4. Register the auth protocol handler + let router = Router::builder(endpoint) + .accept(Authenticator::ALPN, auth.clone()) + + // Register your actual application protocols here + .accept(b"/my-app/1.0", MyProtocolHandler) + .spawn(); + + // ... run your application + router.shutdown().await.map_err(|e| e.to_string())?; + Ok(()) +} \ No newline at end of file From 4cab4dfe77ad21684a672bbdecb0e19c47715e19 Mon Sep 17 00:00:00 2001 From: rustonbsd Date: Sat, 31 Jan 2026 18:19:42 +0100 Subject: [PATCH 4/4] fix: cicd run tests on pr open --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 291feee..fbd8931 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -4,7 +4,7 @@ on: push: branches: [main] pull_request: - types: [synchronize] + types: [opened, synchronize, reopened] env: CARGO_TERM_COLOR: always