From 40c683e3847a21b7519e42ff5bd9e73d489e73a6 Mon Sep 17 00:00:00 2001 From: Jeroen van Maanen Date: Sun, 19 Oct 2025 21:26:57 +0000 Subject: [PATCH 01/12] Added persistence of node-key --- src/main.rs | 149 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 147 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 70ac10c..b81ff50 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,22 +1,27 @@ //! Command line arguments. use clap::{Parser, Subcommand}; use dumbpipe::NodeTicket; -use iroh::{endpoint::Connecting, Endpoint, NodeAddr, SecretKey}; +use hex::FromHexError; +use iroh::{endpoint::Connecting, Endpoint, KeyParsingError, NodeAddr, SecretKey}; use n0_snafu::{Result, ResultExt}; +use snafu::Snafu; use std::{ + borrow::Cow, io, net::{SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs}, str::FromStr, }; use tokio::{ + fs, io::{AsyncRead, AsyncWrite, AsyncWriteExt}, select, }; use tokio_util::sync::CancellationToken; +use tracing::log::{debug, error}; #[cfg(unix)] use { - std::path::PathBuf, + std::path::{Path, PathBuf}, tokio::net::{UnixListener, UnixStream}, }; @@ -122,6 +127,13 @@ pub struct CommonArgs { #[clap(long)] pub custom_alpn: Option, + /// Use a persistent node key pair + #[arg(long)] + persist: bool, + /// Write and read the node keys at the given location + #[arg(long)] + persist_at: Option, + /// The verbosity level. Repeat to increase verbosity. #[clap(short = 'v', long, action = clap::ArgAction::Count)] pub verbose: u8, @@ -298,10 +310,143 @@ async fn create_endpoint( if let Some(addr) = common.ipv6_addr { builder = builder.bind_addr_v6(addr); } + if common.persist || common.persist_at.is_some() { + builder = builder.secret_key(get_secret_key(common.persist_at.as_ref()).await); + } let endpoint = builder.bind().await?; Ok(endpoint) } +async fn get_secret_key(persist_at: Option<&PathBuf>) -> SecretKey { + let persist_at_cow = persist_at + .map(Cow::from) // Reference + .or_else(|| { + std::env::home_dir().map(|mut p| { + p.push(".auth"); + p.push("dumbpipe.key"); + debug!("Persisting key at: {p:?}"); + Cow::from(p) // Owned + }) + }); + let persist_at = persist_at_cow.as_ref().map(Cow::as_ref); + + match read_key(persist_at).await { + Ok(Some(result)) => return result, + Ok(None) => {} + Err(error) => { + error!("Error reading persisted dumbpipe key: [{:?}]", error); + } + } + + let key = SecretKey::generate(&mut rand::rng()); + if let Some(node_path) = persist_at { + if let Err(error) = write_key(node_path, &key).await { + error!("Could not persist dumbpipe key: {node_path:?}: {error:?}"); + } + } + key +} + +#[derive(Debug, Snafu)] +#[non_exhaustive] +pub enum PersistError { + #[snafu(transparent)] + IOError { source: std::io::Error }, + + FileError { + source: std::io::Error, + file: PathBuf, + }, + + #[snafu(transparent)] + KeyDecodeError { source: KeyDecodeErrorSource }, +} + +fn for_file(file: PathBuf) -> impl FnOnce(std::io::Error) -> PersistError { + return |e| PersistError::FileError { source: e, file }; +} + +#[derive(Debug, Snafu)] +#[non_exhaustive] +pub enum KeyDecodeErrorSource { + #[snafu(transparent)] + Hex { + source: FromHexError, + }, + + #[snafu(transparent)] + Parsing { + source: iroh::KeyParsingError, + }, + + InvalidSecretKeySize, +} + +impl From for PersistError { + fn from(source: FromHexError) -> Self { + PersistError::KeyDecodeError { + source: KeyDecodeErrorSource::Hex { source }, + } + } +} + +impl From for PersistError { + fn from(source: KeyParsingError) -> Self { + PersistError::KeyDecodeError { + source: KeyDecodeErrorSource::Parsing { source }, + } + } +} + +async fn read_key(key_path_option: Option<&Path>) -> Result, PersistError> { + if let Some(key_path) = key_path_option { + if !key_path.exists() { + debug!("Secret key not found: {:?}", &key_path); + return Ok(None); + } + let key_base64 = tokio::fs::read_to_string(key_path).await?; + let key_base64 = key_base64.trim(); + let key_bytes = hex::decode(key_base64)?; + if key_bytes.len() != 32 { + return Err(PersistError::KeyDecodeError { + source: KeyDecodeErrorSource::InvalidSecretKeySize, + }); + } + let key = SecretKey::try_from(&key_bytes[0..32]).map_err(KeyParsingError::from)?; + Ok(Some(key)) + } else { + Ok(None) + } +} + +async fn write_key(key_path: &Path, key: &SecretKey) -> Result<(), PersistError> { + let mut secret_hex = hex::encode(key.to_bytes()); + secret_hex.push('\n'); + let mut open_options = tokio::fs::OpenOptions::new(); + open_options.mode(0o400); // Read for owner only + create_file(open_options, key_path, &secret_hex).await?; + Ok(()) +} + +async fn create_file( + mut open_options: tokio::fs::OpenOptions, + file: &Path, + content: &str, +) -> Result<(), PersistError> { + let mut parent = file.to_owned(); + if parent.pop() { + fs::create_dir_all(parent.clone()) + .await + .map_err(for_file(parent))? + } + let mut open_file = open_options.create(true).write(true).open(file).await?; + open_file + .write_all(content.as_bytes()) + .await + .map_err(for_file(file.to_path_buf()))?; + Ok(()) +} + fn cancel_token(token: CancellationToken) -> impl Fn(T) -> T { move |x| { token.cancel(); From 90f335b1280ca47f0a478adc5f4a906a983cdd06 Mon Sep 17 00:00:00 2001 From: Jeroen van Maanen Date: Wed, 22 Oct 2025 19:34:40 +0000 Subject: [PATCH 02/12] Fixed Windows issues --- src/main.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index b81ff50..62dc1c5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ use std::{ borrow::Cow, io, net::{SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs}, + path::{Path, PathBuf}, str::FromStr, }; use tokio::{ @@ -20,10 +21,7 @@ use tokio_util::sync::CancellationToken; use tracing::log::{debug, error}; #[cfg(unix)] -use { - std::path::{Path, PathBuf}, - tokio::net::{UnixListener, UnixStream}, -}; +use tokio::net::{UnixListener, UnixStream}; /// Create a dumb pipe between two machines, using an iroh endpoint. /// @@ -423,7 +421,10 @@ async fn write_key(key_path: &Path, key: &SecretKey) -> Result<(), PersistError> let mut secret_hex = hex::encode(key.to_bytes()); secret_hex.push('\n'); let mut open_options = tokio::fs::OpenOptions::new(); + + #[cfg(unix)] open_options.mode(0o400); // Read for owner only + create_file(open_options, key_path, &secret_hex).await?; Ok(()) } From f5d51ebfe7ca666838b53591319ce20012ff313a Mon Sep 17 00:00:00 2001 From: Jeroen van Maanen Date: Wed, 22 Oct 2025 19:44:16 +0000 Subject: [PATCH 03/12] Fixed merge --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index b53e332..90e5d1f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ use clap::{Parser, Subcommand}; use dumbpipe::EndpointTicket; use hex::FromHexError; -use iroh::{endpoint::Connecting, Endpoint, KeyParsingError, NodeAddr, SecretKey}; +use iroh::{endpoint::Connecting, Endpoint, EndpointAddr, KeyParsingError, SecretKey}; use n0_snafu::{Result, ResultExt}; use snafu::Snafu; use std::{ From 68c0a5ba7af29446869053fc506df7473fbb6c0d Mon Sep 17 00:00:00 2001 From: Jeroen van Maanen Date: Wed, 22 Oct 2025 21:26:09 +0000 Subject: [PATCH 04/12] Use SSH format for key persistence. --- Cargo.lock | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + src/main.rs | 61 +++++++++++++++-------- 3 files changed, 177 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index da724dc..7660e4f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,6 +28,33 @@ dependencies = [ "inout", ] +[[package]] +name = "aes" +version = "0.9.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e713c57c2a2b19159e7be83b9194600d7e8eb3b7c2cd67e671adf47ce189a05" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", + "zeroize", +] + +[[package]] +name = "aes-gcm" +version = "0.11.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0686ba04dc80c816104c96cd7782b748f6ad58c5dd4ee619ff3258cf68e83d54" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", + "zeroize", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -568,6 +595,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ctr" +version = "0.10.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27e41d01c6f73b9330177f5cf782ae5b581b5f2c7840e298e0275ceee5001434" +dependencies = [ + "cipher", +] + [[package]] name = "curve25519-dalek" version = "5.0.0-pre.1" @@ -665,6 +701,15 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "des" +version = "0.9.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f51594a70805988feb1c85495ddec0c2052e4fbe59d9c0bb7f94bfc164f4f90" +dependencies = [ + "cipher", +] + [[package]] name = "diatomic-waker" version = "0.2.3" @@ -740,6 +785,7 @@ dependencies = [ "nix", "rand 0.9.2", "snafu", + "ssh-key", "tempfile", "tokio", "tokio-util", @@ -1009,6 +1055,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "ghash" +version = "0.6.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f88107cb02ed63adcc4282942e60c4d09d80208d33b360ce7c729ce6dae1739" +dependencies = [ + "polyval", +] + [[package]] name = "gimli" version = "0.31.1" @@ -1145,6 +1200,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "http" version = "0.2.12" @@ -1544,9 +1608,9 @@ dependencies = [ [[package]] name = "iroh-base" -version = "0.94.0" +version = "0.94.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db942f6f3d6fa9b475690c6e8e6684d60591dd886bf1bdfef4c60d89d502215c" +checksum = "7db6dfffe81a58daae02b72c7784c20feef5b5d3849b190ed1c96a8fa0b3cae8" dependencies = [ "curve25519-dalek", "data-encoding", @@ -2299,6 +2363,18 @@ checksum = "fb78a635f75d76d856374961deecf61031c0b6f928c83dc9c0924ab6c019c298" dependencies = [ "cpufeatures", "universal-hash", + "zeroize", +] + +[[package]] +name = "polyval" +version = "0.7.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffd40cc99d0fbb02b4b3771346b811df94194bc103983efa0203c8893755085" +dependencies = [ + "cfg-if", + "cpufeatures", + "universal-hash", ] [[package]] @@ -2782,6 +2858,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sec1" +version = "0.8.0-rc.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dff52f6118bc9f0ac974a54a639d499ac26a6cad7a6e39bc0990c19625e793b" +dependencies = [ + "base16ct", + "hybrid-array", +] + [[package]] name = "security-framework" version = "3.2.0" @@ -3080,6 +3166,53 @@ dependencies = [ "der", ] +[[package]] +name = "ssh-cipher" +version = "0.3.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "481f53252058ad302f9dff47a3ca03c5e30e34e49226d9549a7e9d16cb210700" +dependencies = [ + "aes", + "aes-gcm", + "chacha20", + "cipher", + "des", + "poly1305", + "ssh-encoding", + "zeroize", +] + +[[package]] +name = "ssh-encoding" +version = "0.3.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f1447aab1592c131dec60f7d8cc0b2fb4042d0bf2c90c40f972c2c046b25d1b" +dependencies = [ + "base64ct", + "digest", + "pem-rfc7468", + "subtle", + "zeroize", +] + +[[package]] +name = "ssh-key" +version = "0.7.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7307406fcbbeb6933b5c8cc84ec0fefee80fec53ba5b88b96674c0a75495090a" +dependencies = [ + "ed25519-dalek", + "home", + "rand_core 0.9.3", + "sec1", + "sha2", + "signature", + "ssh-cipher", + "ssh-encoding", + "subtle", + "zeroize", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" diff --git a/Cargo.toml b/Cargo.toml index 2959380..64c624a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ iroh = { version = "0.94", default-features = false } iroh-tickets= { version = "0.1" } quinn = { version = "0.14", package = "iroh-quinn" } rand = "0.9.2" +ssh-key = { version = "0.7.0-rc.3", features = ["ed25519"] } tokio = { version = "1.34.0", features = ["full"] } tokio-util = "0.7.10" tracing = "0.1.40" diff --git a/src/main.rs b/src/main.rs index 90e5d1f..39a66f7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,10 @@ //! Command line arguments. use clap::{Parser, Subcommand}; use dumbpipe::EndpointTicket; -use hex::FromHexError; use iroh::{endpoint::Connecting, Endpoint, EndpointAddr, KeyParsingError, SecretKey}; use n0_snafu::{Result, ResultExt}; use snafu::Snafu; +use ssh_key::Algorithm; use std::{ borrow::Cow, io, @@ -356,6 +356,9 @@ pub enum PersistError { file: PathBuf, }, + #[snafu(transparent)] + KeyEncodeError { source: ssh_key::Error }, + #[snafu(transparent)] KeyDecodeError { source: KeyDecodeErrorSource }, } @@ -368,30 +371,35 @@ fn for_file(file: PathBuf) -> impl FnOnce(std::io::Error) -> PersistError { #[non_exhaustive] pub enum KeyDecodeErrorSource { #[snafu(transparent)] - Hex { - source: FromHexError, + SshParsing { + source: ssh_key::Error, }, #[snafu(transparent)] - Parsing { + IrohParsing { source: iroh::KeyParsingError, }, - InvalidSecretKeySize, + InvalidKeyType { + algorithm: Option, + }, } -impl From for PersistError { - fn from(source: FromHexError) -> Self { +impl PersistError { + fn ssh_parsing_error(source: ssh_key::Error) -> Self { PersistError::KeyDecodeError { - source: KeyDecodeErrorSource::Hex { source }, + source: KeyDecodeErrorSource::SshParsing { source }, } } + fn ssh_serializing_error(source: ssh_key::Error) -> Self { + PersistError::KeyEncodeError { source } + } } impl From for PersistError { fn from(source: KeyParsingError) -> Self { PersistError::KeyDecodeError { - source: KeyDecodeErrorSource::Parsing { source }, + source: KeyDecodeErrorSource::IrohParsing { source }, } } } @@ -402,30 +410,43 @@ async fn read_key(key_path_option: Option<&Path>) -> Result, P debug!("Secret key not found: {:?}", &key_path); return Ok(None); } - let key_base64 = tokio::fs::read_to_string(key_path).await?; - let key_base64 = key_base64.trim(); - let key_bytes = hex::decode(key_base64)?; - if key_bytes.len() != 32 { + let keystr = tokio::fs::read_to_string(key_path).await?; + let ser_key = ssh_key::private::PrivateKey::from_openssh(keystr) + .map_err(PersistError::ssh_parsing_error)?; + let ssh_key::private::KeypairData::Ed25519(kp) = ser_key.key_data() else { + let algorithm = ser_key.key_data().algorithm().ok(); + let algorithm_name = algorithm + .as_ref() + .map(Algorithm::as_str) + .map(ToString::to_string); return Err(PersistError::KeyDecodeError { - source: KeyDecodeErrorSource::InvalidSecretKeySize, + source: KeyDecodeErrorSource::InvalidKeyType { + algorithm: algorithm_name, + }, }); - } - let key = SecretKey::try_from(&key_bytes[0..32]).map_err(KeyParsingError::from)?; + }; + let key = SecretKey::from_bytes(&kp.private.to_bytes()); Ok(Some(key)) } else { Ok(None) } } -async fn write_key(key_path: &Path, key: &SecretKey) -> Result<(), PersistError> { - let mut secret_hex = hex::encode(key.to_bytes()); - secret_hex.push('\n'); +async fn write_key(key_path: &Path, secret_key: &SecretKey) -> Result<(), PersistError> { + let ckey = ssh_key::private::Ed25519Keypair { + public: secret_key.public().as_verifying_key().into(), + private: secret_key.as_signing_key().into(), + }; + let ser_key = ssh_key::private::PrivateKey::from(ckey) + .to_openssh(ssh_key::LineEnding::default()) + .map_err(PersistError::ssh_serializing_error)?; + let mut open_options = tokio::fs::OpenOptions::new(); #[cfg(unix)] open_options.mode(0o400); // Read for owner only - create_file(open_options, key_path, &secret_hex).await?; + create_file(open_options, key_path, ser_key.as_str()).await?; Ok(()) } From 209a8b80e307d4805735a5124b805c7566f0fb30 Mon Sep 17 00:00:00 2001 From: Jeroen van Maanen Date: Thu, 23 Oct 2025 15:09:33 +0000 Subject: [PATCH 05/12] Addressed review comments. --- Cargo.lock | 49 ++++++++++++++++++++ Cargo.toml | 1 + src/main.rs | 126 +++++++++++++++------------------------------------- 3 files changed, 86 insertions(+), 90 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7660e4f..cc7fa05 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -727,6 +727,27 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.60.2", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -776,6 +797,7 @@ version = "0.32.0" dependencies = [ "clap", "data-encoding", + "dirs", "duct", "hex", "iroh", @@ -1829,6 +1851,16 @@ version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +[[package]] +name = "libredox" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +dependencies = [ + "bitflags", + "libc", +] + [[package]] name = "linux-raw-sys" version = "0.9.4" @@ -2212,6 +2244,12 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "os_pipe" version = "1.2.2" @@ -2619,6 +2657,17 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 2.0.12", +] + [[package]] name = "regex-automata" version = "0.4.9" diff --git a/Cargo.toml b/Cargo.toml index 64c624a..461cf46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ rust-version = "1.81" [dependencies] snafu = "0.8.6" clap = { version = "4.4.10", features = ["derive"] } +dirs = "6.0.0" hex = "0.4.3" iroh = { version = "0.94", default-features = false } iroh-tickets= { version = "0.1" } diff --git a/src/main.rs b/src/main.rs index 39a66f7..70b88b3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,8 @@ //! Command line arguments. use clap::{Parser, Subcommand}; use dumbpipe::EndpointTicket; -use iroh::{endpoint::Connecting, Endpoint, EndpointAddr, KeyParsingError, SecretKey}; -use n0_snafu::{Result, ResultExt}; -use snafu::Snafu; +use iroh::{endpoint::Connecting, Endpoint, EndpointAddr, SecretKey}; +use n0_snafu::{format_err, Result, ResultExt}; use ssh_key::Algorithm; use std::{ borrow::Cow, @@ -13,7 +12,7 @@ use std::{ str::FromStr, }; use tokio::{ - fs, + fs::{self, OpenOptions}, io::{AsyncRead, AsyncWrite, AsyncWriteExt}, select, }; @@ -35,6 +34,11 @@ use tokio::net::{UnixListener, UnixStream}; /// For all subcommands, you can specify a secret key using the IROH_SECRET /// environment variable. If you don't, a random one will be generated. /// +/// For all subcommands, you can specify that the generated key should be +/// persisted at either a default location or a explicitly given file. In that +/// case the key will be read from that location on subsequent runs that specify +/// the same location. If you don't, a fresh key will be generated each time. +/// /// You can also specify a port for the endpoint. If you don't, a random one /// will be chosen. #[derive(Parser, Debug)] @@ -317,22 +321,16 @@ async fn create_endpoint( async fn get_secret_key(persist_at: Option<&PathBuf>) -> SecretKey { let persist_at_cow = persist_at - .map(Cow::from) // Reference - .or_else(|| { - std::env::home_dir().map(|mut p| { - p.push(".auth"); - p.push("dumbpipe.key"); - debug!("Persisting key at: {p:?}"); - Cow::from(p) // Owned - }) - }); - let persist_at = persist_at_cow.as_ref().map(Cow::as_ref); + .map(Cow::from) // Borrowed Cow + .or_else(|| default_persist_at().map(Cow::from)); // Owned Cow + let mut persist_at = persist_at_cow.as_ref().map(Cow::as_ref); match read_key(persist_at).await { Ok(Some(result)) => return result, Ok(None) => {} Err(error) => { error!("Error reading persisted dumbpipe key: [{:?}]", error); + persist_at = None; // Don't overwrite the key that we couln't read } } @@ -345,85 +343,35 @@ async fn get_secret_key(persist_at: Option<&PathBuf>) -> SecretKey { key } -#[derive(Debug, Snafu)] -#[non_exhaustive] -pub enum PersistError { - #[snafu(transparent)] - IOError { source: std::io::Error }, - - FileError { - source: std::io::Error, - file: PathBuf, - }, - - #[snafu(transparent)] - KeyEncodeError { source: ssh_key::Error }, - - #[snafu(transparent)] - KeyDecodeError { source: KeyDecodeErrorSource }, -} - -fn for_file(file: PathBuf) -> impl FnOnce(std::io::Error) -> PersistError { - return |e| PersistError::FileError { source: e, file }; -} - -#[derive(Debug, Snafu)] -#[non_exhaustive] -pub enum KeyDecodeErrorSource { - #[snafu(transparent)] - SshParsing { - source: ssh_key::Error, - }, - - #[snafu(transparent)] - IrohParsing { - source: iroh::KeyParsingError, - }, - - InvalidKeyType { - algorithm: Option, - }, -} - -impl PersistError { - fn ssh_parsing_error(source: ssh_key::Error) -> Self { - PersistError::KeyDecodeError { - source: KeyDecodeErrorSource::SshParsing { source }, - } - } - fn ssh_serializing_error(source: ssh_key::Error) -> Self { - PersistError::KeyEncodeError { source } - } -} - -impl From for PersistError { - fn from(source: KeyParsingError) -> Self { - PersistError::KeyDecodeError { - source: KeyDecodeErrorSource::IrohParsing { source }, - } - } +fn default_persist_at() -> Option { + dirs::config_dir().map(|mut p| { + p.push("dumbpipe"); + p.push("dumbpipe.key"); + debug!("Persisting key at: {p:?}"); + p + }) } -async fn read_key(key_path_option: Option<&Path>) -> Result, PersistError> { +async fn read_key(key_path_option: Option<&Path>) -> Result> { if let Some(key_path) = key_path_option { if !key_path.exists() { debug!("Secret key not found: {:?}", &key_path); return Ok(None); } - let keystr = tokio::fs::read_to_string(key_path).await?; + let keystr = tokio::fs::read_to_string(key_path) + .await + .map_err(|e| format_err!("Read key error: {key_path:?}: {e:?}"))?; let ser_key = ssh_key::private::PrivateKey::from_openssh(keystr) - .map_err(PersistError::ssh_parsing_error)?; + .map_err(|e| format_err!("Parse key error: {key_path:?}: {e:?}"))?; let ssh_key::private::KeypairData::Ed25519(kp) = ser_key.key_data() else { let algorithm = ser_key.key_data().algorithm().ok(); let algorithm_name = algorithm .as_ref() .map(Algorithm::as_str) .map(ToString::to_string); - return Err(PersistError::KeyDecodeError { - source: KeyDecodeErrorSource::InvalidKeyType { - algorithm: algorithm_name, - }, - }); + return Err(format_err!( + "Invalid key type: {key_path:?}: {algorithm_name:?}" + )); }; let key = SecretKey::from_bytes(&kp.private.to_bytes()); Ok(Some(key)) @@ -432,16 +380,16 @@ async fn read_key(key_path_option: Option<&Path>) -> Result, P } } -async fn write_key(key_path: &Path, secret_key: &SecretKey) -> Result<(), PersistError> { +async fn write_key(key_path: &Path, secret_key: &SecretKey) -> Result<()> { let ckey = ssh_key::private::Ed25519Keypair { public: secret_key.public().as_verifying_key().into(), private: secret_key.as_signing_key().into(), }; let ser_key = ssh_key::private::PrivateKey::from(ckey) .to_openssh(ssh_key::LineEnding::default()) - .map_err(PersistError::ssh_serializing_error)?; + .map_err(|e| format_err!("Error serializing SSH key: {e:?}"))?; - let mut open_options = tokio::fs::OpenOptions::new(); + let mut open_options = OpenOptions::new(); #[cfg(unix)] open_options.mode(0o400); // Read for owner only @@ -450,22 +398,20 @@ async fn write_key(key_path: &Path, secret_key: &SecretKey) -> Result<(), Persis Ok(()) } -async fn create_file( - mut open_options: tokio::fs::OpenOptions, - file: &Path, - content: &str, -) -> Result<(), PersistError> { +async fn create_file(mut open_options: OpenOptions, file: &Path, content: &str) -> Result { let mut parent = file.to_owned(); if parent.pop() { fs::create_dir_all(parent.clone()) .await - .map_err(for_file(parent))? + .map_err(|e| format_err!("Error creating directory {parent:?}: {e:?}"))? } - let mut open_file = open_options.create(true).write(true).open(file).await?; + let mut open_file = (open_options.create(true).write(true).open(file)) + .await + .map_err(|e| format_err!("Error creating key file: {file:?}: {e:?}"))?; open_file .write_all(content.as_bytes()) .await - .map_err(for_file(file.to_path_buf()))?; + .map_err(|e| format_err!("Error writing key file: {file:?}: {e:?}"))?; Ok(()) } From 8493270c79c184775016eb5ea83b843fa4068b5f Mon Sep 17 00:00:00 2001 From: Jeroen van Maanen Date: Thu, 23 Oct 2025 15:34:11 +0000 Subject: [PATCH 06/12] Fixed more Windows issues --- src/main.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/main.rs b/src/main.rs index 70b88b3..1cd55c0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -389,29 +389,30 @@ async fn write_key(key_path: &Path, secret_key: &SecretKey) -> Result<()> { .to_openssh(ssh_key::LineEnding::default()) .map_err(|e| format_err!("Error serializing SSH key: {e:?}"))?; - let mut open_options = OpenOptions::new(); - - #[cfg(unix)] - open_options.mode(0o400); // Read for owner only - - create_file(open_options, key_path, ser_key.as_str()).await?; + create_secret_file(key_path, ser_key.as_str()).await?; Ok(()) } -async fn create_file(mut open_options: OpenOptions, file: &Path, content: &str) -> Result { +async fn create_secret_file(file: &Path, content: &str) -> Result { let mut parent = file.to_owned(); if parent.pop() { fs::create_dir_all(parent.clone()) .await .map_err(|e| format_err!("Error creating directory {parent:?}: {e:?}"))? } + + let mut open_options = OpenOptions::new(); + + #[cfg(unix)] + open_options.mode(0o400); // Read for owner only + let mut open_file = (open_options.create(true).write(true).open(file)) .await - .map_err(|e| format_err!("Error creating key file: {file:?}: {e:?}"))?; + .map_err(|e| format_err!("Error creating secret-file: {file:?}: {e:?}"))?; open_file .write_all(content.as_bytes()) .await - .map_err(|e| format_err!("Error writing key file: {file:?}: {e:?}"))?; + .map_err(|e| format_err!("Error writing secret-file: {file:?}: {e:?}"))?; Ok(()) } From bd7b6d20073e6fa7f444fd7327cfed6fa585799a Mon Sep 17 00:00:00 2001 From: Jeroen van Maanen Date: Thu, 30 Oct 2025 19:39:01 +0000 Subject: [PATCH 07/12] Replaced content of PR by crate `iroh-persist` --- Cargo.lock | 61 ++++++++++++++++++++++ Cargo.toml | 3 +- src/main.rs | 144 +++++++--------------------------------------------- 3 files changed, 80 insertions(+), 128 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cc7fa05..29d8a00 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -801,6 +801,7 @@ dependencies = [ "duct", "hex", "iroh", + "iroh-persist", "iroh-quinn", "iroh-tickets", "n0-snafu", @@ -872,6 +873,27 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "env_filter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +dependencies = [ + "log", +] + +[[package]] +name = "env_logger" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "log", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -1675,6 +1697,23 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "iroh-persist" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a54861989979e6bd113d0cd9ea5b5b65b1f65436de4da827ab30f4571e0411d9" +dependencies = [ + "dirs", + "iroh", + "n0-snafu", + "rand 0.9.2", + "snafu", + "ssh-key", + "test-log", + "tokio", + "tracing", +] + [[package]] name = "iroh-quinn" version = "0.14.0" @@ -3392,6 +3431,28 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "test-log" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e33b98a582ea0be1168eba097538ee8dd4bbe0f2b01b22ac92ea30054e5be7b" +dependencies = [ + "env_logger", + "test-log-macros", + "tracing-subscriber", +] + +[[package]] +name = "test-log-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451b374529930d7601b1eef8d32bc79ae870b6079b069401709c2a8bf9e75f36" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "thiserror" version = "1.0.69" diff --git a/Cargo.toml b/Cargo.toml index 461cf46..3dfcddd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,8 @@ clap = { version = "4.4.10", features = ["derive"] } dirs = "6.0.0" hex = "0.4.3" iroh = { version = "0.94", default-features = false } -iroh-tickets= { version = "0.1" } +iroh-tickets = { version = "0.1" } +iroh-persist = "0.1.0" quinn = { version = "0.14", package = "iroh-quinn" } rand = "0.9.2" ssh-key = { version = "0.7.0-rc.3", features = ["ed25519"] } diff --git a/src/main.rs b/src/main.rs index 1cd55c0..e42155a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,22 +2,18 @@ use clap::{Parser, Subcommand}; use dumbpipe::EndpointTicket; use iroh::{endpoint::Connecting, Endpoint, EndpointAddr, SecretKey}; -use n0_snafu::{format_err, Result, ResultExt}; -use ssh_key::Algorithm; +use iroh_persist::KeyRetriever; +use n0_snafu::{Result, ResultExt}; use std::{ - borrow::Cow, io, net::{SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs}, - path::{Path, PathBuf}, - str::FromStr, + path::PathBuf, }; use tokio::{ - fs::{self, OpenOptions}, io::{AsyncRead, AsyncWrite, AsyncWriteExt}, select, }; use tokio_util::sync::CancellationToken; -use tracing::log::{debug, error}; #[cfg(unix)] use tokio::net::{UnixListener, UnixStream}; @@ -283,20 +279,13 @@ async fn copy_from_quinn( } /// Get the secret key or generate a new one. -/// -/// Print the secret key to stderr if it was generated, so the user can save it. -fn get_or_create_secret() -> Result { - match std::env::var("IROH_SECRET") { - Ok(secret) => SecretKey::from_str(&secret).context("invalid secret"), - Err(_) => { - let key = SecretKey::generate(&mut rand::rng()); - eprintln!( - "using secret key {}", - data_encoding::HEXLOWER.encode(&key.to_bytes()) - ); - Ok(key) - } - } +async fn get_or_create_secret(common: &CommonArgs) -> SecretKey { + KeyRetriever::new("dumbpipe") + .persist(common.persist) + .persist_at(common.persist_at.as_ref()) + .lenient() + .get() + .await } /// Create a new iroh endpoint. @@ -312,110 +301,11 @@ async fn create_endpoint( if let Some(addr) = common.ipv6_addr { builder = builder.bind_addr_v6(addr); } - if common.persist || common.persist_at.is_some() { - builder = builder.secret_key(get_secret_key(common.persist_at.as_ref()).await); - } + let endpoint = builder.bind().await?; Ok(endpoint) } -async fn get_secret_key(persist_at: Option<&PathBuf>) -> SecretKey { - let persist_at_cow = persist_at - .map(Cow::from) // Borrowed Cow - .or_else(|| default_persist_at().map(Cow::from)); // Owned Cow - let mut persist_at = persist_at_cow.as_ref().map(Cow::as_ref); - - match read_key(persist_at).await { - Ok(Some(result)) => return result, - Ok(None) => {} - Err(error) => { - error!("Error reading persisted dumbpipe key: [{:?}]", error); - persist_at = None; // Don't overwrite the key that we couln't read - } - } - - let key = SecretKey::generate(&mut rand::rng()); - if let Some(node_path) = persist_at { - if let Err(error) = write_key(node_path, &key).await { - error!("Could not persist dumbpipe key: {node_path:?}: {error:?}"); - } - } - key -} - -fn default_persist_at() -> Option { - dirs::config_dir().map(|mut p| { - p.push("dumbpipe"); - p.push("dumbpipe.key"); - debug!("Persisting key at: {p:?}"); - p - }) -} - -async fn read_key(key_path_option: Option<&Path>) -> Result> { - if let Some(key_path) = key_path_option { - if !key_path.exists() { - debug!("Secret key not found: {:?}", &key_path); - return Ok(None); - } - let keystr = tokio::fs::read_to_string(key_path) - .await - .map_err(|e| format_err!("Read key error: {key_path:?}: {e:?}"))?; - let ser_key = ssh_key::private::PrivateKey::from_openssh(keystr) - .map_err(|e| format_err!("Parse key error: {key_path:?}: {e:?}"))?; - let ssh_key::private::KeypairData::Ed25519(kp) = ser_key.key_data() else { - let algorithm = ser_key.key_data().algorithm().ok(); - let algorithm_name = algorithm - .as_ref() - .map(Algorithm::as_str) - .map(ToString::to_string); - return Err(format_err!( - "Invalid key type: {key_path:?}: {algorithm_name:?}" - )); - }; - let key = SecretKey::from_bytes(&kp.private.to_bytes()); - Ok(Some(key)) - } else { - Ok(None) - } -} - -async fn write_key(key_path: &Path, secret_key: &SecretKey) -> Result<()> { - let ckey = ssh_key::private::Ed25519Keypair { - public: secret_key.public().as_verifying_key().into(), - private: secret_key.as_signing_key().into(), - }; - let ser_key = ssh_key::private::PrivateKey::from(ckey) - .to_openssh(ssh_key::LineEnding::default()) - .map_err(|e| format_err!("Error serializing SSH key: {e:?}"))?; - - create_secret_file(key_path, ser_key.as_str()).await?; - Ok(()) -} - -async fn create_secret_file(file: &Path, content: &str) -> Result { - let mut parent = file.to_owned(); - if parent.pop() { - fs::create_dir_all(parent.clone()) - .await - .map_err(|e| format_err!("Error creating directory {parent:?}: {e:?}"))? - } - - let mut open_options = OpenOptions::new(); - - #[cfg(unix)] - open_options.mode(0o400); // Read for owner only - - let mut open_file = (open_options.create(true).write(true).open(file)) - .await - .map_err(|e| format_err!("Error creating secret-file: {file:?}: {e:?}"))?; - open_file - .write_all(content.as_bytes()) - .await - .map_err(|e| format_err!("Error writing secret-file: {file:?}: {e:?}"))?; - Ok(()) -} - fn cancel_token(token: CancellationToken) -> impl Fn(T) -> T { move |x| { token.cancel(); @@ -456,7 +346,7 @@ async fn forward_bidi( } async fn listen_stdio(args: ListenArgs) -> Result<()> { - let secret_key = get_or_create_secret()?; + let secret_key = get_or_create_secret(&args.common).await; let endpoint = create_endpoint(secret_key, &args.common, vec![args.common.alpn()?]).await?; // wait for the endpoint to figure out its home relay and addresses before making a ticket endpoint.online().await; @@ -518,7 +408,7 @@ async fn listen_stdio(args: ListenArgs) -> Result<()> { } async fn connect_stdio(args: ConnectArgs) -> Result<()> { - let secret_key = get_or_create_secret()?; + let secret_key = get_or_create_secret(&args.common).await; let endpoint = create_endpoint(secret_key, &args.common, vec![]).await?; let addr = args.ticket.endpoint_addr(); let remote_endpoint_id = addr.id; @@ -555,7 +445,7 @@ async fn connect_tcp(args: ConnectTcpArgs) -> Result<()> { .addr .to_socket_addrs() .context(format!("invalid host string {}", args.addr))?; - let secret_key = get_or_create_secret()?; + let secret_key = get_or_create_secret(&args.common).await; let endpoint = create_endpoint(secret_key, &args.common, vec![]) .await .context("unable to bind endpoint")?; @@ -632,7 +522,7 @@ async fn listen_tcp(args: ListenTcpArgs) -> Result<()> { Ok(addrs) => addrs.collect::>(), Err(e) => snafu::whatever!("invalid host string {}: {}", args.host, e), }; - let secret_key = get_or_create_secret()?; + let secret_key = get_or_create_secret(&args.common).await; let endpoint = create_endpoint(secret_key, &args.common, vec![args.common.alpn()?]).await?; // wait for the endpoint to figure out its address before making a ticket endpoint.online().await; @@ -728,7 +618,7 @@ fn create_short_ticket(addr: &EndpointAddr) -> EndpointTicket { /// Listen on an endpoint and forward incoming connections to a Unix socket. async fn listen_unix(args: ListenUnixArgs) -> Result<()> { let socket_path = args.socket_path.clone(); - let secret_key = get_or_create_secret()?; + let secret_key = get_or_create_secret(&args.common).await; let endpoint = create_endpoint(secret_key, &args.common, vec![args.common.alpn()?]).await?; // wait for the endpoint to figure out its address before making a ticket endpoint.online().await; @@ -844,7 +734,7 @@ impl Drop for UnixSocketGuard { /// Listen on a Unix socket and forward connections to an endpoint. async fn connect_unix(args: ConnectUnixArgs) -> Result<()> { let socket_path = args.socket_path.clone(); - let secret_key = get_or_create_secret()?; + let secret_key = get_or_create_secret(&args.common).await; let endpoint = create_endpoint(secret_key, &args.common, vec![]) .await .context("unable to bind endpoint")?; From 6be34ad1c7b44e382cd1abeec3b051791ceb053f Mon Sep 17 00:00:00 2001 From: Jeroen van Maanen Date: Mon, 3 Nov 2025 16:17:46 +0000 Subject: [PATCH 08/12] Removed unused dependency on crate `ssh-key` --- Cargo.lock | 1 - Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 29d8a00..315664a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -808,7 +808,6 @@ dependencies = [ "nix", "rand 0.9.2", "snafu", - "ssh-key", "tempfile", "tokio", "tokio-util", diff --git a/Cargo.toml b/Cargo.toml index 3dfcddd..e75b2bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,6 @@ iroh-tickets = { version = "0.1" } iroh-persist = "0.1.0" quinn = { version = "0.14", package = "iroh-quinn" } rand = "0.9.2" -ssh-key = { version = "0.7.0-rc.3", features = ["ed25519"] } tokio = { version = "1.34.0", features = ["full"] } tokio-util = "0.7.10" tracing = "0.1.40" From bc53f8599e895b70d220c6ae8e789c4dd74844af Mon Sep 17 00:00:00 2001 From: Jeroen van Maanen Date: Tue, 4 Nov 2025 21:46:22 +0000 Subject: [PATCH 09/12] Bumped iroh-persist to version `0.1.1` --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 315664a..31ffdba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1698,9 +1698,9 @@ dependencies = [ [[package]] name = "iroh-persist" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a54861989979e6bd113d0cd9ea5b5b65b1f65436de4da827ab30f4571e0411d9" +checksum = "eb003f3a9238687b794d80d4c01cf912cf909f6060b3017364b1b2cd6bec7db9" dependencies = [ "dirs", "iroh", diff --git a/Cargo.toml b/Cargo.toml index e75b2bf..4a7c201 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ dirs = "6.0.0" hex = "0.4.3" iroh = { version = "0.94", default-features = false } iroh-tickets = { version = "0.1" } -iroh-persist = "0.1.0" +iroh-persist = "0.1.1" quinn = { version = "0.14", package = "iroh-quinn" } rand = "0.9.2" tokio = { version = "1.34.0", features = ["full"] } From 1d4a63d7249f684ad53cdf1251719b7a91e6e90c Mon Sep 17 00:00:00 2001 From: Jeroen van Maanen Date: Tue, 4 Nov 2025 22:14:37 +0000 Subject: [PATCH 10/12] Fail on parse errors and write errors rather than falling back to using an ephemeral key --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- src/main.rs | 16 ++++++++-------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 31ffdba..c2a997d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1698,9 +1698,9 @@ dependencies = [ [[package]] name = "iroh-persist" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb003f3a9238687b794d80d4c01cf912cf909f6060b3017364b1b2cd6bec7db9" +checksum = "4fe49e858332fe7f5fe3e5bceec3a6645a1df64d31bf5dd996f857a51b3f388e" dependencies = [ "dirs", "iroh", diff --git a/Cargo.toml b/Cargo.toml index 4a7c201..5144b34 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ dirs = "6.0.0" hex = "0.4.3" iroh = { version = "0.94", default-features = false } iroh-tickets = { version = "0.1" } -iroh-persist = "0.1.1" +iroh-persist = "0.1.2" quinn = { version = "0.14", package = "iroh-quinn" } rand = "0.9.2" tokio = { version = "1.34.0", features = ["full"] } diff --git a/src/main.rs b/src/main.rs index e42155a..17bb51d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -279,13 +279,13 @@ async fn copy_from_quinn( } /// Get the secret key or generate a new one. -async fn get_or_create_secret(common: &CommonArgs) -> SecretKey { +async fn get_or_create_secret(common: &CommonArgs) -> Result { KeyRetriever::new("dumbpipe") .persist(common.persist) .persist_at(common.persist_at.as_ref()) - .lenient() .get() .await + .map_err(n0_snafu::Error::from) } /// Create a new iroh endpoint. @@ -346,7 +346,7 @@ async fn forward_bidi( } async fn listen_stdio(args: ListenArgs) -> Result<()> { - let secret_key = get_or_create_secret(&args.common).await; + let secret_key = get_or_create_secret(&args.common).await?; let endpoint = create_endpoint(secret_key, &args.common, vec![args.common.alpn()?]).await?; // wait for the endpoint to figure out its home relay and addresses before making a ticket endpoint.online().await; @@ -408,7 +408,7 @@ async fn listen_stdio(args: ListenArgs) -> Result<()> { } async fn connect_stdio(args: ConnectArgs) -> Result<()> { - let secret_key = get_or_create_secret(&args.common).await; + let secret_key = get_or_create_secret(&args.common).await?; let endpoint = create_endpoint(secret_key, &args.common, vec![]).await?; let addr = args.ticket.endpoint_addr(); let remote_endpoint_id = addr.id; @@ -445,7 +445,7 @@ async fn connect_tcp(args: ConnectTcpArgs) -> Result<()> { .addr .to_socket_addrs() .context(format!("invalid host string {}", args.addr))?; - let secret_key = get_or_create_secret(&args.common).await; + let secret_key = get_or_create_secret(&args.common).await?; let endpoint = create_endpoint(secret_key, &args.common, vec![]) .await .context("unable to bind endpoint")?; @@ -522,7 +522,7 @@ async fn listen_tcp(args: ListenTcpArgs) -> Result<()> { Ok(addrs) => addrs.collect::>(), Err(e) => snafu::whatever!("invalid host string {}: {}", args.host, e), }; - let secret_key = get_or_create_secret(&args.common).await; + let secret_key = get_or_create_secret(&args.common).await?; let endpoint = create_endpoint(secret_key, &args.common, vec![args.common.alpn()?]).await?; // wait for the endpoint to figure out its address before making a ticket endpoint.online().await; @@ -618,7 +618,7 @@ fn create_short_ticket(addr: &EndpointAddr) -> EndpointTicket { /// Listen on an endpoint and forward incoming connections to a Unix socket. async fn listen_unix(args: ListenUnixArgs) -> Result<()> { let socket_path = args.socket_path.clone(); - let secret_key = get_or_create_secret(&args.common).await; + let secret_key = get_or_create_secret(&args.common).await?; let endpoint = create_endpoint(secret_key, &args.common, vec![args.common.alpn()?]).await?; // wait for the endpoint to figure out its address before making a ticket endpoint.online().await; @@ -734,7 +734,7 @@ impl Drop for UnixSocketGuard { /// Listen on a Unix socket and forward connections to an endpoint. async fn connect_unix(args: ConnectUnixArgs) -> Result<()> { let socket_path = args.socket_path.clone(); - let secret_key = get_or_create_secret(&args.common).await; + let secret_key = get_or_create_secret(&args.common).await?; let endpoint = create_endpoint(secret_key, &args.common, vec![]) .await .context("unable to bind endpoint")?; From 05b3ef9f544dbd7a247c00ea4ef5eb2bf1cfba53 Mon Sep 17 00:00:00 2001 From: Jeroen van Maanen Date: Fri, 14 Nov 2025 18:41:53 +0000 Subject: [PATCH 11/12] Bumped `iroh-persist` to 0.1.5 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9efcde3..d34036f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1667,9 +1667,9 @@ dependencies = [ [[package]] name = "iroh-persist" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee41ab77c7e2eb7fd0599ce4beae95f6f502d558cefb70d64d9bef640703d8a6" +checksum = "29bef6a41f4362636668d8528728ce7989473277368e04d2ed9e047265a544d2" dependencies = [ "dirs", "iroh", diff --git a/Cargo.toml b/Cargo.toml index 79efc39..cefc06e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ dirs = "6.0.0" hex = "0.4.3" iroh = { version = "0.95", default-features = false } iroh-tickets = { version = "0.2" } -iroh-persist = "0.1.4" +iroh-persist = "0.1.5" quinn = { version = "0.14", package = "iroh-quinn" } rand = "0.9.2" tokio = { version = "1.34.0", features = ["full"] } From a21f6b9fa11944d2a73e14dc588cb68ec56e3bbf Mon Sep 17 00:00:00 2001 From: Jeroen van Maanen Date: Fri, 14 Nov 2025 19:00:21 +0000 Subject: [PATCH 12/12] Removed unnecessary dependency on `dirs:6.0.0` --- Cargo.lock | 1 - Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d34036f..1da0584 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -771,7 +771,6 @@ version = "0.33.0" dependencies = [ "clap", "data-encoding", - "dirs", "duct", "hex", "iroh", diff --git a/Cargo.toml b/Cargo.toml index cefc06e..13697b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,6 @@ rust-version = "1.81" [dependencies] clap = { version = "4.4.10", features = ["derive"] } -dirs = "6.0.0" hex = "0.4.3" iroh = { version = "0.95", default-features = false } iroh-tickets = { version = "0.2" }