From 0e518f679ece70db3421f16b66b791e666c8b637 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Fri, 16 Dec 2022 09:41:28 -0500 Subject: [PATCH] More simplification, move logic to get subset array of session ID into SessionId itself. --- zssp/src/app_layer.rs | 2 +- zssp/src/ints.rs | 68 -------------------------------- zssp/src/lib.rs | 4 +- zssp/src/sessionid.rs | 51 ++++++++++++++++++++++++ zssp/src/zssp.rs | 92 +++++++++++++++++++++++++++---------------- 5 files changed, 113 insertions(+), 104 deletions(-) delete mode 100644 zssp/src/ints.rs create mode 100644 zssp/src/sessionid.rs diff --git a/zssp/src/app_layer.rs b/zssp/src/app_layer.rs index d66f75354..7d973d5f0 100644 --- a/zssp/src/app_layer.rs +++ b/zssp/src/app_layer.rs @@ -6,7 +6,7 @@ use zerotier_crypto::{ }; use crate::{ - ints::SessionId, + sessionid::SessionId, zssp::{ReceiveContext, Session}, }; diff --git a/zssp/src/ints.rs b/zssp/src/ints.rs deleted file mode 100644 index bfd87c35f..000000000 --- a/zssp/src/ints.rs +++ /dev/null @@ -1,68 +0,0 @@ -use zerotier_crypto::random; -use zerotier_utils::memory; - -/// "Canonical header" for generating 96-bit AES-GCM nonce and for inclusion in HMACs. -/// -/// This is basically the actual header but with fragment count and fragment total set to zero. -/// Fragmentation is not considered when authenticating the entire packet. A separate header -/// check code is used to make fragmentation itself more robust, but that's outside the scope -/// of AEAD authentication. -#[derive(Clone, Copy)] -#[repr(C, packed)] -pub(crate) struct CanonicalHeader(pub u64, pub u32); -impl CanonicalHeader { - #[inline(always)] - pub fn make(session_id: SessionId, packet_type: u8, counter: u32) -> Self { - CanonicalHeader( - (u64::from(session_id) | (packet_type as u64).wrapping_shl(48)).to_le(), - counter.to_le(), - ) - } - - #[inline(always)] - pub fn as_bytes(&self) -> &[u8; 12] { - memory::as_byte_array(self) - } -} - -/// 48-bit session ID (most significant 16 bits of u64 are unused) -#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -#[repr(transparent)] -pub struct SessionId(pub(crate) u64); - -impl SessionId { - /// The nil session ID used in messages initiating a new session. - /// - /// This is all 1's so that ZeroTier can easily tell the difference between ZSSP init packets - /// and ZeroTier V1 packets. - pub const NIL: SessionId = SessionId(0xffffffffffff); - - #[inline] - pub fn new_from_u64(i: u64) -> Option { - if i < Self::NIL.0 { - Some(Self(i)) - } else { - None - } - } - - #[inline] - pub fn new_random() -> Self { - Self(random::next_u64_secure() % Self::NIL.0) - } -} - -impl From for u64 { - #[inline(always)] - fn from(sid: SessionId) -> Self { - sid.0 - } -} - -/// Was this side the one who sent the first offer (Alice) or countered (Bob). -/// Note that role is not fixed. Either side can take either role. It's just who -/// initiated first. -pub enum Role { - Alice, - Bob, -} diff --git a/zssp/src/lib.rs b/zssp/src/lib.rs index 9db22dd2e..539290f65 100644 --- a/zssp/src/lib.rs +++ b/zssp/src/lib.rs @@ -1,11 +1,11 @@ mod app_layer; mod counter; -mod ints; +mod sessionid; mod tests; mod zssp; pub mod constants; pub use crate::app_layer::ApplicationLayer; -pub use crate::ints::{Role, SessionId}; +pub use crate::sessionid::SessionId; pub use crate::zssp::{Error, ReceiveContext, ReceiveResult, Session}; diff --git a/zssp/src/sessionid.rs b/zssp/src/sessionid.rs new file mode 100644 index 000000000..4216bd2f0 --- /dev/null +++ b/zssp/src/sessionid.rs @@ -0,0 +1,51 @@ +use std::fmt::Display; + +use zerotier_crypto::random; +use zerotier_utils::memory::{array_range, as_byte_array}; + +use crate::constants::SESSION_ID_SIZE; + +/// 48-bit session ID (most significant 16 bits of u64 are unused) +#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[repr(transparent)] +pub struct SessionId(u64); // stored little endian internally + +impl SessionId { + /// The nil session ID used in messages initiating a new session. + /// + /// This is all 1's so that ZeroTier can easily tell the difference between ZSSP init packets + /// and ZeroTier V1 packets. + pub const NIL: SessionId = SessionId(0xffffffffffffu64.to_le()); + + #[inline] + pub fn new_from_u64(i: u64) -> Option { + if i < Self::NIL.0 { + Some(Self(i.to_le())) + } else { + None + } + } + + #[inline] + pub fn new_random() -> Self { + Self((random::next_u64_secure() % Self::NIL.0).to_le()) + } + + /// Get this session ID as a 48-bit little endian byte array. + #[inline(always)] + pub fn as_bytes(&self) -> &[u8; SESSION_ID_SIZE] { + array_range::(as_byte_array(&self.0)) + } +} + +impl From for u64 { + fn from(sid: SessionId) -> Self { + u64::from_le(sid.0) + } +} + +impl Display for SessionId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("{:06x}", u64::from_le(self.0))) + } +} diff --git a/zssp/src/zssp.rs b/zssp/src/zssp.rs index cc67ee5ed..1d118e4ba 100644 --- a/zssp/src/zssp.rs +++ b/zssp/src/zssp.rs @@ -20,11 +20,7 @@ use zerotier_utils::varint; use crate::app_layer::ApplicationLayer; use crate::constants::*; use crate::counter::{Counter, CounterValue}; -use crate::ints::*; - -//////////////////////////////////////////////////////////////// -// types -//////////////////////////////////////////////////////////////// +use crate::sessionid::SessionId; pub enum Error { /// The packet was addressed to an unrecognized local session (should usually be ignored) @@ -83,6 +79,15 @@ pub enum ReceiveResult<'a, H: ApplicationLayer> { Ignored, } +/// Was this side the one who sent the first offer (Alice) or countered (Bob). +/// +/// Note that the role can switch through the course of a session. It's the side that most recently +/// initiated a session or a rekey event. Initiator is Alice, responder is Bob. +pub enum Role { + Alice, + Bob, +} + /// State information to associate with receiving contexts such as sockets or remote paths/endpoints. /// /// This holds the data structures used to defragment incoming packets that are not associated with an @@ -153,14 +158,20 @@ struct KeyLifetime { rekey_at_or_after_timestamp: i64, } -//////////////////////////////////////////////////////////////// -// functions -//////////////////////////////////////////////////////////////// +/// "Canonical header" for generating 96-bit AES-GCM nonce and for inclusion in key exchange HMACs. +/// +/// This is basically the actual header but with fragment count and fragment total set to zero. +/// Fragmentation is not considered when authenticating the entire packet. A separate header +/// check code is used to make fragmentation itself more robust, but that's outside the scope +/// of AEAD authentication. +#[derive(Clone, Copy)] +#[repr(C, packed)] +struct CanonicalHeader(pub u64, pub u32); impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::UnknownLocalSessionId(id) => f.write_str(format!("UnknownLocalSessionId({})", id.0).as_str()), + Self::UnknownLocalSessionId(id) => f.write_str(format!("UnknownLocalSessionId({})", id).as_str()), Self::InvalidPacket => f.write_str("InvalidPacket"), Self::InvalidParameter => f.write_str("InvalidParameter"), Self::FailedAuthentication => f.write_str("FailedAuthentication"), @@ -968,7 +979,7 @@ impl ReceiveContext { let plaintext_end = idx; idx = safe_write_all(&mut reply_buf, idx, offer_id)?; - idx = safe_write_all(&mut reply_buf, idx, &session.id.0.to_le_bytes()[..SESSION_ID_SIZE])?; + idx = safe_write_all(&mut reply_buf, idx, session.id.as_bytes())?; idx = varint_safe_write(&mut reply_buf, idx, 0)?; // they don't need our static public; they have it idx = varint_safe_write(&mut reply_buf, idx, 0)?; // no meta-data in counter-offers (could be used in the future) if let Some(bob_hk_public) = bob_hk_public.as_ref() { @@ -1251,7 +1262,7 @@ fn send_ephemeral_offer( let plaintext_end = idx; idx = safe_write_all(&mut packet_buf, idx, &id)?; - idx = safe_write_all(&mut packet_buf, idx, &alice_session_id.0.to_le_bytes()[..SESSION_ID_SIZE])?; + idx = safe_write_all(&mut packet_buf, idx, alice_session_id.as_bytes())?; idx = varint_safe_write(&mut packet_buf, idx, alice_s_public_blob.len() as u64)?; idx = safe_write_all(&mut packet_buf, idx, alice_s_public_blob)?; idx = varint_safe_write(&mut packet_buf, idx, alice_metadata.len() as u64)?; @@ -1480,28 +1491,6 @@ fn parse_dec_key_offer_after_header( )) } -impl KeyLifetime { - fn new(current_counter: CounterValue, current_time: i64) -> Self { - Self { - rekey_at_or_after_counter: current_counter - .counter_value_after_uses(REKEY_AFTER_USES) - .counter_value_after_uses((random::next_u32_secure() % REKEY_AFTER_USES_MAX_JITTER) as u64), - hard_expire_at_counter: current_counter.counter_value_after_uses(EXPIRE_AFTER_USES), - rekey_at_or_after_timestamp: current_time - + REKEY_AFTER_TIME_MS - + (random::next_u32_secure() % REKEY_AFTER_TIME_MS_MAX_JITTER) as i64, - } - } - - fn should_rekey(&self, counter: CounterValue, current_time: i64) -> bool { - counter >= self.rekey_at_or_after_counter || current_time >= self.rekey_at_or_after_timestamp - } - - fn expired(&self, counter: CounterValue) -> bool { - counter >= self.hard_expire_at_counter - } -} - impl SessionKey { /// Create a new symmetric shared session key and set its key expiration times, etc. fn new(key: Secret<64>, role: Role, current_time: i64, current_counter: CounterValue, ratchet_count: u64, jedi: bool) -> Self { @@ -1565,6 +1554,43 @@ impl SessionKey { } } +impl KeyLifetime { + fn new(current_counter: CounterValue, current_time: i64) -> Self { + Self { + rekey_at_or_after_counter: current_counter + .counter_value_after_uses(REKEY_AFTER_USES) + .counter_value_after_uses((random::next_u32_secure() % REKEY_AFTER_USES_MAX_JITTER) as u64), + hard_expire_at_counter: current_counter.counter_value_after_uses(EXPIRE_AFTER_USES), + rekey_at_or_after_timestamp: current_time + + REKEY_AFTER_TIME_MS + + (random::next_u32_secure() % REKEY_AFTER_TIME_MS_MAX_JITTER) as i64, + } + } + + fn should_rekey(&self, counter: CounterValue, current_time: i64) -> bool { + counter >= self.rekey_at_or_after_counter || current_time >= self.rekey_at_or_after_timestamp + } + + fn expired(&self, counter: CounterValue) -> bool { + counter >= self.hard_expire_at_counter + } +} + +impl CanonicalHeader { + #[inline(always)] + pub fn make(session_id: SessionId, packet_type: u8, counter: u32) -> Self { + CanonicalHeader( + (u64::from(session_id) | (packet_type as u64).wrapping_shl(48)).to_le(), + counter.to_le(), + ) + } + + #[inline(always)] + pub fn as_bytes(&self) -> &[u8; 12] { + memory::as_byte_array(self) + } +} + /// Shortcut to HMAC data split into two slices. fn hmac_sha384_2(key: &[u8], a: &[u8], b: &[u8]) -> [u8; 48] { let mut hmac = HMACSHA384::new(key);