diff --git a/Cargo.lock b/Cargo.lock index af49244..19f4996 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,9 +28,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" [[package]] name = "arrayref" @@ -202,9 +202,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" dependencies = [ "serde", ] @@ -340,9 +340,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.2.0-rc.13" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7722afd27468475c9b6063dc03a57ef2ca833816981619f8ebe64d38d207eef" +checksum = "211f05e03c7d03754740fd9e585de910a095d6b99f8bcfffdef8319fa02a8331" dependencies = [ "hybrid-array", ] @@ -371,7 +371,7 @@ dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", - "digest 0.11.0-rc.9", + "digest 0.11.0-rc.10", "fiat-crypto 0.3.0", "rand_core 0.9.5", "rustc_version", @@ -525,12 +525,13 @@ dependencies = [ [[package]] name = "digest" -version = "0.11.0-rc.9" +version = "0.11.0-rc.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff8de092798697546237a3a701e4174fe021579faec9b854379af9bf1e31962" +checksum = "afa94b64bfc6549e6e4b5a3216f22593224174083da7a90db47e951c4fb31725" dependencies = [ "block-buffer 0.11.0", - "crypto-common 0.2.0-rc.13", + "const-oid", + "crypto-common 0.2.0", ] [[package]] @@ -584,9 +585,9 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "ed25519" -version = "3.0.0-rc.3" +version = "3.0.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d058004dae83c9cf58f3d81612d0296bbf0a52dd7d7b6afa30ab7228bb6119f" +checksum = "c6e914c7c52decb085cea910552e24c63ac019e3ab8bf001ff736da9a9d9d890" dependencies = [ "pkcs8", "serde", @@ -603,7 +604,7 @@ dependencies = [ "ed25519", "rand_core 0.9.5", "serde", - "sha2 0.11.0-rc.4", + "sha2 0.11.0-rc.2", "signature", "subtle", "zeroize", @@ -1098,9 +1099,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hybrid-array" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b41fb3dc24fe72c2e3a4685eed55917c2fb228851257f4a8f2d985da9443c3e5" +checksum = "e1b229d73f5803b562cc26e4da0396c8610a4ee209f4fac8fa4f8d709166dc45" dependencies = [ "typenum", ] @@ -1147,14 +1148,13 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ "base64", "bytes", "futures-channel", - "futures-core", "futures-util", "http", "http-body", @@ -1368,9 +1368,9 @@ dependencies = [ [[package]] name = "iroh" -version = "0.96.0" +version = "0.96.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3790cc3a5ef6a89a1e30b64de54de31e692958e2dc8a37cf2831d52c76805de9" +checksum = "5236da4d5681f317ec393c8fe2b7e3d360d31c6bb40383991d0b7429ca5ad117" dependencies = [ "backon", "bytes", @@ -1387,13 +1387,13 @@ dependencies = [ "iroh-metrics", "iroh-quinn", "iroh-quinn-proto", - "iroh-quinn-udp 0.8.0", + "iroh-quinn-udp", "iroh-relay", "n0-error", "n0-future", "n0-watcher", "netdev", - "netwatch 0.14.0", + "netwatch", "papaya", "pin-project", "pkarr", @@ -1421,7 +1421,7 @@ dependencies = [ [[package]] name = "iroh-auth" -version = "0.1.2" +version = "0.1.3" dependencies = [ "anyhow", "hkdf", @@ -1441,17 +1441,19 @@ dependencies = [ [[package]] name = "iroh-base" -version = "0.96.0" +version = "0.96.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c3fc0440c8775bf2677a58550fcef7e544346add01bf1b163f9fc0cedd436e" +checksum = "20c99d836a1c99e037e98d1bf3ef209c3a4df97555a00ce9510eb78eccdf5567" dependencies = [ "curve25519-dalek 5.0.0-pre.1", "data-encoding", "derive_more", + "digest 0.11.0-rc.10", "ed25519-dalek", "n0-error", "rand_core 0.9.5", "serde", + "sha2 0.11.0-rc.2", "url", "zeroize", "zeroize_derive", @@ -1516,14 +1518,14 @@ dependencies = [ [[package]] name = "iroh-quinn" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a207ea77da14b683e8d454088795b6ac38e5d5cf26ded6868b9d80392cf8c1" +checksum = "034ed21f34c657a123d39525d948c885aacba59508805e4dd67d71f022e7151b" dependencies = [ "bytes", "cfg_aliases", "iroh-quinn-proto", - "iroh-quinn-udp 0.8.0", + "iroh-quinn-udp", "pin-project-lite", "rustc-hash", "rustls", @@ -1537,9 +1539,9 @@ dependencies = [ [[package]] name = "iroh-quinn-proto" -version = "0.15.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f66e567aa8b052c5632b8fc902fbb503891eda705553494ccddf3505257be40" +checksum = "0de99ad8adc878ee0e68509ad256152ce23b8bbe45f5539d04e179630aca40a9" dependencies = [ "bytes", "derive_more", @@ -1561,19 +1563,6 @@ dependencies = [ "web-time", ] -[[package]] -name = "iroh-quinn-udp" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a91fe9ec3db6615d7ab1b303717f3b98fc40b96955a4ea25b113b1b879f7481f" -dependencies = [ - "cfg_aliases", - "libc", - "socket2 0.6.2", - "tracing", - "windows-sys 0.61.2", -] - [[package]] name = "iroh-quinn-udp" version = "0.8.0" @@ -1589,9 +1578,9 @@ dependencies = [ [[package]] name = "iroh-relay" -version = "0.96.0" +version = "0.96.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "236c6f131ce774f7cc7548f467890c313b09f7849b8d703360d6602bc8c5184c" +checksum = "cd2b63e654b9dec799a73372cdc79b529ca6c7248c0c8de7da78a02e3a46f03c" dependencies = [ "blake3", "bytes", @@ -1844,9 +1833,9 @@ dependencies = [ [[package]] name = "n0-watcher" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba717c22ceec021ace0ff7674bf8fd60c9394605740a8201678fc1cb3a7398f6" +checksum = "38795f7932e6e9d1c6e989270ef5b3ff24ebb910e2c9d4bed2d28d8bae3007dc" dependencies = [ "derive_more", "n0-error", @@ -1935,42 +1924,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "netwatch" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "970729c08dbe7987d698f996c6b4945cbfdcdd6ee627df6de51d5469cec13b99" -dependencies = [ - "atomic-waker", - "bytes", - "cfg_aliases", - "derive_more", - "iroh-quinn-udp 0.7.0", - "js-sys", - "libc", - "n0-error", - "n0-future", - "n0-watcher", - "netdev", - "netlink-packet-core", - "netlink-packet-route 0.28.0", - "netlink-proto", - "netlink-sys", - "objc2-core-foundation", - "objc2-system-configuration", - "pin-project-lite", - "serde", - "socket2 0.6.2", - "time", - "tokio", - "tokio-util", - "tracing", - "web-sys", - "windows", - "windows-result", - "wmi", -] - [[package]] name = "netwatch" version = "0.14.0" @@ -1981,7 +1934,7 @@ dependencies = [ "bytes", "cfg_aliases", "derive_more", - "iroh-quinn-udp 0.8.0", + "iroh-quinn-udp", "js-sys", "libc", "n0-error", @@ -2304,9 +2257,9 @@ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portmapper" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29fb522a166045a35b507dea30e3eb69bca1c5a53669d252744d5a0d8474ffa" +checksum = "7d2a8825353ace3285138da3378b1e21860d60351942f7aa3b99b13b41f80318" dependencies = [ "base64", "bytes", @@ -2318,7 +2271,7 @@ dependencies = [ "iroh-metrics", "libc", "n0-error", - "netwatch 0.13.0", + "netwatch", "num_enum", "rand", "serde", @@ -2527,9 +2480,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -2538,9 +2491,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" [[package]] name = "reqwest" @@ -2799,13 +2752,13 @@ dependencies = [ [[package]] name = "sha2" -version = "0.11.0-rc.4" +version = "0.11.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7535f94fa3339fe9e5e9be6260a909e62af97f6e14b32345ccf79b92b8b81233" +checksum = "d1e3878ab0f98e35b2df35fe53201d088299b41a6bb63e3e34dada2ac4abd924" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.11.0-rc.9", + "digest 0.11.0-rc.10", ] [[package]] @@ -2835,9 +2788,9 @@ dependencies = [ [[package]] name = "signature" -version = "3.0.0-rc.9" +version = "3.0.0-rc.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad0ce3b3f8efd7406f22e2ca5d02be21cdf3b3d1d53ab141f784de8965c7c7e" +checksum = "7f1880df446116126965eeec169136b2e0251dba37c6223bcc819569550edea3" [[package]] name = "simdutf8" @@ -3053,9 +3006,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.46" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9da98b7d9b7dad93488a84b8248efc35352b0b2657397d4167e7ad67e5d535e5" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", @@ -3077,9 +3030,9 @@ checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.26" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78cc610bac2dcee56805c99642447d4c5dbde4d01f752ffea0199aee1f601dc4" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", @@ -3576,9 +3529,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" dependencies = [ "rustls-pki-types", ] @@ -4063,18 +4016,18 @@ checksum = "2164e798d9e3d84ee2c91139ace54638059a3b23e361f5c11781c2c6459bde0f" [[package]] name = "zerocopy" -version = "0.8.37" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7456cf00f0685ad319c5b1693f291a650eaf345e941d082fc4e03df8a03996ac" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.37" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1328722bbf2115db7e19d69ebcc15e795719e2d66b60827c6a69a117365e37a0" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", @@ -4157,6 +4110,6 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1966f8ac2c1f76987d69a74d0e0f929241c10e78136434e3be70ff7f58f64214" +checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" diff --git a/Cargo.toml b/Cargo.toml index c1c03c6..506b6c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iroh-auth" -version = "0.1.2" +version = "0.1.3" edition = "2021" authors = ["rustonbsd "] description = "Authentication middleware for iroh" diff --git a/src/lib.rs b/src/lib.rs index d201d33..199db11 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,13 +4,13 @@ use std::{ sync::{Arc, Mutex}, time::Duration, }; -use tracing::{trace, debug, error, info, warn}; +use tracing::{debug, error, info, trace, warn}; use hkdf::Hkdf; use iroh::{ endpoint::{AfterHandshakeOutcome, Connection, EndpointHooks, VarInt}, protocol::ProtocolHandler, - Endpoint, PublicKey, Watcher, + Endpoint, EndpointId, PublicKey, Watcher, }; use n0_future::{task::spawn, time::timeout, StreamExt}; use secrecy::{ExposeSecret, SecretSlice}; @@ -24,6 +24,8 @@ pub enum AuthenticatorError { AddFailed, AcceptFailed(String), OpenFailed(String), + AcceptFailedAndBlock(String, EndpointId), + OpenFailedAndBlock(String, EndpointId), EndpointNotSet, } @@ -37,6 +39,12 @@ impl std::fmt::Display for AuthenticatorError { 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) + } } } } @@ -104,6 +112,7 @@ pub struct 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; @@ -188,9 +197,9 @@ impl Authenticator { /// 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!("accepting auth connection from {}", remote_id); + debug!("[auth_accept] accepting auth connection from {}", remote_id); let (mut send, mut recv) = conn.accept_bi().await.map_err(|err| { - error!("accept bidirectional stream failed: {}", err); + error!("[auth_accept] accept bidirectional stream failed: {}", err); AuthenticatorError::AcceptFailed(format!("Accept bidirectional stream failed: {}", err)) })?; @@ -202,18 +211,18 @@ impl Authenticator { let mut token_a = [0u8; 33]; recv.read_exact(&mut token_a).await.map_err(|err| { - error!("failed to read token_a: {}", 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!("failed to write token_b: {}", 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!("SPAKE2 invalid: {}", err); - AuthenticatorError::AcceptFailed(format!("SPAKE2 invalid: {}", err)) + error!("[auth_accept] SPAKE2 invalid: {}", err); + AuthenticatorError::AcceptFailedAndBlock(format!("SPAKE2 invalid: {}", err), remote_id) })?; let hk = Hkdf::::new(None, shared_secret.as_slice()); @@ -221,34 +230,35 @@ impl Authenticator { let mut open_key = [0u8; 64]; hk.expand(Self::ACCEPT_CONTEXT, &mut accept_key) .map_err(|err| { - error!("failed to expand accept_key: {}", 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!("failed to expand open_key: {}", 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!("failed to write accept_key: {}", 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!("failed to read remote_open_key: {}", err); + error!("[auth_accept] failed to read remote_open_key: {}", err); AuthenticatorError::AcceptFailed(format!("Failed to read remote_open_key: {}", err)) })?; if !bool::from(remote_open_key.ct_eq(&open_key)) { - error!("remote open_key mismatch"); - return Err(AuthenticatorError::AcceptFailed( + 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!("authenticated connection from {}", remote_id); + info!("[auth_accept] authenticated connection from {}", remote_id); Ok(()) } @@ -258,10 +268,10 @@ impl Authenticator { /// 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!("opening auth connection to {}", remote_id); + debug!("[auth_open] opening auth connection to {}", remote_id); let (mut send, mut recv) = conn.open_bi().await.map_err(|err| { - error!("open bidirectional stream failed: {}", err); - AuthenticatorError::AcceptFailed(format!("Open bidirectional stream failed: {}", err)) + error!("[auth_open] open bidirectional stream failed: {}", err); + AuthenticatorError::OpenFailed(format!("Open bidirectional stream failed: {}", err)) })?; let (spake, token_a) = Spake2::::start_a( @@ -271,19 +281,19 @@ impl Authenticator { ); send.write_all(&token_a).await.map_err(|err| { - error!("failed to write token_a: {}", err); - AuthenticatorError::AcceptFailed(format!("Failed to write token_a: {}", 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!("failed to read token_b: {}", err); - AuthenticatorError::AcceptFailed(format!("Failed to read token_b: {}", 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!("SPAKE2 invalid: {}", err); - AuthenticatorError::AcceptFailed(format!("SPAKE2 invalid: {}", err)) + error!("[auth_open] SPAKE2 invalid: {}", err); + AuthenticatorError::OpenFailedAndBlock(format!("SPAKE2 invalid: {}", err), remote_id) })?; let hk = Hkdf::::new(None, shared_secret.as_slice()); @@ -291,46 +301,52 @@ impl Authenticator { let mut open_key = [0u8; 64]; hk.expand(Self::ACCEPT_CONTEXT, &mut accept_key) .map_err(|err| { - error!("failed to expand accept_key: {}", err); - AuthenticatorError::AcceptFailed(format!("Failed to expand accept_key: {}", 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!("failed to expand open_key: {}", err); - AuthenticatorError::AcceptFailed(format!("Failed to expand open_key: {}", 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!("failed to read remote_accept_key: {}", err); - AuthenticatorError::AcceptFailed(format!( - "Failed to read remote_accept_key: {}", - 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!("remote accept_key mismatch"); - return Err(AuthenticatorError::AcceptFailed( + 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(); + send.finish().ok(); + conn.closed().await; + + return Err(AuthenticatorError::OpenFailedAndBlock( "Remote accept_key mismatch".to_string(), + remote_id, )); } send.write_all(&open_key).await.map_err(|err| { - error!("failed to write open_key: {}", err); - AuthenticatorError::AcceptFailed(format!("Failed to write open_key: {}", err)) + error!("[auth_open] failed to write open_key: {}", err); + AuthenticatorError::OpenFailed(format!("Failed to write open_key: {}", err)) })?; send.finish().map_err(|err| { - error!("failed to finish stream: {}", err); - AuthenticatorError::AcceptFailed(format!("Failed to finish stream: {}", err)) + error!("[auth_open] failed to finish stream: {}", err); + AuthenticatorError::OpenFailed(format!("Failed to finish stream: {}", err)) })?; conn.closed().await; self.add_authenticated(conn.remote_id())?; - info!("authenticated connection to {}", remote_id); + info!("[auth_open] authenticated connection to {}", remote_id); Ok(()) } @@ -341,15 +357,25 @@ impl ProtocolHandler for Authenticator { &self, connection: iroh::endpoint::Connection, ) -> Result<(), iroh::protocol::AcceptError> { - if let Err(err) = self - .auth_accept(connection) - .await - .map_err(|err| iroh::protocol::AcceptError::from_err(err)) - { - self.add_blocked().ok(); - Err(err) - } else { - Ok(()) + 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()), + )) + } } } } @@ -360,13 +386,13 @@ impl EndpointHooks for Authenticator { conn_info: &'a iroh::endpoint::ConnectionInfo, ) -> iroh::endpoint::AfterHandshakeOutcome { if self.is_authenticated(&conn_info.remote_id()) { - debug!("already authenticated: {}", conn_info.remote_id()); + debug!("[after_handshake] already authenticated: {}", conn_info.remote_id()); return AfterHandshakeOutcome::accept(); } if conn_info.alpn() == Self::ALPN { debug!( - "skipping auth for connection with alpn {}", + "[after_handshake] skipping auth for connection with alpn {}", String::from_utf8_lossy(conn_info.alpn()) ); return AfterHandshakeOutcome::accept(); @@ -387,10 +413,10 @@ impl EndpointHooks for Authenticator { )) }; - match timeout(Duration::from_secs(10), wait_for_auth).await { + match timeout(AUTH_TIMEOUT, wait_for_auth).await { Ok(_) => AfterHandshakeOutcome::accept(), Err(_) => { - warn!("authentication timed out for {}", remote_id); + warn!("[after_handshake] authentication timed out for {}", remote_id); AfterHandshakeOutcome::Reject { error_code: VarInt::from_u32(401), reason: b"Authentication timed out".to_vec(), @@ -405,27 +431,27 @@ impl EndpointHooks for Authenticator { alpn: &'a [u8], ) -> iroh::endpoint::BeforeConnectOutcome { if self.is_authenticated(&remote_addr.id) { - debug!("already authenticated: {}", remote_addr.id); + debug!("[before_connect] already authenticated: {}", remote_addr.id); return iroh::endpoint::BeforeConnectOutcome::Accept; } if alpn == Self::ALPN { debug!( - "skipping auth for connection to {} with alpn {:?}", + "[before_connect] skipping auth for connection to {} with alpn {:?}", remote_addr.id, alpn ); return iroh::endpoint::BeforeConnectOutcome::Accept; } debug!( - "initiating auth for client connection with alpn {} to {}", + "[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!("authenticator endpoint not set"); + warn!("[before_connect] authenticator endpoint not set"); return iroh::endpoint::BeforeConnectOutcome::Reject; } }; @@ -434,28 +460,52 @@ impl EndpointHooks for Authenticator { let remote_id = remote_addr.id; async move { - debug!("background: connecting to {} for auth", remote_id); - - match endpoint.connect(remote_id, Self::ALPN).await { - Ok(conn) => { - debug!("background: connected to {}, performing auth", remote_id); - if let Err(err) = auth.auth_open(conn).await { - auth.add_blocked().ok(); + 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!( - "background: authentication failed for {}: {}", - remote_id, err + "[before_connect] background: failed to open connection for authentication to {}: {}, retrying...", + remote_id, e ); - } else { - debug!("background: authentication successful for {}", remote_id); } - } - Err(e) => { - warn!( - "background: failed to open connection for authentication to {}: {}", - 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 @@ -518,7 +568,6 @@ mod tests { secret_a: &'static [u8], secret_b: &'static [u8], ) -> Result { - let auth_a = Authenticator::new(secret_a); let endpoint_a = iroh::Endpoint::builder() .hooks(auth_a.clone()) @@ -564,6 +613,10 @@ mod tests { 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; } @@ -572,6 +625,10 @@ mod tests { 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; } @@ -580,7 +637,7 @@ mod tests { tokio::join!(wait_a, wait_b); }; - if timeout(Duration::from_secs(20), wait_loop).await.is_err() { + 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());