From af7a7f6a29105f774abf9e823c42307e12b00255 Mon Sep 17 00:00:00 2001 From: Dennis Schwerdel Date: Mon, 28 Sep 2020 12:50:08 +0200 Subject: [PATCH] Send salted hash of node_id --- src/cloud.rs | 17 ++++--- src/config.rs | 2 +- src/crypto/init.rs | 107 +++++++++++++++++++++++++++++++-------------- src/crypto/mod.rs | 35 ++++++++++----- src/main.rs | 2 +- src/messages.rs | 17 ++++++- 6 files changed, 128 insertions(+), 52 deletions(-) diff --git a/src/cloud.rs b/src/cloud.rs index 05db190..8eb83fe 100644 --- a/src/cloud.rs +++ b/src/cloud.rs @@ -292,7 +292,12 @@ impl GenericCloud Result<(), Error> { @@ -584,12 +589,12 @@ impl GenericCloud Result<(), Error> { + fn add_new_peer(&mut self, addr: SocketAddr, info: NodeInfo) -> Result<(), Error> { info!("Added peer {}", addr_nice(addr)); if let Some(init) = self.pending_inits.remove(&addr) { self.peers.insert(addr, PeerData { crypto: init, - node_id, + node_id: info.node_id, peer_timeout: info.peer_timeout.unwrap_or(DEFAULT_PEER_TIMEOUT), last_seen: TS::now(), timeout: TS::now() + self.config.peer_timeout as Time @@ -684,9 +689,9 @@ impl GenericCloud return Err(Error::Message("Unknown message type")) } } - MessageResult::Initialized(node_id, info) => self.add_new_peer(src, node_id, info)?, - MessageResult::InitializedWithReply(node_id, info) => { - self.add_new_peer(src, node_id, info)?; + MessageResult::Initialized(info) => self.add_new_peer(src, info)?, + MessageResult::InitializedWithReply(info) => { + self.add_new_peer(src, info)?; self.send_to(src, data)? } MessageResult::Reply => self.send_to(src, data)?, diff --git a/src/config.rs b/src/config.rs index 19e63fd..cb73de8 100644 --- a/src/config.rs +++ b/src/config.rs @@ -417,7 +417,7 @@ pub struct Args { pub version: bool, /// Generate and print a key-pair and exit - #[structopt(long, conflicts_with_all=&["password", "private_key"])] + #[structopt(long, conflicts_with="private_key")] pub genkey: bool, /// Disable automatic port forwarding diff --git a/src/crypto/init.rs b/src/crypto/init.rs index 273fa41..484d351 100644 --- a/src/crypto/init.rs +++ b/src/crypto/init.rs @@ -55,7 +55,7 @@ use super::{ core::{CryptoCore, EXTRA_LEN}, Algorithms, EcdhPrivateKey, EcdhPublicKey, Ed25519PublicKey, Error, MsgBuffer, Payload }; -use crate::types::{NodeId, NODE_ID_BYTES}; +use crate::types::NodeId; use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt}; use ring::{ aead::{Algorithm, LessSafeKey, UnboundKey, AES_128_GCM, AES_256_GCM, CHACHA20_POLY1305}, @@ -79,20 +79,35 @@ pub const STAGE_PENG: u8 = 3; pub const WAITING_TO_CLOSE: u8 = 4; pub const CLOSING: u8 = 5; +pub const SALTED_NODE_ID_HASH_LEN: usize = 20; +pub type SaltedNodeIdHash = [u8; SALTED_NODE_ID_HASH_LEN]; #[allow(clippy::large_enum_variant)] pub enum InitMsg { - Ping { node_id: NodeId, ecdh_public_key: EcdhPublicKey, algorithms: Algorithms }, - Pong { node_id: NodeId, ecdh_public_key: EcdhPublicKey, algorithms: Algorithms, encrypted_payload: MsgBuffer }, - Peng { node_id: NodeId, encrypted_payload: MsgBuffer } + // TODO: include only salted hashes of node_id and use public_key for leader election + Ping { + salted_node_id_hash: SaltedNodeIdHash, + ecdh_public_key: EcdhPublicKey, + algorithms: Algorithms + }, + Pong { + salted_node_id_hash: SaltedNodeIdHash, + ecdh_public_key: EcdhPublicKey, + algorithms: Algorithms, + encrypted_payload: MsgBuffer + }, + Peng { + salted_node_id_hash: SaltedNodeIdHash, + encrypted_payload: MsgBuffer + } } impl InitMsg { const PART_ALGORITHMS: u8 = 4; const PART_ECDH_PUBLIC_KEY: u8 = 3; const PART_END: u8 = 0; - const PART_NODE_ID: u8 = 2; const PART_PAYLOAD: u8 = 5; + const PART_SALTED_NODE_ID_HASH: u8 = 2; const PART_STAGE: u8 = 1; fn stage(&self) -> u8 { @@ -103,9 +118,11 @@ impl InitMsg { } } - fn node_id(&self) -> NodeId { + fn salted_node_id_hash(&self) -> &SaltedNodeIdHash { match self { - InitMsg::Ping { node_id, .. } | InitMsg::Pong { node_id, .. } | InitMsg::Peng { node_id, .. } => *node_id + InitMsg::Ping { salted_node_id_hash, .. } + | InitMsg::Pong { salted_node_id_hash, .. } + | InitMsg::Peng { salted_node_id_hash, .. } => salted_node_id_hash } } @@ -140,7 +157,7 @@ impl InitMsg { } let mut stage = None; - let mut node_id = None; + let mut salted_node_id_hash = None; let mut ecdh_public_key = None; let mut encrypted_payload = None; let mut algorithms = None; @@ -158,13 +175,13 @@ impl InitMsg { } stage = Some(r.read_u8().map_err(|_| Error::Parse("Init message too short"))?) } - Self::PART_NODE_ID => { - if field_len != NODE_ID_BYTES { - return Err(Error::CryptoInit("Invalid size for node id field")) + Self::PART_SALTED_NODE_ID_HASH => { + if field_len != SALTED_NODE_ID_HASH_LEN { + return Err(Error::CryptoInit("Invalid size for salted node id hash field")) } - let mut id = [0; NODE_ID_BYTES]; + let mut id = [0; SALTED_NODE_ID_HASH_LEN]; r.read_exact(&mut id).map_err(|_| Error::Parse("Init message too short"))?; - node_id = Some(id) + salted_node_id_hash = Some(id) } Self::PART_ECDH_PUBLIC_KEY => { let mut pub_key_data = smallvec![0; field_len]; @@ -223,7 +240,7 @@ impl InitMsg { Some(val) => val, None => return Err(Error::CryptoInit("Init message without stage")) }; - let node_id = match node_id { + let salted_node_id_hash = match salted_node_id_hash { Some(val) => val, None => return Err(Error::CryptoInit("Init message without node id")) }; @@ -238,7 +255,7 @@ impl InitMsg { Some(val) => val, None => return Err(Error::CryptoInit("Init message without algorithms")) }; - Self::Ping { node_id, ecdh_public_key, algorithms } + Self::Ping { salted_node_id_hash, ecdh_public_key, algorithms } } STAGE_PONG => { let ecdh_public_key = match ecdh_public_key { @@ -253,14 +270,14 @@ impl InitMsg { Some(val) => val, None => return Err(Error::CryptoInit("Init message without payload")) }; - Self::Pong { node_id, ecdh_public_key, algorithms, encrypted_payload } + Self::Pong { salted_node_id_hash, ecdh_public_key, algorithms, encrypted_payload } } STAGE_PENG => { let encrypted_payload = match encrypted_payload { Some(val) => val, None => return Err(Error::CryptoInit("Init message without payload")) }; - Self::Peng { node_id, encrypted_payload } + Self::Peng { salted_node_id_hash, encrypted_payload } } _ => return Err(Error::CryptoInit("Invalid stage")) }; @@ -285,10 +302,12 @@ impl InitMsg { w.write_u8(self.stage())?; match &self { - Self::Ping { node_id, .. } | Self::Pong { node_id, .. } | Self::Peng { node_id, .. } => { - w.write_u8(Self::PART_NODE_ID)?; - w.write_u16::(NODE_ID_BYTES as u16)?; - w.write_all(node_id)?; + Self::Ping { salted_node_id_hash, .. } + | Self::Pong { salted_node_id_hash, .. } + | Self::Peng { salted_node_id_hash, .. } => { + w.write_u8(Self::PART_SALTED_NODE_ID_HASH)?; + w.write_u16::(SALTED_NODE_ID_HASH_LEN as u16)?; + w.write_all(salted_node_id_hash)?; } } @@ -354,12 +373,13 @@ impl InitMsg { #[derive(PartialEq, Debug)] pub enum InitResult { Continue, - Success { peer_payload: P, node_id: NodeId, is_initiator: bool } + Success { peer_payload: P, is_initiator: bool } } pub struct InitState { node_id: NodeId, + salted_node_id_hash: SaltedNodeIdHash, payload: P, key_pair: Arc, trusted_keys: Arc>, @@ -378,8 +398,15 @@ impl InitState

{ algorithms: Algorithms ) -> Self { + let mut hash = [0; SALTED_NODE_ID_HASH_LEN]; + let rng = SystemRandom::new(); + rng.fill(&mut hash[0..4]).unwrap(); + hash[4..].clone_from_slice(&node_id); + let d = digest::digest(&digest::SHA256, &hash); + hash[4..].clone_from_slice(&d.as_ref()[..16]); Self { node_id, + salted_node_id_hash: hash, payload, key_pair, trusted_keys, @@ -462,6 +489,14 @@ impl InitState

{ Ok(P::read_from(Cursor::new(data.message()))?) } + fn check_salted_node_id_hash(&self, hash: &SaltedNodeIdHash, node_id: NodeId) -> bool { + let mut h2 = [0; SALTED_NODE_ID_HASH_LEN]; + h2[0..4].clone_from_slice(&hash[0..4]); + h2[4..].clone_from_slice(&node_id); + let d = digest::digest(&digest::SHA256, &h2); + hash == d.as_ref() + } + fn send_message( &mut self, stage: u8, ecdh_public_key: Option, out: &mut MsgBuffer ) -> Result<(), Error> { @@ -472,20 +507,25 @@ impl InitState

{ let msg = match stage { STAGE_PING => { InitMsg::Ping { - node_id: self.node_id, + salted_node_id_hash: self.salted_node_id_hash, ecdh_public_key: ecdh_public_key.unwrap(), algorithms: self.algorithms.clone() } } STAGE_PONG => { InitMsg::Pong { - node_id: self.node_id, + salted_node_id_hash: self.salted_node_id_hash, ecdh_public_key: ecdh_public_key.unwrap(), algorithms: self.algorithms.clone(), encrypted_payload: self.encrypt_payload()? } } - STAGE_PENG => InitMsg::Peng { node_id: self.node_id, encrypted_payload: self.encrypt_payload()? }, + STAGE_PENG => { + InitMsg::Peng { + salted_node_id_hash: self.salted_node_id_hash, + encrypted_payload: self.encrypt_payload()? + } + } _ => unreachable!() }; let mut bytes = out.buffer(); @@ -535,16 +575,18 @@ impl InitState

{ let (msg, _peer_key) = InitMsg::read_from(out.buffer(), &self.trusted_keys)?; out.clear(); let stage = msg.stage(); - let node_id = msg.node_id(); + let salted_node_id_hash = *msg.salted_node_id_hash(); debug!("Received init with stage={}, expected stage={}", stage, self.next_stage); - if self.node_id == node_id { + if self.salted_node_id_hash == salted_node_id_hash + || self.check_salted_node_id_hash(&salted_node_id_hash, self.node_id) + { return Err(Error::CryptoInit("Connected to self")) } if stage != self.next_stage { if self.next_stage == STAGE_PONG && stage == STAGE_PING { // special case for concurrent init messages in both directions // the node with the higher node_id "wins" and gets to initialize the connection - if node_id > self.node_id { + if salted_node_id_hash > self.salted_node_id_hash { // reset to initial state self.next_stage = STAGE_PING; self.last_message = None; @@ -571,7 +613,7 @@ impl InitState

{ let algorithm = self.select_algorithm(&algorithms)?; if let Some((algorithm, _speed)) = algorithm { let master_key = self.derive_master_key(algorithm, my_ecdh_private_key, &ecdh_public_key); - self.crypto = Some(CryptoCore::new(master_key, self.node_id > node_id)); + self.crypto = Some(CryptoCore::new(master_key, self.salted_node_id_hash > salted_node_id_hash)); } // create and send stage 2 reply @@ -586,7 +628,7 @@ impl InitState

{ let algorithm = self.select_algorithm(&algorithms)?; if let Some((algorithm, _speed)) = algorithm { let master_key = self.derive_master_key(algorithm, ecdh_private_key, &ecdh_public_key); - self.crypto = Some(CryptoCore::new(master_key, self.node_id > node_id)); + self.crypto = Some(CryptoCore::new(master_key, self.salted_node_id_hash > salted_node_id_hash)); } // decrypt the payload @@ -598,7 +640,7 @@ impl InitState

{ self.next_stage = WAITING_TO_CLOSE; self.close_time = 60; - Ok(InitResult::Success { peer_payload, node_id, is_initiator: true }) + Ok(InitResult::Success { peer_payload, is_initiator: true }) } InitMsg::Peng { mut encrypted_payload, .. } => { // decrypt the payload @@ -606,7 +648,7 @@ impl InitState

{ self.decrypt(&mut encrypted_payload).map_err(|_| Error::CryptoInit("Failed to decrypt payload"))?; self.next_stage = CLOSING; // force resend when receiving any message - Ok(InitResult::Success { peer_payload, node_id, is_initiator: false }) + Ok(InitResult::Success { peer_payload, is_initiator: false }) } } } @@ -620,6 +662,7 @@ impl InitState

{ #[cfg(test)] mod tests { use super::*; + use crate::types::NODE_ID_BYTES; impl Payload for Vec { fn write_to(&self, buffer: &mut MsgBuffer) { diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 6e71170..c3df94c 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -127,10 +127,23 @@ impl Crypto { Ok(Self { node_id, key_pair: Arc::new(key_pair), trusted_keys: Arc::new(trusted_keys), algorithms: algos }) } - pub fn generate_keypair() -> (String, String) { - let rng = SystemRandom::new(); + pub fn generate_keypair(password: Option<&str>) -> (String, String) { let mut bytes = [0; 32]; - rng.fill(&mut bytes).unwrap(); + match password { + None => { + let rng = SystemRandom::new(); + rng.fill(&mut bytes).unwrap(); + } + Some(password) => { + pbkdf2::derive( + pbkdf2::PBKDF2_HMAC_SHA256, + NonZeroU32::new(4096).unwrap(), + SALT, + password.as_bytes(), + &mut bytes + ); + } + } let keypair = Ed25519KeyPair::from_seed_unchecked(&bytes).unwrap(); let privkey = to_base62(&bytes); let pubkey = to_base62(keypair.public_key().as_ref()); @@ -183,8 +196,8 @@ impl Crypto { #[derive(Debug, PartialEq)] pub enum MessageResult { Message(u8), - Initialized(NodeId, P), - InitializedWithReply(NodeId, P), + Initialized(P), + InitializedWithReply(P), Reply, None } @@ -262,7 +275,7 @@ impl PeerCrypto

{ } match result { InitResult::Continue => Ok(MessageResult::Reply), - InitResult::Success { peer_payload, node_id, is_initiator } => { + InitResult::Success { peer_payload, is_initiator } => { self.core = self.get_init()?.take_core(); if self.core.is_none() { self.unencrypted = true; @@ -275,13 +288,13 @@ impl PeerCrypto

{ } if !is_initiator { if self.unencrypted { - return Ok(MessageResult::Initialized(node_id, peer_payload)) - } + return Ok(MessageResult::Initialized(peer_payload)) + } assert!(!buffer.is_empty()); buffer.prepend_byte(MESSAGE_TYPE_ROTATION); self.encrypt_message(buffer)?; } - Ok(MessageResult::InitializedWithReply(node_id, peer_payload)) + Ok(MessageResult::InitializedWithReply(peer_payload)) } } } @@ -415,12 +428,12 @@ mod tests { debug!("Node1 <- Node2"); let res = node1.handle_message(&mut msg).unwrap(); - assert_eq!(res, MessageResult::InitializedWithReply(node2.node_id, vec![])); + assert_eq!(res, MessageResult::InitializedWithReply(vec![])); assert!(!msg.is_empty()); debug!("Node1 -> Node2"); let res = node2.handle_message(&mut msg).unwrap(); - assert_eq!(res, MessageResult::InitializedWithReply(node1.node_id, vec![])); + assert_eq!(res, MessageResult::InitializedWithReply(vec![])); assert!(!msg.is_empty()); debug!("Node1 <- Node2"); diff --git a/src/main.rs b/src/main.rs index b005d05..e580d61 100644 --- a/src/main.rs +++ b/src/main.rs @@ -226,7 +226,7 @@ fn main() { return } if args.genkey { - let (privkey, pubkey) = Crypto::generate_keypair(); + let (privkey, pubkey) = Crypto::generate_keypair(args.password.as_deref()); println!("Private key: {}\nPublic key: {}\n", privkey, pubkey); println!( "Attention: Keep the private key secret and use only the public key on other nodes to establish trust." diff --git a/src/messages.rs b/src/messages.rs index 32b4d1f..da2a423 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -33,12 +33,14 @@ pub struct PeerInfo { #[derive(Debug, PartialEq)] pub struct NodeInfo { + pub node_id: NodeId, pub peers: PeerList, pub claims: RangeList, pub peer_timeout: Option } impl NodeInfo { + const PART_NODEID: u8 = 4; const PART_CLAIMS: u8 = 2; const PART_END: u8 = 0; const PART_PEERS: u8 = 1; @@ -89,6 +91,7 @@ impl NodeInfo { let mut peers = smallvec![]; let mut claims = smallvec![]; let mut peer_timeout = None; + let mut node_id = None; loop { let part = r.read_u8().map_err(|_| Error::Message("Truncated message"))?; if part == Self::PART_END { @@ -105,6 +108,11 @@ impl NodeInfo { peer_timeout = Some(rp.read_u16::().map_err(|_| Error::Message("Truncated message"))?) } + Self::PART_NODEID => { + let mut data = [0; NODE_ID_BYTES]; + rp.read_exact(&mut data).map_err(|_| Error::Message("Truncated message"))?; + node_id = Some(data); + } _ => { let mut data = vec![0; part_len]; rp.read_exact(&mut data).map_err(|_| Error::Message("Truncated message"))?; @@ -112,7 +120,11 @@ impl NodeInfo { } r = rp.into_inner(); } - Ok(Self { peers, claims, peer_timeout }) + let node_id = match node_id { + Some(node_id) => node_id, + None => return Err(Error::Message("Payload without node_id")) + }; + Ok(Self { node_id, peers, claims, peer_timeout }) } pub fn decode(r: R) -> Result { @@ -174,6 +186,9 @@ impl NodeInfo { let len; { let mut cursor = Cursor::new(buffer.buffer()); + Self::encode_part(&mut cursor, Self::PART_NODEID, |cursor| { + cursor.write_all(&self.node_id) + })?; Self::encode_part(&mut cursor, Self::PART_PEERS, |cursor| self.encode_peer_list_part(cursor))?; Self::encode_part(&mut cursor, Self::PART_CLAIMS, |mut cursor| { for c in &self.claims {