From 58df1d88a5ee73fa1c2824779bb8391c13c07548 Mon Sep 17 00:00:00 2001 From: Friedel Ziegelmayer Date: Mon, 16 Dec 2024 13:16:46 +0100 Subject: [PATCH] refactor(iroh-base): remove automatic key caching (#3051) ## Description - remove automatic caching of public key material - move `crypto_box` based shared secret into `iroh` behind a private API ## Breaking Changes - remove `iroh_base::SharedSecret` - remove `iroh_base::DecryptionError`, `iroh::DecryptionError` - remove `iroh_base::SecretKey::shared` ## Notes I removed the benchmark for this, because it would require keeping this API public. This needs a follow up PR adding an explicit key cache for the relay protocol, which needs to check keys on a per packet basis. --- Cargo.lock | 9 +- iroh-base/Cargo.toml | 10 +- iroh-base/src/key.rs | 184 ++++-------------- iroh-base/src/lib.rs | 4 +- iroh/Cargo.toml | 9 +- iroh/benches/key.rs | 93 --------- iroh/src/disco.rs | 8 +- .../src/key/encryption.rs => iroh/src/key.rs | 34 ++-- iroh/src/lib.rs | 4 +- iroh/src/magicsock.rs | 44 +++-- 10 files changed, 99 insertions(+), 300 deletions(-) delete mode 100644 iroh/benches/key.rs rename iroh-base/src/key/encryption.rs => iroh/src/key.rs (79%) diff --git a/Cargo.lock b/Cargo.lock index a76eeec8fd..e384d5b93a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -807,7 +807,9 @@ dependencies = [ "curve25519-dalek-derive", "digest", "fiat-crypto", + "rand_core", "rustc_version", + "serde", "subtle", "zeroize", ] @@ -2053,6 +2055,7 @@ dependencies = [ name = "iroh" version = "0.29.0" dependencies = [ + "aead", "anyhow", "axum", "backoff", @@ -2060,11 +2063,11 @@ dependencies = [ "bytes", "clap", "concurrent-queue", - "criterion", "crypto_box", "data-encoding", "der", "derive_more", + "ed25519-dalek", "futures-buffered", "futures-concurrency", "futures-lite 2.5.0", @@ -2143,8 +2146,7 @@ dependencies = [ name = "iroh-base" version = "0.29.0" dependencies = [ - "aead", - "crypto_box", + "curve25519-dalek", "data-encoding", "derive_more", "ed25519-dalek", @@ -2158,7 +2160,6 @@ dependencies = [ "serde_json", "serde_test", "thiserror 2.0.6", - "ttl_cache", "url", ] diff --git a/iroh-base/Cargo.toml b/iroh-base/Cargo.toml index a340a8754c..3a2585b467 100644 --- a/iroh-base/Cargo.toml +++ b/iroh-base/Cargo.toml @@ -15,8 +15,7 @@ rust-version = "1.81" workspace = true [dependencies] -aead = { version = "0.5.2", features = ["bytes"], optional = true } -crypto_box = { version = "0.9.1", features = ["serde", "chacha20"], optional = true } +curve25519-dalek = { version = "4.1.3", features = ["serde", "rand_core", "zeroize"], optional = true } data-encoding = { version = "2.3.3", optional = true } ed25519-dalek = { version = "2.0.0", features = ["serde", "rand_core", "zeroize"], optional = true } derive_more = { version = "1.0.0", features = ["display"], optional = true } @@ -25,7 +24,6 @@ postcard = { version = "1", default-features = false, features = ["alloc", "use- rand_core = { version = "0.6.4", optional = true } serde = { version = "1", features = ["derive"] } thiserror = { version = "2", optional = true } -ttl_cache = { version = "0.5.1", optional = true } # wasm getrandom = { version = "0.2", default-features = false, optional = true } @@ -43,16 +41,14 @@ serde_test = "1" default = ["ticket", "relay"] ticket = ["key", "dep:postcard", "dep:data-encoding"] key = [ + "dep:curve25519-dalek", "dep:ed25519-dalek", - "dep:rand_core", - "dep:ttl_cache", - "dep:aead", - "dep:crypto_box", "dep:url", "dep:derive_more", "dep:getrandom", "dep:thiserror", "dep:data-encoding", + "dep:rand_core", "relay", ] wasm = ["getrandom?/js"] diff --git a/iroh-base/src/key.rs b/iroh-base/src/key.rs index 391bbcdf94..9c50c5abe8 100644 --- a/iroh-base/src/key.rs +++ b/iroh-base/src/key.rs @@ -1,94 +1,38 @@ //! Cryptographic key handling for `iroh`. -mod encryption; - use std::{ + cmp::{Ord, PartialOrd}, fmt::{Debug, Display}, hash::Hash, str::FromStr, - sync::{Mutex, OnceLock}, - time::Duration, }; +use curve25519_dalek::edwards::CompressedEdwardsY; pub use ed25519_dalek::Signature; use ed25519_dalek::{SignatureError, SigningKey, VerifyingKey}; use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; -use ttl_cache::TtlCache; - -use self::encryption::{public_ed_box, secret_ed_box}; -pub use self::encryption::{DecryptionError, SharedSecret}; - -#[derive(Debug)] -struct CryptoKeys { - verifying_key: VerifyingKey, - crypto_box: crypto_box::PublicKey, -} - -impl CryptoKeys { - fn new(verifying_key: VerifyingKey) -> Self { - let crypto_box = public_ed_box(&verifying_key); - Self { - verifying_key, - crypto_box, - } - } -} -/// Expiry time for the crypto key cache. -/// -/// Basically, if no crypto operations have been performed with a key for this -/// duration, the crypto keys will be removed from the cache and need to be -/// re-created when they are used again. -const KEY_CACHE_TTL: Duration = Duration::from_secs(60); -/// Maximum number of keys in the crypto key cache. CryptoKeys are 224 bytes, -/// keys are 32 bytes, so each entry is 256 bytes plus some overhead. +/// A public key. /// -/// So that is about 4MB of max memory for the cache. -const KEY_CACHE_CAPACITY: usize = 1024 * 16; -static KEY_CACHE: OnceLock>> = OnceLock::new(); +/// The key itself is stored as the `CompressedEdwards` y coordinate of the public key +/// It is verified to decompress into a valid key when created. +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct PublicKey(CompressedEdwardsY); -fn lock_key_cache() -> std::sync::MutexGuard<'static, TtlCache<[u8; 32], CryptoKeys>> { - let mutex = KEY_CACHE.get_or_init(|| Mutex::new(TtlCache::new(KEY_CACHE_CAPACITY))); - mutex.lock().expect("not poisoned") +impl PartialOrd for PublicKey { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } } -/// Get or create the crypto keys, and project something out of them. -/// -/// If the key has been verified before, this will not fail. -fn get_or_create_crypto_keys( - key: &[u8; 32], - f: impl Fn(&CryptoKeys) -> T, -) -> std::result::Result { - let mut state = lock_key_cache(); - Ok(match state.entry(*key) { - ttl_cache::Entry::Occupied(entry) => { - // cache hit - f(entry.get()) - } - ttl_cache::Entry::Vacant(entry) => { - // cache miss, create. This might fail if the key is invalid. - let vk = VerifyingKey::from_bytes(key)?; - let item = CryptoKeys::new(vk); - let item = entry.insert(item, KEY_CACHE_TTL); - f(item) - } - }) +impl Ord for PublicKey { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.0.as_bytes().cmp(other.0.as_bytes()) + } } -/// A public key. -/// -/// The key itself is just a 32 byte array, but a key has associated crypto -/// information that is cached for performance reasons. -/// -/// The cache item will be refreshed every time a crypto operation is performed, -/// or when a key is deserialised or created from a byte array. -/// -/// Serialisation or creation from a byte array is cheap if the key is already -/// in the cache, but expensive if it is not. -#[derive(Clone, Copy, PartialEq, Eq, Ord, PartialOrd)] -pub struct PublicKey([u8; 32]); - /// The identifier for a node in the (iroh) network. /// /// Each node in iroh has a unique identifier created as a cryptographic key. This can be @@ -117,7 +61,7 @@ impl Serialize for PublicKey { if serializer.is_human_readable() { serializer.serialize_str(&self.to_string()) } else { - self.0.serialize(serializer) + self.0.as_bytes().serialize(serializer) } } } @@ -140,16 +84,12 @@ impl<'de> Deserialize<'de> for PublicKey { impl PublicKey { /// Get this public key as a byte array. pub fn as_bytes(&self) -> &[u8; 32] { - &self.0 - } - - fn public(&self) -> VerifyingKey { - get_or_create_crypto_keys(&self.0, |item| item.verifying_key).expect("key has been checked") + self.0.as_bytes() } - fn public_crypto_box(&self) -> crypto_box::PublicKey { - get_or_create_crypto_keys(&self.0, |item| item.crypto_box.clone()) - .expect("key has been checked") + /// Returns the [`VerifyingKey`] for this `PublicKey`. + pub fn public(&self) -> VerifyingKey { + VerifyingKey::from_bytes(self.0.as_bytes()).expect("already verified") } /// Construct a `PublicKey` from a slice of bytes. @@ -160,8 +100,9 @@ impl PublicKey { /// a valid `ed25519_dalek` curve point. Will never fail for bytes return from [`Self::as_bytes`]. /// See [`VerifyingKey::from_bytes`] for details. pub fn from_bytes(bytes: &[u8; 32]) -> Result { - get_or_create_crypto_keys(bytes, |item| item.verifying_key)?; - Ok(Self(*bytes)) + let key = VerifyingKey::from_bytes(bytes)?; + let y = CompressedEdwardsY(key.to_bytes()); + Ok(Self(y)) } /// Verify a signature on a message with this secret key's public key. @@ -188,21 +129,8 @@ impl TryFrom<&[u8]> for PublicKey { #[inline] fn try_from(bytes: &[u8]) -> Result { - Ok(match <[u8; 32]>::try_from(bytes) { - Ok(bytes) => { - // using from_bytes is faster than going via the verifying - // key in case the key is already in the cache, which should - // be quite common. - Self::from_bytes(&bytes)? - } - Err(_) => { - // this will always fail since the size is wrong. - // but there is no public constructor for SignatureError, - // so ¯\_(ツ)_/¯... - let vk = VerifyingKey::try_from(bytes)?; - vk.into() - } - }) + let vk = VerifyingKey::try_from(bytes)?; + Ok(Self(CompressedEdwardsY(vk.to_bytes()))) } } @@ -223,13 +151,8 @@ impl AsRef<[u8]> for PublicKey { impl From for PublicKey { fn from(verifying_key: VerifyingKey) -> Self { - let item = CryptoKeys::new(verifying_key); - let key = *verifying_key.as_bytes(); - let mut table = lock_key_cache(); - // we already have performed the crypto operation, so no need for - // get_or_create_crypto_keys. Just insert in any case. - table.insert(key, item, KEY_CACHE_TTL); - PublicKey(key) + let key = verifying_key.to_bytes(); + PublicKey(CompressedEdwardsY(key)) } } @@ -272,7 +195,7 @@ impl FromStr for PublicKey { fn from_str(s: &str) -> Result { let bytes = decode_base32_hex(s)?; - Ok(Self::try_from(&bytes)?) + Ok(Self::from_bytes(&bytes)?) } } @@ -280,7 +203,6 @@ impl FromStr for PublicKey { #[derive(Clone)] pub struct SecretKey { secret: SigningKey, - secret_crypto_box: OnceLock, } impl Debug for SecretKey { @@ -344,10 +266,7 @@ impl SecretKey { pub fn generate(mut csprng: R) -> Self { let secret = SigningKey::generate(&mut csprng); - Self { - secret, - secret_crypto_box: Default::default(), - } + Self { secret } } /// Sign the given message and return a digital signature @@ -369,18 +288,15 @@ impl SecretKey { secret.into() } - fn secret_crypto_box(&self) -> &crypto_box::SecretKey { - self.secret_crypto_box - .get_or_init(|| secret_ed_box(&self.secret)) + /// Returns the [`SigningKey`] for this `SecretKey`. + pub fn secret(&self) -> &SigningKey { + &self.secret } } impl From for SecretKey { fn from(secret: SigningKey) -> Self { - SecretKey { - secret, - secret_crypto_box: Default::default(), - } + SecretKey { secret } } } @@ -467,36 +383,4 @@ mod tests { key.public() ); } - - /// Test the different ways a key can come into existence, and that they - /// all populate the key cache. - #[test] - fn test_key_creation_cache() { - let random_verifying_key = || { - let sk = SigningKey::generate(&mut rand::thread_rng()); - sk.verifying_key() - }; - let random_public_key = || random_verifying_key().to_bytes(); - let k1 = random_public_key(); - let _key = PublicKey::from_bytes(&k1).unwrap(); - assert!(lock_key_cache().contains_key(&k1)); - - let k2 = random_public_key(); - let _key = PublicKey::try_from(&k2).unwrap(); - assert!(lock_key_cache().contains_key(&k2)); - - let k3 = random_public_key(); - let _key = PublicKey::try_from(k3.as_slice()).unwrap(); - assert!(lock_key_cache().contains_key(&k3)); - - let k4 = random_verifying_key(); - let _key = PublicKey::from(k4); - assert!(lock_key_cache().contains_key(k4.as_bytes())); - - let k5 = random_verifying_key(); - let bytes = postcard::to_stdvec(&k5).unwrap(); - // VerifyingKey serialises with a length prefix, PublicKey does not. - let _key: PublicKey = postcard::from_bytes(&bytes[1..]).unwrap(); - assert!(lock_key_cache().contains_key(k5.as_bytes())); - } } diff --git a/iroh-base/src/lib.rs b/iroh-base/src/lib.rs index 11e18c48c7..2b32e2719b 100644 --- a/iroh-base/src/lib.rs +++ b/iroh-base/src/lib.rs @@ -15,9 +15,7 @@ mod node_addr; mod relay_url; #[cfg(feature = "key")] -pub use self::key::{ - DecryptionError, KeyParsingError, NodeId, PublicKey, SecretKey, SharedSecret, Signature, -}; +pub use self::key::{KeyParsingError, NodeId, PublicKey, SecretKey, Signature}; #[cfg(feature = "key")] pub use self::node_addr::NodeAddr; #[cfg(feature = "relay")] diff --git a/iroh/Cargo.toml b/iroh/Cargo.toml index 96ef553fa5..2668d5e6fe 100644 --- a/iroh/Cargo.toml +++ b/iroh/Cargo.toml @@ -16,12 +16,14 @@ rust-version = "1.81" workspace = true [dependencies] +aead = { version = "0.5.2", features = ["bytes"] } anyhow = { version = "1" } concurrent-queue = "2.5" axum = { version = "0.7", optional = true } backoff = "0.4.0" base64 = "0.22.1" bytes = "1.7" +crypto_box = { version = "0.9.1", features = ["serde", "chacha20"] } data-encoding = "2.2" der = { version = "0.7", features = ["alloc", "derive"] } derive_more = { version = "1.0.0", features = [ @@ -32,6 +34,7 @@ derive_more = { version = "1.0.0", features = [ "deref", "from_str" ] } +ed25519-dalek = "2.0" futures-buffered = "0.2.8" futures-concurrency = "7.6" futures-lite = "2.5" @@ -147,8 +150,6 @@ windows = { version = "0.58", features = [ [dev-dependencies] axum = { version = "0.7" } clap = { version = "4", features = ["derive"] } -criterion = "0.5.1" -crypto_box = { version = "0.9.1", features = ["serde", "chacha20"] } pretty_assertions = "1.4" rand_chacha = "0.3.1" tokio = { version = "1", features = [ @@ -167,10 +168,6 @@ serde_json = "1" testresult = "0.4.0" iroh-relay = { version = "0.29", path = "../iroh-relay", default-features = false, features = ["test-utils", "server"] } -[[bench]] -name = "key" -harness = false - [features] default = ["metrics", "discovery-pkarr-dht"] metrics = ["iroh-metrics/metrics", "iroh-relay/metrics", "net-report/metrics", "portmapper/metrics"] diff --git a/iroh/benches/key.rs b/iroh/benches/key.rs deleted file mode 100644 index 81b65c9300..0000000000 --- a/iroh/benches/key.rs +++ /dev/null @@ -1,93 +0,0 @@ -use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; -use crypto_box::aead::{AeadCore, AeadInPlace}; -use iroh::SecretKey; -use rand::{RngCore, SeedableRng}; - -pub fn seal_to(c: &mut Criterion) { - let mut group = c.benchmark_group("seal_to"); - for i in [64, 1024, 2048].iter() { - let mut text = vec![0u8; *i]; - rand::thread_rng().fill_bytes(&mut text); - - group.bench_with_input(BenchmarkId::new("wrapper", i), i, |b, _| { - let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(42); - let key = SecretKey::generate(&mut rng); - let target_key = SecretKey::generate(&mut rng); - - b.iter(|| { - let shared = key.shared(&target_key.public()); - let mut res = text.clone(); - shared.seal(&mut res); - black_box(res) - }) - }); - - group.bench_with_input(BenchmarkId::new("raw", i), i, |b, _| { - let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(42); - - let key = crypto_box::SecretKey::generate(&mut rng); - let target_key = crypto_box::SecretKey::generate(&mut rng); - b.iter(|| { - let boxx = crypto_box::ChaChaBox::new(&target_key.public_key(), &key); - let nonce = crypto_box::ChaChaBox::generate_nonce(&mut rng); - let mut ciphertext = text.clone(); - boxx.encrypt_in_place(&nonce, &[], &mut ciphertext).unwrap(); - ciphertext.extend_from_slice(&nonce); - black_box(ciphertext) - }) - }); - } - group.finish(); -} - -pub fn open_from(c: &mut Criterion) { - let mut group = c.benchmark_group("open_from"); - for i in [64, 1024, 2048].iter() { - let mut text = vec![0u8; *i]; - rand::thread_rng().fill_bytes(&mut text); - - group.bench_with_input(BenchmarkId::new("wrapper", i), i, |b, _| { - let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(42); - - let key = SecretKey::generate(&mut rng); - let target_key = SecretKey::generate(&mut rng); - let shared = key.shared(&target_key.public()); - let mut cipher_text = text.clone(); - shared.seal(&mut cipher_text); - - b.iter(|| { - let shared = target_key.shared(&key.public()); - let mut res = cipher_text.clone(); - shared.open(&mut res).unwrap(); - black_box(res) - }) - }); - - group.bench_with_input(BenchmarkId::new("raw", i), i, |b, _| { - let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(42); - - let key = crypto_box::SecretKey::generate(&mut rng); - let target_key = crypto_box::SecretKey::generate(&mut rng); - let boxx = crypto_box::ChaChaBox::new(&key.public_key(), &target_key); - let nonce = crypto_box::ChaChaBox::generate_nonce(&mut rng); - let mut ciphertext = text.clone(); - boxx.encrypt_in_place(&nonce, &[], &mut ciphertext).unwrap(); - ciphertext.extend_from_slice(&nonce); - - b.iter(|| { - let mut ciphertext = ciphertext.clone(); - let offset = ciphertext.len() - 24; - let nonce: [u8; 24] = ciphertext[offset..].try_into().unwrap(); - ciphertext.truncate(offset); - let boxx = crypto_box::ChaChaBox::new(&target_key.public_key(), &key); - boxx.decrypt_in_place(&nonce.into(), &[], &mut ciphertext) - .unwrap(); - black_box(ciphertext) - }) - }); - } - group.finish(); -} - -criterion_group!(benches, seal_to, open_from); -criterion_main!(benches); diff --git a/iroh/src/disco.rs b/iroh/src/disco.rs index b9e4cf5e7a..f1beaedf77 100644 --- a/iroh/src/disco.rs +++ b/iroh/src/disco.rs @@ -405,6 +405,7 @@ mod tests { use iroh_base::SecretKey; use super::*; + use crate::key::{public_ed_box, secret_ed_box, SharedSecret}; #[test] fn test_to_from_bytes() { @@ -482,7 +483,8 @@ mod tests { node_key: sender_key.public(), }); - let shared = sender_key.shared(&recv_key.public()); + let sender_secret = secret_ed_box(sender_key.secret()); + let shared = SharedSecret::new(&sender_secret, &public_ed_box(&recv_key.public().public())); let mut seal = msg.as_bytes(); shared.seal(&mut seal); @@ -495,7 +497,9 @@ mod tests { assert_eq!(raw_key, sender_key.public()); assert_eq!(seal_back, seal); - let shared_recv = recv_key.shared(&sender_key.public()); + let recv_secret = secret_ed_box(recv_key.secret()); + let shared_recv = + SharedSecret::new(&recv_secret, &public_ed_box(&sender_key.public().public())); let mut open_seal = seal_back.to_vec(); shared_recv .open(&mut open_seal) diff --git a/iroh-base/src/key/encryption.rs b/iroh/src/key.rs similarity index 79% rename from iroh-base/src/key/encryption.rs rename to iroh/src/key.rs index 51a022b8b5..cfc29d3793 100644 --- a/iroh-base/src/key/encryption.rs +++ b/iroh/src/key.rs @@ -35,7 +35,7 @@ impl Debug for SharedSecret { } impl SharedSecret { - fn new(this: &crypto_box::SecretKey, other: &crypto_box::PublicKey) -> Self { + pub fn new(this: &crypto_box::SecretKey, other: &crypto_box::PublicKey) -> Self { SharedSecret(crypto_box::ChaChaBox::new(other, this)) } @@ -72,36 +72,34 @@ impl SharedSecret { } } -impl crate::key::SecretKey { - /// Returns the shared key for communication between this key and `other`. - pub fn shared(&self, other: &crate::key::PublicKey) -> SharedSecret { - let secret_key = self.secret_crypto_box(); - let public_key = other.public_crypto_box(); - - SharedSecret::new(secret_key, &public_key) - } -} - #[cfg(test)] mod tests { use super::*; + fn shared(this: &iroh_base::SecretKey, other: &iroh_base::PublicKey) -> SharedSecret { + let secret_key = secret_ed_box(this.secret()); + let public_key = public_ed_box(&other.public()); + + SharedSecret::new(&secret_key, &public_key) + } + #[test] fn test_seal_open_roundtrip() { - let key_a = crate::key::SecretKey::generate(&mut rand::thread_rng()); - let key_b = crate::key::SecretKey::generate(&mut rand::thread_rng()); + let mut rng = rand::thread_rng(); + let key_a = iroh_base::SecretKey::generate(&mut rng); + let key_b = iroh_base::SecretKey::generate(&mut rng); seal_open_roundtrip(&key_a, &key_b); seal_open_roundtrip(&key_b, &key_a); seal_open_roundtrip(&key_a, &key_a); } - fn seal_open_roundtrip(key_a: &crate::key::SecretKey, key_b: &crate::key::SecretKey) { + fn seal_open_roundtrip(key_a: &iroh_base::SecretKey, key_b: &iroh_base::SecretKey) { let msg = b"super secret message!!!!".to_vec(); - let shared_a = key_a.shared(&key_b.public()); + let shared_a = shared(key_a, &key_b.public()); let mut sealed_message = msg.clone(); shared_a.seal(&mut sealed_message); - let shared_b = key_b.shared(&key_a.public()); + let shared_b = shared(key_b, &key_a.public()); let mut decrypted_message = sealed_message.clone(); shared_b.open(&mut decrypted_message).unwrap(); assert_eq!(&msg[..], &decrypted_message); @@ -117,9 +115,9 @@ mod tests { #[test] fn test_same_public_key_api() { - let key = crate::key::SecretKey::generate(&mut rand::thread_rng()); + let key = iroh_base::SecretKey::generate(rand::thread_rng()); let public_key1: crypto_box::PublicKey = public_ed_box(&key.public().public()); - let public_key2: crypto_box::PublicKey = secret_ed_box(&key.secret).public_key(); + let public_key2: crypto_box::PublicKey = secret_ed_box(key.secret()).public_key(); assert_eq!(public_key1, public_key2); } diff --git a/iroh/src/lib.rs b/iroh/src/lib.rs index cf350c5432..988b7697a4 100644 --- a/iroh/src/lib.rs +++ b/iroh/src/lib.rs @@ -234,6 +234,7 @@ #![cfg_attr(iroh_docsrs, feature(doc_auto_cfg))] mod disco; +mod key; mod magicsock; pub(crate) mod util; @@ -249,8 +250,7 @@ pub mod watchable; pub use endpoint::{Endpoint, RelayMode}; pub use iroh_base::{ - DecryptionError, KeyParsingError, NodeAddr, NodeId, PublicKey, RelayUrl, RelayUrlParseError, - SecretKey, + KeyParsingError, NodeAddr, NodeId, PublicKey, RelayUrl, RelayUrlParseError, SecretKey, }; pub use iroh_relay::{RelayMap, RelayNode}; diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index 5d2191322b..3364d7b4ae 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -35,7 +35,7 @@ use concurrent_queue::ConcurrentQueue; use data_encoding::HEXLOWER; use futures_lite::{FutureExt, StreamExt}; use futures_util::{stream::BoxStream, task::AtomicWaker}; -use iroh_base::{DecryptionError, NodeAddr, NodeId, PublicKey, RelayUrl, SecretKey, SharedSecret}; +use iroh_base::{NodeAddr, NodeId, PublicKey, RelayUrl, SecretKey}; use iroh_metrics::{inc, inc_by}; use iroh_relay::{protos::stun, RelayMap}; use netwatch::{interfaces, ip::LocalAddresses, netmon, UdpSocket}; @@ -65,6 +65,7 @@ use crate::{ disco::{self, CallMeMaybe, SendAddr}, discovery::{Discovery, DiscoveryItem}, dns::DnsResolver, + key::{public_ed_box, secret_ed_box, DecryptionError, SharedSecret}, watchable::{Watchable, Watcher}, }; @@ -197,6 +198,8 @@ pub(crate) struct MagicSock { /// Key for this node. secret_key: SecretKey, + /// Encryption key for this node. + secret_encryption_key: crypto_box::SecretKey, /// Cached version of the Ipv4 and Ipv6 addrs of the current connection. local_addrs: std::sync::RwLock<(SocketAddr, Option)>, @@ -989,7 +992,7 @@ impl MagicSock { // We're now reasonably sure we're expecting communication from // this node, do the heavy crypto lifting to see what they want. let dm = match self.disco_secrets.unseal_and_decode( - &self.secret_key, + &self.secret_encryption_key, sender, sealed_box.to_vec(), ) { @@ -1113,8 +1116,12 @@ impl MagicSock { } fn encode_disco_message(&self, dst_key: PublicKey, msg: &disco::Message) -> Bytes { - self.disco_secrets - .encode_and_seal(&self.secret_key, dst_key, msg) + self.disco_secrets.encode_and_seal( + &self.secret_encryption_key, + self.secret_key.public(), + dst_key, + msg, + ) } fn send_ping_queued(&self, ping: SendPing) { @@ -1546,10 +1553,13 @@ impl Handle { let node_map = node_map.unwrap_or_default(); let node_map = NodeMap::load_from_vec(node_map); + let secret_encryption_key = secret_ed_box(secret_key.secret()); + let inner = Arc::new(MagicSock { me, port: AtomicU16::new(port), secret_key, + secret_encryption_key, proxy_url, local_addrs: std::sync::RwLock::new((ipv4_addr, ipv6_addr)), closing: AtomicBool::new(false), @@ -1674,31 +1684,35 @@ impl Handle { struct DiscoSecrets(std::sync::Mutex>); impl DiscoSecrets { - fn get(&self, secret: &SecretKey, node_id: PublicKey, cb: F) -> T + fn get(&self, secret: &crypto_box::SecretKey, node_id: PublicKey, cb: F) -> T where F: FnOnce(&mut SharedSecret) -> T, { let mut inner = self.0.lock().expect("poisoned"); - let x = inner - .entry(node_id) - .or_insert_with(|| secret.shared(&node_id)); + let x = inner.entry(node_id).or_insert_with(|| { + let public_key = public_ed_box(&node_id.public()); + SharedSecret::new(secret, &public_key) + }); cb(x) } - pub fn encode_and_seal( + fn encode_and_seal( &self, - secret_key: &SecretKey, - node_id: PublicKey, + this_secret_key: &crypto_box::SecretKey, + this_node_id: NodeId, + other_node_id: NodeId, msg: &disco::Message, ) -> Bytes { let mut seal = msg.as_bytes(); - self.get(secret_key, node_id, |secret| secret.seal(&mut seal)); - disco::encode_message(&secret_key.public(), seal).into() + self.get(this_secret_key, other_node_id, |secret| { + secret.seal(&mut seal) + }); + disco::encode_message(&this_node_id, seal).into() } - pub fn unseal_and_decode( + fn unseal_and_decode( &self, - secret: &SecretKey, + secret: &crypto_box::SecretKey, node_id: PublicKey, mut sealed_box: Vec, ) -> Result {