Skip to content

Commit 4a774f1

Browse files
refactor(iroh-base)!: reduce dependencies (#3046)
## Description - ensure `zeroize` is used for `SecretKey` - remove unused dependencies and features from `iroh-base` ## Breaking Changes - `iroh_base::SecretKey::generate` now takes an rng - removed `iroh_base::SecretKey::generate_with_rng`, use `generate` directly - removed `iroh_base::SecretKey::to_openssh` - removed `iroh_base::SecretKey::from_openssh` - `anyhow::Error` is replaced with explicit errors for `RelayUrl::from_str` - `anyhow::Error` is replaced with explicit errors for `SharedSecret::open` ## Notes & open questions As a follow up, all usage of `SecretKey::generate` in tests should be changed to use a seeded rng, like chacha8, instead of `thread_rng` ## Change checklist - [ ] Self-review. - [ ] Documentation updates following the [style guide](https://rust-lang.github.io/rfcs/1574-more-api-documentation-conventions.html#appendix-a-full-conventions-text), if relevant. - [ ] Tests if relevant. - [ ] All breaking changes documented.
1 parent 8ec0d73 commit 4a774f1

39 files changed

+186
-441
lines changed

Cargo.lock

Lines changed: 2 additions & 257 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

iroh-base/Cargo.toml

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,48 +15,39 @@ rust-version = "1.81"
1515
workspace = true
1616

1717
[dependencies]
18-
anyhow = { version = "1", optional = true }
18+
aead = { version = "0.5.2", features = ["bytes"], optional = true }
19+
crypto_box = { version = "0.9.1", features = ["serde", "chacha20"], optional = true }
1920
data-encoding = { version = "2.3.3", optional = true }
21+
ed25519-dalek = { version = "2.0.0", features = ["serde", "rand_core", "zeroize"], optional = true }
22+
derive_more = { version = "1.0.0", features = ["display"], optional = true }
23+
url = { version = "2.5", features = ["serde"], optional = true }
2024
postcard = { version = "1", default-features = false, features = ["alloc", "use-std", "experimental-derive"], optional = true }
25+
rand_core = { version = "0.6.4", optional = true }
2126
serde = { version = "1", features = ["derive"] }
2227
thiserror = { version = "2", optional = true }
23-
24-
# key module
25-
aead = { version = "0.5.2", features = ["bytes"], optional = true }
26-
derive_more = { version = "1.0.0", features = ["debug", "display", "from_str"], optional = true }
27-
ed25519-dalek = { version = "2.0.0", features = ["serde", "rand_core"], optional = true }
28-
once_cell = { version = "1.18.0", optional = true }
29-
rand = { version = "0.8", optional = true }
30-
rand_core = { version = "0.6.4", optional = true }
31-
ssh-key = { version = "0.6.0", features = ["ed25519", "std", "rand_core"], optional = true }
3228
ttl_cache = { version = "0.5.1", optional = true }
33-
crypto_box = { version = "0.9.1", features = ["serde", "chacha20"], optional = true }
34-
zeroize = { version = "1.5", optional = true }
35-
url = { version = "2.5", features = ["serde"], optional = true }
29+
3630
# wasm
3731
getrandom = { version = "0.2", default-features = false, optional = true }
3832

3933
[dev-dependencies]
4034
iroh-test = "0.28.0"
35+
postcard = { version = "1", features = ["use-std"] }
4136
proptest = "1.0.0"
37+
rand = "0.8"
4238
serde_json = "1"
4339
serde_test = "1"
44-
postcard = { version = "1", features = ["use-std"] }
40+
4541

4642
[features]
4743
default = ["ticket", "relay"]
4844
ticket = ["key", "dep:postcard", "dep:data-encoding"]
4945
key = [
50-
"dep:anyhow",
5146
"dep:ed25519-dalek",
52-
"dep:once_cell",
53-
"dep:rand",
5447
"dep:rand_core",
55-
"dep:ssh-key",
5648
"dep:ttl_cache",
5749
"dep:aead",
5850
"dep:crypto_box",
59-
"dep:zeroize",
6051
"dep:url",
6152
"dep:derive_more",
6253
"dep:getrandom",
@@ -68,7 +59,6 @@ wasm = ["getrandom?/js"]
6859
relay = [
6960
"dep:url",
7061
"dep:derive_more",
71-
"dep:anyhow",
7262
"dep:thiserror",
7363
]
7464

iroh-base/src/key.rs

Lines changed: 17 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,18 @@ use std::{
66
fmt::{Debug, Display},
77
hash::Hash,
88
str::FromStr,
9-
sync::Mutex,
9+
sync::{Mutex, OnceLock},
1010
time::Duration,
1111
};
1212

1313
pub use ed25519_dalek::Signature;
1414
use ed25519_dalek::{SignatureError, SigningKey, VerifyingKey};
15-
use once_cell::sync::OnceCell;
1615
use rand_core::CryptoRngCore;
1716
use serde::{Deserialize, Serialize};
18-
use ssh_key::LineEnding;
1917
use ttl_cache::TtlCache;
2018

21-
pub use self::encryption::SharedSecret;
2219
use self::encryption::{public_ed_box, secret_ed_box};
20+
pub use self::encryption::{DecryptionError, SharedSecret};
2321

2422
#[derive(Debug)]
2523
struct CryptoKeys {
@@ -48,7 +46,7 @@ const KEY_CACHE_TTL: Duration = Duration::from_secs(60);
4846
///
4947
/// So that is about 4MB of max memory for the cache.
5048
const KEY_CACHE_CAPACITY: usize = 1024 * 16;
51-
static KEY_CACHE: OnceCell<Mutex<TtlCache<[u8; 32], CryptoKeys>>> = OnceCell::new();
49+
static KEY_CACHE: OnceLock<Mutex<TtlCache<[u8; 32], CryptoKeys>>> = OnceLock::new();
5250

5351
fn lock_key_cache() -> std::sync::MutexGuard<'static, TtlCache<[u8; 32], CryptoKeys>> {
5452
let mutex = KEY_CACHE.get_or_init(|| Mutex::new(TtlCache::new(KEY_CACHE_CAPACITY)));
@@ -181,6 +179,7 @@ impl PublicKey {
181179
data_encoding::HEXLOWER.encode(&self.as_bytes()[..5])
182180
}
183181

182+
/// The length of an ed25519 `PublicKey`, in bytes.
184183
pub const LENGTH: usize = ed25519_dalek::PUBLIC_KEY_LENGTH;
185184
}
186185

@@ -259,6 +258,7 @@ pub enum KeyParsingError {
259258
/// Error when decoding the public key.
260259
#[error("key: {0}")]
261260
Key(#[from] ed25519_dalek::SignatureError),
261+
/// The encoded information had the wrong length.
262262
#[error("invalid length")]
263263
DecodeInvalidLength,
264264
}
@@ -280,7 +280,7 @@ impl FromStr for PublicKey {
280280
#[derive(Clone)]
281281
pub struct SecretKey {
282282
secret: SigningKey,
283-
secret_crypto_box: OnceCell<crypto_box::SecretKey>,
283+
secret_crypto_box: OnceLock<crypto_box::SecretKey>,
284284
}
285285

286286
impl Debug for SecretKey {
@@ -334,40 +334,19 @@ impl SecretKey {
334334
self.secret.verifying_key().into()
335335
}
336336

337-
/// Generate a new [`SecretKey`] with the default randomness generator.
338-
pub fn generate() -> Self {
339-
let mut rng = rand::rngs::OsRng;
340-
Self::generate_with_rng(&mut rng)
341-
}
342-
343337
/// Generate a new [`SecretKey`] with a randomness generator.
344-
pub fn generate_with_rng<R: CryptoRngCore + ?Sized>(csprng: &mut R) -> Self {
345-
let secret = SigningKey::generate(csprng);
338+
///
339+
/// ```rust
340+
/// // use the OsRng option for OS depedndent most secure RNG.
341+
/// let mut rng = rand::rngs::OsRng;
342+
/// let _key = iroh_base::SecretKey::generate(&mut rng);
343+
/// ```
344+
pub fn generate<R: CryptoRngCore>(mut csprng: R) -> Self {
345+
let secret = SigningKey::generate(&mut csprng);
346346

347347
Self {
348348
secret,
349-
secret_crypto_box: OnceCell::default(),
350-
}
351-
}
352-
353-
/// Serialise this key to OpenSSH format.
354-
pub fn to_openssh(&self) -> ssh_key::Result<zeroize::Zeroizing<String>> {
355-
let ckey = ssh_key::private::Ed25519Keypair {
356-
public: self.secret.verifying_key().into(),
357-
private: self.secret.clone().into(),
358-
};
359-
ssh_key::private::PrivateKey::from(ckey).to_openssh(LineEnding::default())
360-
}
361-
362-
/// Deserialise this key from OpenSSH format.
363-
pub fn try_from_openssh<T: AsRef<[u8]>>(data: T) -> anyhow::Result<Self> {
364-
let ser_key = ssh_key::private::PrivateKey::from_openssh(data)?;
365-
match ser_key.key_data() {
366-
ssh_key::private::KeypairData::Ed25519(kp) => Ok(SecretKey {
367-
secret: kp.private.clone().into(),
368-
secret_crypto_box: OnceCell::default(),
369-
}),
370-
_ => anyhow::bail!("invalid key format"),
349+
secret_crypto_box: Default::default(),
371350
}
372351
}
373352

@@ -400,7 +379,7 @@ impl From<SigningKey> for SecretKey {
400379
fn from(secret: SigningKey) -> Self {
401380
SecretKey {
402381
secret,
403-
secret_crypto_box: OnceCell::default(),
382+
secret_crypto_box: Default::default(),
404383
}
405384
}
406385
}
@@ -459,14 +438,6 @@ mod tests {
459438
assert_eq_hex!(bytes, expected);
460439
}
461440

462-
#[test]
463-
fn test_secret_key_openssh_roundtrip() {
464-
let kp = SecretKey::generate();
465-
let ser = kp.to_openssh().unwrap();
466-
let de = SecretKey::try_from_openssh(&ser).unwrap();
467-
assert_eq!(kp.to_bytes(), de.to_bytes());
468-
}
469-
470441
#[test]
471442
fn public_key_postcard() {
472443
let key = PublicKey::from_bytes(&[0; 32]).unwrap();
@@ -485,7 +456,7 @@ mod tests {
485456

486457
#[test]
487458
fn test_display_from_str() {
488-
let key = SecretKey::generate();
459+
let key = SecretKey::generate(&mut rand::thread_rng());
489460
assert_eq!(
490461
SecretKey::from_str(&key.to_string()).unwrap().to_bytes(),
491462
key.to_bytes()

iroh-base/src/key/encryption.rs

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
use std::fmt::Debug;
44

55
use aead::Buffer;
6-
use anyhow::{anyhow, ensure, Context, Result};
76

87
pub(crate) const NONCE_LEN: usize = 24;
98

@@ -18,6 +17,17 @@ pub(super) fn secret_ed_box(key: &ed25519_dalek::SigningKey) -> crypto_box::Secr
1817
/// Shared Secret.
1918
pub struct SharedSecret(crypto_box::ChaChaBox);
2019

20+
/// Errors that can occur during [`SharedSecret::open`].
21+
#[derive(Debug, thiserror::Error)]
22+
pub enum DecryptionError {
23+
/// The nonce had the wrong size.
24+
#[error("Invalid nonce")]
25+
InvalidNonce,
26+
/// AEAD decryption failed.
27+
#[error("Aead error")]
28+
Aead(aead::Error),
29+
}
30+
2131
impl Debug for SharedSecret {
2232
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2333
write!(f, "SharedSecret(crypto_box::ChaChaBox)")
@@ -42,19 +52,21 @@ impl SharedSecret {
4252
}
4353

4454
/// Opens the ciphertext, which must have been created using `Self::seal`, and places the clear text into the provided buffer.
45-
pub fn open(&self, buffer: &mut dyn Buffer) -> Result<()> {
55+
pub fn open(&self, buffer: &mut dyn Buffer) -> Result<(), DecryptionError> {
4656
use aead::AeadInPlace;
47-
ensure!(buffer.len() > NONCE_LEN, "too short");
57+
if buffer.len() < NONCE_LEN {
58+
return Err(DecryptionError::InvalidNonce);
59+
}
4860

4961
let offset = buffer.len() - NONCE_LEN;
5062
let nonce: [u8; NONCE_LEN] = buffer.as_ref()[offset..]
5163
.try_into()
52-
.context("nonce wrong length")?;
64+
.map_err(|_| DecryptionError::InvalidNonce)?;
5365

5466
buffer.truncate(offset);
5567
self.0
5668
.decrypt_in_place(&nonce.into(), &[], buffer)
57-
.map_err(|e| anyhow!("decryption failed: {:?}", e))?;
69+
.map_err(DecryptionError::Aead)?;
5870

5971
Ok(())
6072
}
@@ -76,8 +88,8 @@ mod tests {
7688

7789
#[test]
7890
fn test_seal_open_roundtrip() {
79-
let key_a = crate::key::SecretKey::generate();
80-
let key_b = crate::key::SecretKey::generate();
91+
let key_a = crate::key::SecretKey::generate(&mut rand::thread_rng());
92+
let key_b = crate::key::SecretKey::generate(&mut rand::thread_rng());
8193

8294
seal_open_roundtrip(&key_a, &key_b);
8395
seal_open_roundtrip(&key_b, &key_a);
@@ -105,7 +117,7 @@ mod tests {
105117

106118
#[test]
107119
fn test_same_public_key_api() {
108-
let key = crate::key::SecretKey::generate();
120+
let key = crate::key::SecretKey::generate(&mut rand::thread_rng());
109121
let public_key1: crypto_box::PublicKey = public_ed_box(&key.public().public());
110122
let public_key2: crypto_box::PublicKey = secret_ed_box(&key.secret).public_key();
111123

iroh-base/src/lib.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
//! Base types and utilities for Iroh
22
#![cfg_attr(iroh_docsrs, feature(doc_auto_cfg))]
3+
#![deny(missing_docs, rustdoc::broken_intra_doc_links)]
4+
#![cfg_attr(not(test), deny(clippy::unwrap_used))]
35

46
// TODO: move to own crate
57
#[cfg(feature = "ticket")]
@@ -13,8 +15,10 @@ mod node_addr;
1315
mod relay_url;
1416

1517
#[cfg(feature = "key")]
16-
pub use self::key::{KeyParsingError, NodeId, PublicKey, SecretKey, SharedSecret, Signature};
18+
pub use self::key::{
19+
DecryptionError, KeyParsingError, NodeId, PublicKey, SecretKey, SharedSecret, Signature,
20+
};
1721
#[cfg(feature = "key")]
1822
pub use self::node_addr::NodeAddr;
1923
#[cfg(feature = "relay")]
20-
pub use self::relay_url::RelayUrl;
24+
pub use self::relay_url::{RelayUrl, RelayUrlParseError};

iroh-base/src/relay_url.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use std::{fmt, ops::Deref, str::FromStr};
22

3-
use anyhow::Context;
43
use serde::{Deserialize, Serialize};
54
use url::Url;
65
/// A URL identifying a relay server.
@@ -36,15 +35,20 @@ impl From<Url> for RelayUrl {
3635
}
3736
}
3837

38+
/// Can occur when parsing a string into a [`RelayUrl`].
39+
#[derive(Debug, thiserror::Error)]
40+
#[error("Failed to parse: {0}")]
41+
pub struct RelayUrlParseError(#[from] url::ParseError);
42+
3943
/// Support for parsing strings directly.
4044
///
4145
/// If you need more control over the error first create a [`Url`] and use [`RelayUrl::from`]
4246
/// instead.
4347
impl FromStr for RelayUrl {
44-
type Err = anyhow::Error;
48+
type Err = RelayUrlParseError;
4549

4650
fn from_str(s: &str) -> Result<Self, Self::Err> {
47-
let inner = Url::from_str(s).context("invalid URL")?;
51+
let inner = Url::from_str(s)?;
4852
Ok(RelayUrl::from(inner))
4953
}
5054
}

iroh-base/src/ticket.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
//! Tickets is a serializable object combining information required for an operation.
2+
//! Typically tickets contain all information required for an operation, e.g. an iroh blob
3+
//! ticket would contain the hash of the data as well as information about how to reach the
4+
//! provider.
5+
16
use std::{collections::BTreeSet, net::SocketAddr};
27

38
use serde::{Deserialize, Serialize};
@@ -10,10 +15,6 @@ pub use self::node::NodeTicket;
1015

1116
/// A ticket is a serializable object combining information required for an operation.
1217
///
13-
/// Typically tickets contain all information required for an operation, e.g. an iroh blob
14-
/// ticket would contain the hash of the data as well as information about how to reach the
15-
/// provider.
16-
///
1718
/// Tickets support serialization to a string using base32 encoding. The kind of
1819
/// ticket will be prepended to the string to make it somewhat self describing.
1920
///
@@ -60,7 +61,10 @@ pub trait Ticket: Sized {
6061
pub enum Error {
6162
/// Found a ticket of with the wrong prefix, indicating the wrong kind.
6263
#[error("wrong prefix, expected {expected}")]
63-
Kind { expected: &'static str },
64+
Kind {
65+
/// The expected prefix.
66+
expected: &'static str,
67+
},
6468
/// This looks like a ticket, but postcard deserialization failed.
6569
#[error("deserialization failed: {_0}")]
6670
Postcard(#[from] postcard::Error),

iroh-base/src/ticket/node.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ mod tests {
141141
use crate::key::{PublicKey, SecretKey};
142142

143143
fn make_ticket() -> NodeTicket {
144-
let peer = SecretKey::generate().public();
144+
let peer = SecretKey::generate(&mut rand::thread_rng()).public();
145145
let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, 1234));
146146
let relay_url = None;
147147
NodeTicket {

iroh-dns-server/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ hickory-resolver = "=0.25.0-alpha.4"
6363
iroh = { version = "0.29.0", path = "../iroh" }
6464
iroh-test = { version = "0.29.0", path = "../iroh-test" }
6565
pkarr = { version = "2.2.0", features = ["rand"] }
66+
rand = "0.8"
67+
rand_chacha = "0.3.1"
6668
testresult = "0.4.1"
6769

6870
[[bench]]

iroh-dns-server/benches/write.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use anyhow::Result;
22
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
33
use iroh::{discovery::pkarr::PkarrRelayClient, dns::node_info::NodeInfo, SecretKey};
44
use iroh_dns_server::{config::Config, server::Server, ZoneStore};
5+
use rand_chacha::rand_core::SeedableRng;
56
use tokio::runtime::Runtime;
67

78
const LOCALHOST_PKARR: &str = "http://localhost:8080/pkarr";
@@ -23,7 +24,8 @@ fn benchmark_dns_server(c: &mut Criterion) {
2324
let config = Config::load("./config.dev.toml").await.unwrap();
2425
let server = start_dns_server(config).await.unwrap();
2526

26-
let secret_key = SecretKey::generate();
27+
let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(42);
28+
let secret_key = SecretKey::generate(&mut rng);
2729
let node_id = secret_key.public();
2830

2931
let pkarr_relay = LOCALHOST_PKARR.parse().expect("valid url");

0 commit comments

Comments
 (0)