From 887585f6fab66dcc3ead48272ab63661966bd146 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Tue, 13 Sep 2022 21:27:47 -0400 Subject: [PATCH] Some VL1 work, and reorg the header in ZSSP to make backward compatibility easy. --- crypto/src/zssp.rs | 102 ++++++++++++------------- network-hypervisor/src/vl1/node.rs | 37 +++++---- network-hypervisor/src/vl1/peer.rs | 96 +++++++++-------------- network-hypervisor/src/vl1/protocol.rs | 9 +++ 4 files changed, 115 insertions(+), 129 deletions(-) diff --git a/crypto/src/zssp.rs b/crypto/src/zssp.rs index 47482a7e4..e4b618825 100644 --- a/crypto/src/zssp.rs +++ b/crypto/src/zssp.rs @@ -427,11 +427,11 @@ impl Session { let fragment_size = fragment_data_size + HEADER_SIZE; c.crypt(&data[..fragment_data_size], &mut mtu_buffer[HEADER_SIZE..fragment_size]); data = &data[fragment_data_size..]; - armor_header(mtu_buffer, &self.header_check_cipher); + set_header_mac(mtu_buffer, &self.header_check_cipher); send(&mut mtu_buffer[..fragment_size]); - debug_assert!(header[7].wrapping_shr(2) < 63); - header[7] += 0x04; // increment fragment number + debug_assert!(header[15].wrapping_shr(2) < 63); + header[15] += 0x04; // increment fragment number mtu_buffer[..HEADER_SIZE].copy_from_slice(&header); if data.len() <= last_fragment_data_mtu { @@ -445,7 +445,7 @@ impl Session { c.crypt(data, &mut mtu_buffer[HEADER_SIZE..gcm_tag_idx]); mtu_buffer[gcm_tag_idx..packet_len].copy_from_slice(&c.finish_encrypt()); - armor_header(mtu_buffer, &self.header_check_cipher); + set_header_mac(mtu_buffer, &self.header_check_cipher); send(&mut mtu_buffer[..packet_len]); key.return_send_cipher(c); @@ -562,11 +562,17 @@ impl ReceiveContext { return Err(Error::InvalidPacket); } - if let Some(local_session_id) = SessionId::new_from_u64(memory::u64_from_le_bytes(incoming_packet) & SessionId::MAX_BIT_MASK) { + let counter = memory::u32_from_le_bytes(incoming_packet); + let packet_type_fragment_info = memory::u16_from_le_bytes(&incoming_packet[14..16]); + let packet_type = (packet_type_fragment_info & 0x0f) as u8; + let fragment_count = ((packet_type_fragment_info.wrapping_shr(4) + 1) as u8) & 63; + let fragment_no = packet_type_fragment_info.wrapping_shr(10) as u8; + + if let Some(local_session_id) = + SessionId::new_from_u64(memory::u64_from_le_bytes(&incoming_packet[8..16]) & SessionId::MAX_BIT_MASK) + { if let Some(session) = host.session_lookup(local_session_id) { - if let Some((packet_type, fragment_count, fragment_no, counter)) = - dearmor_header(incoming_packet, &session.header_check_cipher) - { + if check_header_mac(incoming_packet, &session.header_check_cipher) { let pseudoheader = Pseudoheader::make(u64::from(local_session_id), packet_type, counter); if fragment_count > 1 { if fragment_count <= (MAX_FRAGMENTS as u8) && fragment_no < fragment_count { @@ -613,9 +619,7 @@ impl ReceiveContext { } } else { unlikely_branch(); - if let Some((packet_type, fragment_count, fragment_no, counter)) = - dearmor_header(incoming_packet, &self.incoming_init_header_check_cipher) - { + if check_header_mac(incoming_packet, &self.incoming_init_header_check_cipher) { let pseudoheader = Pseudoheader::make(0, packet_type, counter); if fragment_count > 1 { let mut defrag = self.initial_offer_defrag.lock(); @@ -1152,7 +1156,7 @@ impl ReceiveContext { reply_buf[HEADER_SIZE..].copy_from_slice(&c.finish_encrypt()); key.return_send_cipher(c); - armor_header(&mut reply_buf, &session.header_check_cipher); + set_header_mac(&mut reply_buf, &session.header_check_cipher); send(&mut reply_buf); let mut state = RwLockUpgradableReadGuard::upgrade(state); @@ -1351,6 +1355,15 @@ fn create_initial_offer( })) } +#[derive(Copy, Clone)] +#[repr(C, packed)] +struct PackedHeader { + counter: u32, + recipient_session_id_low32: u32, + recipient_session_id_high16_packet_type_fragment_info: u32, + zero: u32, +} + #[inline(always)] fn create_packet_header( header: &mut [u8], @@ -1365,17 +1378,20 @@ fn create_packet_header( debug_assert!(header.len() >= HEADER_SIZE); debug_assert!(mtu >= MIN_MTU); debug_assert!(packet_len >= MIN_PACKET_SIZE); - debug_assert!(fragment_count <= MAX_FRAGMENTS); debug_assert!(fragment_count > 0); debug_assert!(packet_type <= 0x0f); // packet type is 4 bits debug_assert!(recipient_session_id <= 0xffffffffffff); // session ID is 48 bits if fragment_count <= MAX_FRAGMENTS { - header[0..8].copy_from_slice( - &(recipient_session_id | (packet_type as u64).wrapping_shl(48) | ((fragment_count - 1) as u64).wrapping_shl(52)).to_le_bytes(), - ); - header[8..12].copy_from_slice(&counter.to_u32().to_le_bytes()); - header[12..16].fill(0); + header[..16].copy_from_slice(memory::as_byte_array::<[u32; 4], 16>(&[ + counter.to_u32().to_le(), + 0, + (recipient_session_id as u32).to_le(), + ((recipient_session_id.wrapping_shr(32) as u32) + | (packet_type as u32).wrapping_shl(16) + | ((fragment_count - 1) as u32).wrapping_shl(20)) + .to_le(), + ])); Ok(()) } else { unlikely_branch(); @@ -1395,11 +1411,11 @@ fn send_with_fragmentation( let mut header: [u8; 16] = packet[..HEADER_SIZE].try_into().unwrap(); loop { let fragment = &mut packet[fragment_start..fragment_end]; - armor_header(fragment, header_check_cipher); + set_header_mac(fragment, header_check_cipher); send(fragment); if fragment_end < packet_len { - debug_assert!(header[7].wrapping_shr(2) < 63); - header[7] += 0x04; // increment fragment number + debug_assert!(header[15].wrapping_shr(2) < 63); + header[15] += 0x04; // increment fragment number fragment_start = fragment_end - HEADER_SIZE; fragment_end = (fragment_start + mtu).min(packet_len); packet[fragment_start..(fragment_start + HEADER_SIZE)].copy_from_slice(&header); @@ -1410,45 +1426,21 @@ fn send_with_fragmentation( } } -/// Encrypt everything in header after session ID using AES-CTR and the second 16 bytes as a nonce. -/// The last four bytes of the header must be zero, so this also embeds a small header MAC. +/// Set 32-bit header MAC. #[inline(always)] -fn armor_header(packet: &mut [u8], header_check_cipher: &Aes) { +fn set_header_mac(packet: &mut [u8], header_check_cipher: &Aes) { debug_assert!(packet.len() >= MIN_PACKET_SIZE); - let mut header_pad = 0u128.to_ne_bytes(); - header_check_cipher.encrypt_block(&packet[16..32], &mut header_pad); - packet[SESSION_ID_SIZE..HEADER_SIZE] - .iter_mut() - .zip(header_pad.iter()) - .for_each(|(x, y)| *x ^= *y); + let mut header_mac = 0u128.to_ne_bytes(); + header_check_cipher.encrypt_block(&packet[8..24], &mut header_mac); + packet[4..8].copy_from_slice(&header_mac[..4]); } -/// Dearmor the armored part of the header and return it if the 32-bit MAC matches. -fn dearmor_header(packet: &[u8], header_check_cipher: &Aes) -> Option<(u8, u8, u8, u32)> { +/// Check 32-bit header MAC on an incoming packet. +fn check_header_mac(packet: &[u8], header_check_cipher: &Aes) -> bool { debug_assert!(packet.len() >= MIN_PACKET_SIZE); - let mut header_pad = 0u128.to_ne_bytes(); - header_check_cipher.encrypt_block(&packet[16..32], &mut header_pad); - let header_pad = u128::from_ne_bytes(header_pad); - - #[cfg(target_endian = "little")] - let (header_0_8, header_8_16) = { - let header = memory::u128_from_ne_bytes(packet) ^ header_pad.wrapping_shl(48); - (header as u64, header.wrapping_shr(64) as u64) - }; - #[cfg(target_endian = "big")] - let (header_0_8, header_8_16) = { - let header = memory::u128_from_ne_bytes(packet) ^ header_pad.wrapping_shr(48); - ((header.wrapping_shr(64) as u64).swap_bytes(), (header as u64).swap_bytes()) - }; - - if header_8_16.wrapping_shr(32) == 0 { - let packet_type = (header_0_8.wrapping_shr(48) as u8) & 15; - let fragment_count = ((header_0_8.wrapping_shr(52) as u8) & 63).wrapping_add(1); - let fragment_no = (header_0_8.wrapping_shr(58) as u8) & 63; - Some((packet_type, fragment_count, fragment_no, header_8_16 as u32)) - } else { - None - } + let mut header_mac = 0u128.to_ne_bytes(); + header_check_cipher.encrypt_block(&packet[8..24], &mut header_mac); + memory::u32_from_ne_bytes(&packet[4..8]) == memory::u32_from_ne_bytes(&header_mac) } /// Parse KEY_OFFER and KEY_COUNTER_OFFER starting after the unencrypted public key part. diff --git a/network-hypervisor/src/vl1/node.rs b/network-hypervisor/src/vl1/node.rs index 1c1bdf275..8631296cd 100644 --- a/network-hypervisor/src/vl1/node.rs +++ b/network-hypervisor/src/vl1/node.rs @@ -155,7 +155,7 @@ struct BackgroundTaskIntervals { struct RootInfo { sets: HashMap, roots: HashMap>, Vec>, - my_root_sets: Option>, + this_root_sets: Option>, sets_modified: bool, online: bool, } @@ -278,7 +278,7 @@ impl Node { roots: RwLock::new(RootInfo { sets: HashMap::new(), roots: HashMap::new(), - my_root_sets: None, + this_root_sets: None, sets_modified: false, online: false, }), @@ -504,7 +504,7 @@ impl Node { if !old_root_identities.eq(&new_root_identities) { let mut roots = self.roots.write(); roots.roots = new_roots; - roots.my_root_sets = my_root_sets; + roots.this_root_sets = my_root_sets; si.event(Event::UpdatedRoots(old_root_identities, new_root_identities)); } } @@ -729,14 +729,17 @@ impl Node { } } + /// Get the current "best" root from among this node's trusted roots. pub fn best_root(&self) -> Option>> { self.best_root.read().clone() } + /// Check whether this peer is a root according to any root set trusted by this node. pub fn is_peer_root(&self, peer: &Peer) -> bool { self.roots.read().roots.keys().any(|p| p.identity.eq(&peer.identity)) } + /// Called when a remote node sends us a root set update. pub(crate) fn remote_update_root_set(&self, received_from: &Identity, rs: RootSet) { let mut roots = self.roots.write(); if let Some(entry) = roots.sets.get_mut(&rs.name) { @@ -763,23 +766,27 @@ impl Node { return false; } + /// Returns whether or not this node has any root sets defined. pub fn has_roots_defined(&self) -> bool { self.roots.read().sets.iter().any(|rs| !rs.1.members.is_empty()) } + /// Get the root sets that this node trusts. pub fn root_sets(&self) -> Vec { self.roots.read().sets.values().cloned().collect() } - pub(crate) fn my_root_sets(&self) -> Option> { - self.roots.read().my_root_sets.clone() + /// Get the root set(s) to which this node belongs if it is a root. + pub(crate) fn this_root_sets_as_bytes(&self) -> Option> { + self.roots.read().this_root_sets.clone() } - #[allow(unused)] - pub(crate) fn this_node_is_root(&self) -> bool { - self.roots.read().my_root_sets.is_some() + /// Returns true if this node is a member of a root set (that it knows about). + pub fn this_node_is_root(&self) -> bool { + self.roots.read().this_root_sets.is_some() } + /// Get the canonical Path object corresponding to an endpoint. pub(crate) fn canonical_path( &self, ep: &Endpoint, @@ -788,13 +795,13 @@ impl Node { time_ticks: i64, ) -> Arc> { if let Some(path) = self.paths.read().get(&PathKey::Ref(ep, local_socket)) { - return path.clone(); + path.clone() + } else { + self.paths + .write() + .entry(PathKey::Copied(ep.clone(), local_socket.clone())) + .or_insert_with(|| Arc::new(Path::new(ep.clone(), local_socket.clone(), local_interface.clone(), time_ticks))) + .clone() } - return self - .paths - .write() - .entry(PathKey::Copied(ep.clone(), local_socket.clone())) - .or_insert_with(|| Arc::new(Path::new(ep.clone(), local_socket.clone(), local_interface.clone(), time_ticks))) - .clone(); } } diff --git a/network-hypervisor/src/vl1/peer.rs b/network-hypervisor/src/vl1/peer.rs index ef446264c..0088a51fd 100644 --- a/network-hypervisor/src/vl1/peer.rs +++ b/network-hypervisor/src/vl1/peer.rs @@ -8,7 +8,7 @@ use std::sync::{Arc, Weak}; use parking_lot::{Mutex, RwLock}; use zerotier_crypto::poly1305; -use zerotier_crypto::random::next_u64_secure; +use zerotier_crypto::random; use zerotier_crypto::salsa::Salsa; use zerotier_crypto::secret::Secret; use zerotier_utils::memory::array_range; @@ -25,50 +25,35 @@ use crate::{VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION}; pub(crate) const SERVICE_INTERVAL_MS: i64 = 10000; +pub struct Peer { + pub identity: Identity, + + static_symmetric_key: SymmetricSecret, + paths: Mutex>>, + + pub(crate) last_send_time_ticks: AtomicI64, + pub(crate) last_receive_time_ticks: AtomicI64, + pub(crate) last_hello_reply_time_ticks: AtomicI64, + pub(crate) last_forward_time_ticks: AtomicI64, + pub(crate) create_time_ticks: i64, + + random_ticks_offset: u32, + message_id_counter: AtomicU64, + remote_node_info: RwLock, +} + struct PeerPath { path: Weak>, last_receive_time_ticks: i64, } struct RemoteNodeInfo { - remote_instance_id: [u8; 16], reported_local_endpoints: HashMap, - remote_version: u64, remote_protocol_version: u8, + remote_version: (u8, u8, u16), } -/// A remote peer known to this node. -/// -/// Equality and hashing is implemented in terms of the identity. -pub struct Peer { - // This peer's identity. - pub identity: Identity, - - // Static shared secret computed from agreement with identity. - identity_symmetric_key: SymmetricSecret, - - // Paths sorted in descending order of quality / preference. - paths: Mutex>>, - - // Statistics and times of events. - pub(crate) last_send_time_ticks: AtomicI64, - pub(crate) last_receive_time_ticks: AtomicI64, - pub(crate) last_hello_reply_time_ticks: AtomicI64, - pub(crate) last_forward_time_ticks: AtomicI64, - pub(crate) last_incoming_message_id: AtomicU64, - pub(crate) create_time_ticks: i64, - - // A random offset added to ticks sent to this peer to avoid disclosing the actual tick count. - random_ticks_offset: u64, - - // Counter for assigning sequential message IDs. - message_id_counter: AtomicU64, - - // Other information reported by remote node. - remote_node_info: RwLock, -} - -/// Attempt AEAD packet encryption and MAC validation. Returns message ID on success. +/// Attempt ZeroTier V1 protocol AEAD packet encryption and MAC validation. Returns message ID on success. fn try_aead_decrypt( secret: &SymmetricSecret, packet_frag0_payload_bytes: &[u8], @@ -185,7 +170,7 @@ impl Peer { this_node_identity.agree(&id).map(|static_secret| -> Self { Self { identity: id, - identity_symmetric_key: SymmetricSecret::new(static_secret), + static_symmetric_key: SymmetricSecret::new(static_secret), paths: Mutex::new(Vec::with_capacity(4)), last_send_time_ticks: AtomicI64::new(crate::util::NEVER_HAPPENED_TICKS), last_receive_time_ticks: AtomicI64::new(crate::util::NEVER_HAPPENED_TICKS), @@ -193,29 +178,23 @@ impl Peer { last_hello_reply_time_ticks: AtomicI64::new(crate::util::NEVER_HAPPENED_TICKS), last_incoming_message_id: AtomicU64::new(0), create_time_ticks: time_ticks, - random_ticks_offset: next_u64_secure(), - message_id_counter: AtomicU64::new(next_u64_secure()), + random_ticks_offset: random::xorshift64_random() as u32, + message_id_counter: AtomicU64::new(random::xorshift64_random()), remote_node_info: RwLock::new(RemoteNodeInfo { - remote_instance_id: [0_u8; 16], reported_local_endpoints: HashMap::new(), - remote_version: 0, remote_protocol_version: 0, + remote_version: (0, 0, 0), }), } }) } - /// Get the remote version of this peer: major, minor, revision, and build. + /// Get the remote version of this peer: major, minor, revision. /// Returns None if it's not yet known. - pub fn version(&self) -> Option<[u16; 4]> { + pub fn version(&self) -> Option<(u8, u8, u16)> { let rv = self.remote_node_info.read().remote_version; - if rv != 0 { - Some([ - rv.wrapping_shr(48) as u16, - rv.wrapping_shr(32) as u16, - rv.wrapping_shr(16) as u16, - rv as u16, - ]) + if rv.0 != 0 || rv.1 != 0 || rv.2 != 0 { + Some(rv) } else { None } @@ -433,7 +412,7 @@ impl Peer { v1::CIPHER_AES_GMAC_SIV }; - let mut aes_gmac_siv = self.identity_symmetric_key.aes_gmac_siv.get(); + let mut aes_gmac_siv = self.static_symmetric_key.aes_gmac_siv.get(); aes_gmac_siv.encrypt_init(&self.next_message_id().to_ne_bytes()); aes_gmac_siv.encrypt_set_aad(&v1::get_packet_aad_bytes( self.identity.address, @@ -543,14 +522,14 @@ impl Peer { f.1.version_major = VERSION_MAJOR; f.1.version_minor = VERSION_MINOR; f.1.version_revision = VERSION_REVISION.to_be_bytes(); - f.1.timestamp = (time_ticks as u64).wrapping_add(self.random_ticks_offset).to_be_bytes(); + f.1.timestamp = (time_ticks as u64).wrapping_add(self.random_ticks_offset as u64).to_be_bytes(); } debug_assert_eq!(packet.len(), 41); assert!(packet.append_bytes((&node.identity.to_public_bytes()).into()).is_ok()); let (_, poly1305_key) = salsa_poly_create( - &self.identity_symmetric_key, + &self.static_symmetric_key, packet.struct_at::(0).unwrap(), packet.len(), ); @@ -611,7 +590,7 @@ impl Peer { let mut payload = PacketBuffer::new(); let message_id = if let Some(message_id2) = try_aead_decrypt( - &self.identity_symmetric_key, + &self.static_symmetric_key, packet_frag0_payload_bytes, packet_header, fragments, @@ -684,9 +663,6 @@ impl Peer { verbs::VL1_USER_MESSAGE => self.handle_incoming_user_message(si, node, time_ticks, source_path, &payload).await, _ => ph.handle_packet(self, &source_path, verb, &payload).await, } { - // This needs to be saved AFTER processing the packet since some message types may use it to try to filter for replays. - self.last_incoming_message_id.store(message_id, Ordering::Relaxed); - return true; } } @@ -721,9 +697,11 @@ impl Peer { { let mut remote_node_info = self.remote_node_info.write(); remote_node_info.remote_protocol_version = hello_fixed_headers.version_proto; - remote_node_info.remote_version = (hello_fixed_headers.version_major as u64).wrapping_shl(48) - | (hello_fixed_headers.version_minor as u64).wrapping_shl(32) - | (u16::from_be_bytes(hello_fixed_headers.version_revision) as u64).wrapping_shl(16); + remote_node_info.remote_version = ( + hello_fixed_headers.version_major, + hello_fixed_headers.version_minor, + u16::from_be_bytes(hello_fixed_headers.version_revision), + ); } let mut packet = PacketBuffer::new(); diff --git a/network-hypervisor/src/vl1/protocol.rs b/network-hypervisor/src/vl1/protocol.rs index 3739e4296..2f47a90f4 100644 --- a/network-hypervisor/src/vl1/protocol.rs +++ b/network-hypervisor/src/vl1/protocol.rs @@ -80,6 +80,11 @@ pub mod verbs { pub const VL1_PUSH_DIRECT_PATHS: u8 = 0x10; pub const VL1_USER_MESSAGE: u8 = 0x14; + pub const VL2_VERB_MULTICAST_LIKE: u8 = 0x09; + pub const VL2_VERB_NETWORK_CONFIG_REQUEST: u8 = 0x0b; + pub const VL2_VERB_NETWORK_CONFIG: u8 = 0x0c; + pub const VL2_VERB_MULTICAST_GATHER: u8 = 0x0d; + pub fn name(verb: u8) -> &'static str { match verb { VL1_NOP => "VL1_NOP", @@ -91,6 +96,10 @@ pub mod verbs { VL1_ECHO => "VL1_ECHO", VL1_PUSH_DIRECT_PATHS => "VL1_PUSH_DIRECT_PATHS", VL1_USER_MESSAGE => "VL1_USER_MESSAGE", + VL2_VERB_MULTICAST_LIKE => "VL2_VERB_MULTICAST_LIKE", + VL2_VERB_NETWORK_CONFIG_REQUEST => "VL2_VERB_NETWORK_CONFIG_REQUEST", + VL2_VERB_NETWORK_CONFIG => "VL2_VERB_NETWORK_CONFIG", + VL2_VERB_MULTICAST_GATHER => "VL2_VERB_MULTICAST_GATHER", _ => "???", } }