From 301336fc8639f25f7edd6043ba988b7cbf8ecd5d Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 2 Aug 2023 16:01:55 -0400 Subject: [PATCH 01/37] trying out new storage interface --- examples/basic_test.rs | 40 +++-- examples/basic_test_copy.rs | 348 ++++++++++++++++++++++++++++++++++++ src/applicationlayer.rs | 11 +- src/ratchet_state.rs | 106 ++++------- src/zeta.rs | 143 ++++++++------- 5 files changed, 488 insertions(+), 160 deletions(-) create mode 100644 examples/basic_test_copy.rs diff --git a/examples/basic_test.rs b/examples/basic_test.rs index 6d98679..b24402c 100644 --- a/examples/basic_test.rs +++ b/examples/basic_test.rs @@ -20,7 +20,6 @@ use rand_core::OsRng; use rand_core::RngCore; use sha2::Sha512; -use zssp_proto::crypto::secure_eq; use zssp_proto::ratchet_state::RatchetState; use zssp_proto::{Session, Settings, RATCHET_SIZE}; @@ -29,7 +28,7 @@ const TEST_MTU: usize = 1500; struct TestApplication { time: Instant, name: &'static str, - ratchets: Mutex<[RatchetState; 2]>, + ratchets: Mutex<(RatchetState, Option)>, } #[allow(unused)] @@ -66,17 +65,18 @@ impl zssp_proto::ApplicationLayer for &TestApplication { (Some((true, ())), true) } - fn restore_by_fingerprint(&self, ratchet_fingerprint: &[u8; RATCHET_SIZE]) -> Result { + fn restore_by_fingerprint(&self, ratchet_fingerprint: &[u8; RATCHET_SIZE]) -> Result, Self::DiskError> { let ratchets = self.ratchets.lock().unwrap(); - for rs in ratchets.iter() { - if rs.nonempty().map_or(false, |rs| secure_eq(&rs.fingerprint, ratchet_fingerprint)) { - return Ok(rs.clone()); - } + if ratchets.0.fingerprint_eq(ratchet_fingerprint) { + return Ok(Some(ratchets.0.clone())); } - Ok(RatchetState::Null) + if ratchets.1.as_ref().map_or(false, |rs| rs.fingerprint_eq(ratchet_fingerprint)) { + return Ok(ratchets.1.clone()); + } + Ok(None) } - fn restore_by_identity(&self, remote_static_key: &Self::PublicKey, application_data: &Self::Data) -> Result<[RatchetState; 2], Self::DiskError> { + fn restore_by_identity(&self, remote_static_key: &Self::PublicKey, application_data: &Self::Data) -> Result<(RatchetState, Option), Self::DiskError> { Ok(self.ratchets.lock().unwrap().clone()) } @@ -84,15 +84,17 @@ impl zssp_proto::ApplicationLayer for &TestApplication { &self, remote_static_key: &Self::PublicKey, application_data: &Self::Data, - pre_ratchet_states: [&RatchetState; 2], - new_ratchet_states: [&RatchetState; 2], + state1: &RatchetState, + state2: Option<&RatchetState>, + state_added: Option<&RatchetState>, + state_deleted1: Option<&RatchetState>, + state_deleted2: Option<&RatchetState>, ) -> Result<(), Self::DiskError> { let mut ratchets = self.ratchets.lock().unwrap(); - ratchets[0] = new_ratchet_states[0].clone(); - ratchets[1] = new_ratchet_states[1].clone(); - let chain_len = new_ratchet_states[0].chain_len(); - if chain_len > pre_ratchet_states[0].chain_len() { - println!("[{}] new ratchet #{}", self.name, chain_len); + ratchets.0 = state1.clone(); + ratchets.1 = state2.cloned(); + if let Some(rs) = state_added { + println!("[{}] new ratchet #{}", self.name, rs.chain_len); } Ok(()) } @@ -283,19 +285,19 @@ fn bob_main( fn core(time: u64, packet_success_rate: u32) { let run = &AtomicBool::new(true); - let shared_ratchet_states = RatchetState::new_from_otp::(b"password1"); + let shared_ratchet_state = RatchetState::new_from_otp::(b"password1"); let alice_keypair = EphemeralSecret::random(&mut OsRng); let alice_app = TestApplication { time: Instant::now(), name: "alice", - ratchets: Mutex::new(shared_ratchet_states.clone()), + ratchets: Mutex::new((shared_ratchet_state.clone(), None)), }; let bob_keypair = EphemeralSecret::random(&mut OsRng); let bob_pubkey = bob_keypair.public_key(); let bob_app = TestApplication { time: Instant::now(), name: "bob", - ratchets: Mutex::new(shared_ratchet_states), + ratchets: Mutex::new((shared_ratchet_state, None)), }; let (alice_out, bob_in) = mpsc::sync_channel::>(256); diff --git a/examples/basic_test_copy.rs b/examples/basic_test_copy.rs new file mode 100644 index 0000000..4199295 --- /dev/null +++ b/examples/basic_test_copy.rs @@ -0,0 +1,348 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +use std::collections::HashMap; +use std::iter::ExactSizeIterator; +use std::str::FromStr; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{mpsc, Arc, Mutex}; +use std::thread; +use std::time::{Duration, Instant}; + +use aes::Aes256; +use aes_gcm::Aes256Gcm; +use p384::{ecdh::EphemeralSecret, PublicKey}; +use rand_core::OsRng; +use rand_core::RngCore; +use sha2::Sha512; + +use zssp_proto::ratchet_state::RatchetState; +use zssp_proto::{Session, Settings, RATCHET_SIZE}; + +const TEST_MTU: usize = 1500; + +struct TestApplication { + time: Instant, + name: &'static str, + ratchets: Mutex, +} + +struct Ratchets { + rf_map: HashMap<[u8; RATCHET_SIZE], RatchetState>, + peer_map: HashMap)>, +} + +#[allow(unused)] +impl zssp_proto::ApplicationLayer for &TestApplication { + const SETTINGS: Settings = Settings { + initial_offer_timeout: Settings::INITIAL_OFFER_TIMEOUT_MS, + rekey_timeout: 60 * 1000, + rekey_after_time: 3000, + rekey_time_max_jitter: 1000, + rekey_after_key_uses: Settings::REKEY_AFTER_KEY_USES, + resend_time: 250, + fragment_assembly_timeout: Settings::FRAGMENT_ASSEMBLY_TIMEOUT_MS, + }; + type Rng = OsRng; + type Prp = Aes256; + type Aead = Aes256Gcm; + type Hash = Sha512; + type PublicKey = PublicKey; + type KeyPair = EphemeralSecret; + type Kem = [u8; pqc_kyber::KYBER_SECRETKEYBYTES]; + + type DiskError = (); + type Data = u128; + + fn hello_requires_recognized_ratchet(&self) -> bool { + true + } + + fn initiator_disallows_downgrade(&self, session: &Arc>) -> bool { + true + } + + fn check_accept_session(&self, remote_static_key: &Self::PublicKey, identity: &[u8]) -> (Option<(bool, Self::Data)>, bool) { + (Some((true, 0)), true) + } + + fn restore_by_fingerprint(&self, ratchet_fingerprint: &[u8; RATCHET_SIZE]) -> Result, Self::DiskError> { + let ratchets = self.ratchets.lock().unwrap(); + Ok(ratchets.rf_map.get(ratchet_fingerprint).cloned()) + } + + fn restore_by_identity(&self, remote_static_key: &Self::PublicKey, application_data: &Self::Data) -> Result<(RatchetState, Option), Self::DiskError> { + let ratchets = self.ratchets.lock().unwrap(); + Ok(ratchets.peer_map.get(application_data).cloned()) + } + + fn save_ratchet_state( + &self, + remote_static_key: &Self::PublicKey, + application_data: &Self::Data, + state1: &RatchetState, + state2: Option<&RatchetState>, + state_added: Option<&RatchetState>, + state_deleted1: Option<&RatchetState>, + state_deleted2: Option<&RatchetState>, + ) -> Result<(), Self::DiskError> { + let mut ratchets = self.ratchets.lock().unwrap(); + ratchets.0 = state1.clone(); + ratchets.1 = state2.cloned(); + if let Some(rs) = state_added { + println!("[{}] new ratchet #{}", self.name, rs.chain_len); + } + Ok(()) + } + + fn time(&self) -> i64 { + self.time.elapsed().as_millis() as i64 + } + + fn event_log(&self, event: zssp_proto::LogEvent) { + println!(">[{}] {:?}", self.name, event); + } +} + +fn alice_main( + run: &AtomicBool, + packet_success_rate: u32, + alice_app: &TestApplication, + alice_out: mpsc::SyncSender>, + alice_in: mpsc::Receiver>, + recursive_out: mpsc::SyncSender>, + alice_keypair: EphemeralSecret, + bob_pubkey: PublicKey, +) { + let startup_time = std::time::Instant::now(); + let context = zssp_proto::Context::<&TestApplication>::new(alice_keypair, OsRng); + let mut next_service = startup_time.elapsed().as_millis() as i64 + 500; + let test_data = [1u8; TEST_MTU * 10]; + let mut up = false; + let mut alice_session = None; + + while run.load(Ordering::Relaxed) { + if alice_session.is_none() { + up = false; + alice_session = Some( + context + .open(alice_app, |b| alice_out.send(b).is_ok(), TEST_MTU, bob_pubkey.clone(), (), Vec::new()) + .unwrap(), + ); + println!("[alice] opening session"); + } + let current_time = startup_time.elapsed().as_millis() as i64; + loop { + let pkt = alice_in.try_recv(); + if let Ok(pkt) = pkt { + if OsRng.next_u32() <= packet_success_rate { + use zssp_proto::result::ReceiveError::*; + use zssp_proto::result::ReceiveOk::*; + use zssp_proto::result::SessionEvent::*; + match context.receive( + alice_app, + |b| alice_out.send(b).is_ok(), + TEST_MTU, + |_| Some((|b| alice_out.send(b).is_ok(), TEST_MTU)), + &0, + pkt, + ) { + Ok(Unassociated) => { + //println!("[alice] ok"); + } + Ok(Session(_, event)) => match event { + Established => { + up = true; + } + Data(data) => { + assert!(!data.is_empty()); + //println!("[alice] received {}", data.len()); + } + NewSession => panic!(), + Rejected => panic!(), + Control => (), + }, + Err(e) => { + println!("[alice] ERROR {:?}", e); + if let ByzantineFault { unnatural, .. } = e { + assert!(!unnatural) + } + } + } + } else if OsRng.next_u32() | 1 > 0 { + let _ = recursive_out.send(pkt); + } + } else { + break; + } + } + + if up { + context + .send( + alice_session.as_ref().unwrap(), + |b| alice_out.send(b).is_ok(), + TEST_MTU, + test_data[..1400 + ((OsRng.next_u64() as usize) % (test_data.len() - 1400))].to_vec(), + ) + .unwrap(); + } else { + thread::sleep(Duration::from_millis(10)); + } + // TODO: we need to more comprehensively test if re-opening the session works + if OsRng.next_u32() <= ((u32::MAX as f64) * 0.0000025) as u32 { + alice_session = None; + } + + if current_time >= next_service { + next_service = current_time + context.service(alice_app, |_| Some((|b| alice_out.send(b).is_ok(), TEST_MTU))); + } + } +} + +fn bob_main( + run: &AtomicBool, + packet_success_rate: u32, + bob_app: &TestApplication, + bob_out: mpsc::SyncSender>, + bob_in: mpsc::Receiver>, + recursive_out: mpsc::SyncSender>, + bob_keypair: EphemeralSecret, +) { + let startup_time = std::time::Instant::now(); + let context = zssp_proto::Context::<&TestApplication>::new(bob_keypair, OsRng); + let mut last_speed_metric = startup_time.elapsed().as_millis() as i64; + let mut next_service = last_speed_metric + 500; + let mut transferred = 0u64; + + let mut bob_session = None; + + while run.load(Ordering::Relaxed) { + let pkt = bob_in.recv_timeout(Duration::from_millis(100)); + let current_time = startup_time.elapsed().as_millis() as i64; + + if let Ok(pkt) = pkt { + if OsRng.next_u32() <= packet_success_rate { + use zssp_proto::result::ReceiveError::*; + use zssp_proto::result::ReceiveOk::*; + use zssp_proto::result::SessionEvent::*; + match context.receive( + bob_app, + |b| bob_out.send(b).is_ok(), + TEST_MTU, + |_| Some((|b| bob_out.send(b).is_ok(), TEST_MTU)), + &0, + pkt, + ) { + Ok(Unassociated) => {} + Ok(Session(s, event)) => match event { + NewSession => { + println!("[bob] new session, took {}s", current_time as f32 / 1000.0); + let _ = bob_session.replace(s); + } + Data(data) => { + assert!(!data.is_empty()); + //println!("[bob] received {}", data.len()); + transferred += data.len() as u64 * 2; // *2 because we are also sending this many bytes back + context.send(&s, |b| bob_out.send(b).is_ok(), TEST_MTU, data).unwrap(); + } + Established => panic!(), + Rejected => panic!(), + Control => (), + }, + Err(e) => { + println!("[bob] ERROR {:?}", e); + if let ByzantineFault { unnatural, .. } = e { + assert!(!unnatural) + } + } + } + } else if OsRng.next_u32() | 1 > 0 { + let _ = recursive_out.try_send(pkt); + } + } + + let speed_metric_elapsed = current_time - last_speed_metric; + if speed_metric_elapsed >= 10000 { + last_speed_metric = current_time; + println!( + "[bob] throughput: {} MiB/sec (combined input and output)", + ((transferred as f64) / 1048576.0) / ((speed_metric_elapsed as f64) / 1000.0) + ); + transferred = 0; + } + + if current_time >= next_service { + next_service = current_time + context.service(bob_app, |_| Some((|b| bob_out.send(b).is_ok(), TEST_MTU))); + } + } +} + +fn core(time: u64, packet_success_rate: u32) { + let run = &AtomicBool::new(true); + + let shared_ratchet_state = RatchetState::new_from_otp::(b"password1"); + let alice_keypair = EphemeralSecret::random(&mut OsRng); + let alice_app = TestApplication { + time: Instant::now(), + name: "alice", + ratchets: Mutex::new((shared_ratchet_state.clone(), None)), + }; + let bob_keypair = EphemeralSecret::random(&mut OsRng); + let bob_pubkey = bob_keypair.public_key(); + let bob_app = TestApplication { + time: Instant::now(), + name: "bob", + ratchets: Mutex::new((shared_ratchet_state, None)), + }; + + let (alice_out, bob_in) = mpsc::sync_channel::>(256); + let (bob_out, alice_in) = mpsc::sync_channel::>(256); + + thread::scope(|ts| { + { + let alice_out = alice_out.clone(); + let bob_out = bob_out.clone(); + ts.spawn(move || { + alice_main( + run, + packet_success_rate, + &alice_app, + alice_out, + alice_in, + bob_out, + alice_keypair, + bob_pubkey, + ) + }); + } + ts.spawn(move || bob_main(run, packet_success_rate, &bob_app, bob_out, bob_in, alice_out, bob_keypair)); + + thread::sleep(Duration::from_secs(time)); + + run.store(false, Ordering::SeqCst); + println!("finished"); + }); +} + +fn main() { + let args = std::env::args(); + let packet_success_rate = if args.len() <= 1 { + let default_success_rate = 1.0; + ((u32::MAX as f64) * default_success_rate) as u32 + } else { + ((u32::MAX as f64) * f64::from_str(args.last().unwrap().as_str()).unwrap()) as u32 + }; + + core(60 * 60, packet_success_rate) +} + +#[test] +fn test_main() { + core(2, u32::MAX / 2) +} diff --git a/src/applicationlayer.rs b/src/applicationlayer.rs index 3ea8922..2d0d78a 100644 --- a/src/applicationlayer.rs +++ b/src/applicationlayer.rs @@ -161,11 +161,11 @@ pub trait ApplicationLayer: Sized { /// If `RatchetAction::DowngradeRatchet` is returned we will attempt to convince Alice to downgrade /// to the empty ratchet key, restarting the ratchet chain. /// If `RatchetAction::FailAuthentication` is returned Alice's connection will be silently dropped. - fn restore_by_fingerprint(&self, ratchet_fingerprint: &[u8; RATCHET_SIZE]) -> Result; + fn restore_by_fingerprint(&self, ratchet_fingerprint: &[u8; RATCHET_SIZE]) -> Result, Self::DiskError>; /// Lookup a specific ratchet state based on the identity of the peer being communicated with. /// This function will be called whenever Alice attempts to open a session, or Bob attempts /// to verify Alice's identity. - fn restore_by_identity(&self, remote_static_key: &Self::PublicKey, application_data: &Self::Data) -> Result<[RatchetState; 2], Self::DiskError>; + fn restore_by_identity(&self, remote_static_key: &Self::PublicKey, application_data: &Self::Data) -> Result<(RatchetState, Option), Self::DiskError>; /// Atomically save the given `new_ratchet_states` to persistent storage. /// `pre_ratchet_states` contains what should be the previous contents of persistent storage. /// @@ -186,8 +186,11 @@ pub trait ApplicationLayer: Sized { &self, remote_static_key: &Self::PublicKey, application_data: &Self::Data, - pre_ratchet_states: [&RatchetState; 2], - new_ratchet_states: [&RatchetState; 2], + state1: &RatchetState, + state2: Option<&RatchetState>, + state_added: Option<&RatchetState>, + state_deleted1: Option<&RatchetState>, + state_deleted2: Option<&RatchetState>, ) -> Result<(), Self::DiskError>; /// Receives a stream of events that occur during an execution of ZSSP. diff --git a/src/ratchet_state.rs b/src/ratchet_state.rs index 615a60f..8c26086 100644 --- a/src/ratchet_state.rs +++ b/src/ratchet_state.rs @@ -1,30 +1,34 @@ +use zeroize::Zeroizing; + use crate::crypto::{secure_eq, HashSha512}; use crate::proto::*; -use std::ops::Deref; - -#[derive(Clone, PartialEq, Eq)] -pub enum RatchetState { - Null, - Empty, - NonEmpty(NonEmptyRatchetState), +/// A ratchet key and fingerprint, +/// along with the length of the ratchet chain the keys were derived from. +#[derive(Clone, Eq)] +pub struct RatchetState { + pub key: Zeroizing<[u8; RATCHET_SIZE]>, + pub fingerprint: Option>, + pub chain_len: u64, +} +impl PartialEq for RatchetState { + fn eq(&self, other: &Self) -> bool { + secure_eq(&self.key, &other.key) & (self.chain_len == other.chain_len) & + match (self.fingerprint.as_ref(), other.fingerprint.as_ref()) { + (Some(rf1), Some(rf2)) => secure_eq(rf1, rf2), + (None, None) => true, + _ => false, + } + } } -use zeroize::Zeroizing; -use RatchetState::*; impl RatchetState { - /// Helper function to create a new nonempty ratchet state from a raw ratchet key, fingerprint, - /// and the ratchet chain's current length. - pub fn new_nonempty(key: Zeroizing<[u8; RATCHET_SIZE]>, fingerprint: Zeroizing<[u8; RATCHET_SIZE]>, chain_len: u64) -> Self { - NonEmpty(NonEmptyRatchetState { key, fingerprint, chain_len }) + pub fn new(key: Zeroizing<[u8; RATCHET_SIZE]>, fingerprint: Zeroizing<[u8; RATCHET_SIZE]>, chain_len: u64) -> Self { + RatchetState { key, fingerprint: Some(fingerprint), chain_len } } - /// Creates the set of two ratchet states that ZSSP initializes a key exchange with when - /// communicating to a brand new peer. - pub fn new_initial_states() -> [RatchetState; 2] { - [RatchetState::Empty, RatchetState::Null] + pub fn empty() -> Self { + RatchetState { key: Zeroizing::new([0u8; RATCHET_SIZE]), fingerprint: None, chain_len: 0 } } - /// Creates a set of two ratchet states that ZSSP can initialize a key exchange with when - /// communicating to a brand new peer. - /// The peer must know the one-time-password and initialize their key exchange with it as well. - pub fn new_from_otp(otp: &[u8]) -> [RatchetState; 2] { + + pub fn new_from_otp(otp: &[u8]) -> RatchetState { let mut buffer = Vec::new(); buffer.push(1); buffer.extend(LABEL_OTP_TO_RATCHET); @@ -33,60 +37,16 @@ impl RatchetState { let r1 = Hmac::hmac(otp, &buffer); buffer[0] = 2; let r2 = Hmac::hmac(otp, &buffer); - [ - Self::new_nonempty( - Zeroizing::new(r1[..RATCHET_SIZE].try_into().unwrap()), - Zeroizing::new(r2[..RATCHET_SIZE].try_into().unwrap()), - 1, - ), - RatchetState::Null, - ] + Self::new( + Zeroizing::new(r1[..RATCHET_SIZE].try_into().unwrap()), + Zeroizing::new(r2[..RATCHET_SIZE].try_into().unwrap()), + 1, + ) } - /// Returns true if this ratchet state is the null ratchet state. - pub fn is_null(&self) -> bool { - matches!(self, Null) - } - /// Returns true if this ratchet state is the empty ratchet state. The empty ratchet state has - /// a key of all zeros and the empty string as the ratchet fingerprint. pub fn is_empty(&self) -> bool { - matches!(self, Empty) + self.fingerprint.is_none() } - /// Retrieve a nonempty ratchet state if it exists. - pub fn nonempty(&self) -> Option<&NonEmptyRatchetState> { - match self { - NonEmpty(rs) => Some(rs), - _ => None, - } - } - /// Retrieve the ratchet chain length, or 0 if this ratchet state is null or empty. - pub fn chain_len(&self) -> u64 { - self.nonempty().map_or(0, |rs| rs.chain_len) - } - /// Retrieve the ratchet fingerprint if it exists. - pub fn fingerprint(&self) -> Option<&[u8; RATCHET_SIZE]> { - self.nonempty().map(|rs| rs.fingerprint.deref()) - } - /// Retrieve the ratchet key if it exists. - /// This function will return a key of all zeros if this ratchet state is the empty ratchet state. - pub fn key(&self) -> Option<&[u8; RATCHET_SIZE]> { - const ZERO_KEY: [u8; RATCHET_SIZE] = [0u8; RATCHET_SIZE]; - match self { - Null => None, - Empty => Some(&ZERO_KEY), - NonEmpty(rs) => Some(&rs.key), - } - } -} -/// A ratchet key and fingerprint, -/// along with the length of the ratchet chain the keys were derived from. -#[derive(Clone, Eq)] -pub struct NonEmptyRatchetState { - pub key: Zeroizing<[u8; RATCHET_SIZE]>, - pub fingerprint: Zeroizing<[u8; RATCHET_SIZE]>, - pub chain_len: u64, -} -impl PartialEq for NonEmptyRatchetState { - fn eq(&self, other: &Self) -> bool { - secure_eq(&self.key, &other.key) && secure_eq(&self.fingerprint, &other.fingerprint) && self.chain_len == other.chain_len + pub fn fingerprint_eq(&self, rf: &[u8; RATCHET_SIZE]) -> bool { + self.fingerprint.as_ref().map_or(false, |rf0| secure_eq(rf0, rf)) } } diff --git a/src/zeta.rs b/src/zeta.rs index 74026cd..0537945 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -50,7 +50,8 @@ pub(crate) struct Zeta { key_index: bool, keys: [DuplexKey; 2], - ratchet_states: [RatchetState; 2], + ratchet_state1: RatchetState, + ratchet_state2: Option, pub hk_send: Zeroizing<[u8; AES_256_KEY_SIZE]>, resend_timer: i64, @@ -66,7 +67,6 @@ pub(crate) struct Zeta { pub struct Session(pub(crate) Mutex>); pub(crate) struct StateB2 { - /// Can never be Null. ratchet_state: RatchetState, kid_send: NonZeroU32, pub kid_recv: NonZeroU32, @@ -207,7 +207,8 @@ fn create_a1_state( rng: &Mutex, s_remote: &App::PublicKey, kid_recv: NonZeroU32, - ratchet_states: &[RatchetState; 2], + ratchet_state1: &RatchetState, + ratchet_state2: Option<&RatchetState>, identity: Vec, ) -> Option> { // <- s @@ -231,10 +232,11 @@ fn create_a1_state( noise.encrypt_and_hash_in_place(to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, 0), i, &mut x1); // Process message pattern 1 payload. let i = x1.len(); - for r in ratchet_states { - if let Some(rf) = r.fingerprint() { - x1.extend(rf); - } + if let Some(rf) = ratchet_state1.fingerprint.as_ref() { + x1.extend(rf.as_ref()); + } + if let Some(Some(rf)) = ratchet_state2.map(|rs| rs.fingerprint.as_ref()) { + x1.extend(rf.as_ref()); } noise.encrypt_and_hash_in_place(to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, 1), i, &mut x1); @@ -257,14 +259,14 @@ pub(crate) fn trans_to_a1( identity: Vec, send: impl FnOnce(&Packet), ) -> Result>, OpenError> { - let ratchet_states = app + let (ratchet_state1, ratchet_state2) = app .restore_by_identity(&s_remote, &application_data) .map_err(|e| OpenError::RatchetIoError(e))?; let mut session_map = ctx.session_map.lock().unwrap(); let kid_recv = gen_kid(session_map.deref(), ctx.rng.lock().unwrap().deref_mut()); - let a1 = create_a1_state(&ctx.rng, &s_remote, kid_recv, &ratchet_states, identity).ok_or(OpenError::InvalidPublicKey)?; + let a1 = create_a1_state(&ctx.rng, &s_remote, kid_recv, &ratchet_state1, ratchet_state2.as_ref(), identity).ok_or(OpenError::InvalidPublicKey)?; let packet = a1.packet.clone(); let (hk_recv, hk_send) = a1.noise.get_ask(LABEL_HEADER_KEY); @@ -281,7 +283,8 @@ pub(crate) fn trans_to_a1( defrag: DefragBuffer::new(Some(hk_recv)), key_index: true, keys: [DuplexKey::default(), DuplexKey::default()], - ratchet_states, + ratchet_state1, + ratchet_state2, hk_send, resend_timer: current_time + App::SETTINGS.resend_time as i64, timeout_timer: current_time + App::SETTINGS.initial_offer_timeout as i64, @@ -355,24 +358,26 @@ pub(crate) fn received_x1_trans( return Err(byzantine_fault!(FailedAuth, true)); } - let mut ratchet_state = RatchetState::Null; + let mut ratchet_state = None; while i + RATCHET_SIZE <= j { match app.restore_by_fingerprint((&x1[i..i + RATCHET_SIZE]).try_into().unwrap()) { - Ok(RatchetState::Null) | Ok(RatchetState::Empty) => {} - Ok(rs) => { - ratchet_state = rs; + Ok(None) => {} + Ok(Some(rs)) => { + ratchet_state = Some(rs); break; } Err(e) => return Err(ReceiveError::RatchetIoError(e)), } i += RATCHET_SIZE; } - if ratchet_state.is_null() { + let ratchet_state = if let Some(rs) = ratchet_state { + rs + } else { if app.hello_requires_recognized_ratchet() { return Err(byzantine_fault!(FailedAuth, true)); } - ratchet_state = RatchetState::Empty; - } + RatchetState::empty() + }; let (hk_send, hk_recv) = noise.get_ask(LABEL_HEADER_KEY); let mut x2 = Vec::new(); @@ -390,7 +395,7 @@ pub(crate) fn received_x1_trans( noise.mix_key(ekem1_secret.as_ref()); drop(ekem1_secret); // Process message pattern 2 psk2 token. - noise.mix_key_and_hash(ratchet_state.key().unwrap()); + noise.mix_key_and_hash(ratchet_state.key.as_ref()); // Process message pattern 2 payload. let session_map = ctx.session_map.lock().unwrap(); let kid_recv = gen_kid(session_map.deref(), ctx.rng.lock().unwrap().deref_mut()); @@ -490,19 +495,15 @@ pub(crate) fn received_x2_trans( NonZeroU32::new(u32::from_be_bytes(payload)).map(|kid2| (kid2, noise)) }; // Check first key. - let mut ratchet_i = 0; - let mut result = None; - let mut chain_len = 0; - if let Some(key) = zeta.ratchet_states[0].key() { - chain_len = zeta.ratchet_states[0].chain_len(); - result = test_ratchet_key(key); - } + let mut ratchet_i = 1; + let mut chain_len = zeta.ratchet_state1.chain_len; + let mut result = test_ratchet_key(zeta.ratchet_state1.key.as_ref()); // Check second key. if result.is_none() { - ratchet_i = 1; - if let Some(key) = zeta.ratchet_states[1].key() { - chain_len = zeta.ratchet_states[1].chain_len(); - result = test_ratchet_key(key); + ratchet_i = 2; + if let Some(rs) = zeta.ratchet_state2.as_ref() { + chain_len = rs.chain_len; + result = test_ratchet_key(rs.key.as_ref()); } } // Check zero key. @@ -529,14 +530,21 @@ pub(crate) fn received_x2_trans( noise.encrypt_and_hash_in_place(to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 0), i, &mut x3); let (rk, rf) = noise.get_ask(LABEL_RATCHET_STATE); - let new_ratchet_state = RatchetState::new_nonempty(rk, rf, chain_len + 1); + let new_ratchet_state = RatchetState::new(rk, rf, chain_len + 1); - let ratchet_to_preserve = &zeta.ratchet_states[ratchet_i]; + let (ratchet_to_preserve, ratchet_to_delete) = if ratchet_i == 1 { + (Some(&zeta.ratchet_state1), zeta.ratchet_state2.as_ref()) + } else { + (zeta.ratchet_state2.as_ref(), Some(&zeta.ratchet_state1)) + }; let result = app.save_ratchet_state( &zeta.s_remote, &zeta.application_data, - [&zeta.ratchet_states[0], &zeta.ratchet_states[1]], - [&new_ratchet_state, ratchet_to_preserve], + &new_ratchet_state, + ratchet_to_preserve, + Some(&new_ratchet_state), + ratchet_to_delete, + None, ); if let Err(e) = result { return Err(ReceiveError::RatchetIoError(e)); @@ -552,8 +560,8 @@ pub(crate) fn received_x2_trans( zeta.key_mut(true).send.nk = Some(nk_send); zeta.key_mut(true).recv.kek = Some(kek_recv); zeta.key_mut(true).recv.nk = Some(nk_recv); - zeta.ratchet_states[1] = zeta.ratchet_states[ratchet_i].clone(); - zeta.ratchet_states[0] = new_ratchet_state; + zeta.ratchet_state2 = Some(zeta.ratchet_state1.clone()); + zeta.ratchet_state1 = new_ratchet_state; let current_time = app.time(); zeta.key_creation_counter = zeta.send_counter; zeta.resend_timer = current_time + App::SETTINGS.resend_time as i64; @@ -630,15 +638,9 @@ pub(crate) fn received_x3_trans( if let Some((responder_disallows_downgrade, application_data)) = responder_disallows_downgrade { let result = app.restore_by_identity(&s_remote, &application_data); match result { - Ok(true_ratchet_states) => { - let mut has_match = false; - for rs in &true_ratchet_states { - if !rs.is_null() { - has_match |= &zeta.ratchet_state == rs; - } - } - if !has_match { - if !responder_disallows_downgrade && zeta.ratchet_state.is_empty() { + Ok((ratchet_state1, ratchet_state2)) => { + if (&zeta.ratchet_state != &ratchet_state1) & (Some(&zeta.ratchet_state) != ratchet_state2.as_ref()) { + if !responder_disallows_downgrade && zeta.ratchet_state.fingerprint.is_none() { // TODO: add some kind of warning callback or signal. } else { if !responder_silently_rejects { @@ -650,12 +652,15 @@ pub(crate) fn received_x3_trans( let (rk, rf) = noise.get_ask(LABEL_RATCHET_STATE); // We must make sure the ratchet key is saved before we transition. - let new_ratchet_state = RatchetState::new_nonempty(rk, rf, zeta.ratchet_state.chain_len() + 1); + let new_ratchet_state = RatchetState{ key: rk, fingerprint: Some(rf), chain_len: zeta.ratchet_state.chain_len + 1,}; let result = app.save_ratchet_state( &s_remote, &application_data, - [&true_ratchet_states[0], &true_ratchet_states[1]], - [&new_ratchet_state, &RatchetState::Null], + &new_ratchet_state, + None, + Some(&new_ratchet_state), + Some(&ratchet_state1), + ratchet_state2.as_ref(), ); if let Err(e) = result { return Err(ReceiveError::RatchetIoError(e)); @@ -690,7 +695,8 @@ pub(crate) fn received_x3_trans( key_creation_counter: INIT_COUNTER + 1, key_index: false, keys: [keys, DuplexKey::default()], - ratchet_states: [new_ratchet_state, RatchetState::Null], + ratchet_state1: new_ratchet_state, + ratchet_state2: None, hk_send: zeta.hk_send.clone(), resend_timer: current_time + App::SETTINGS.resend_time as i64, timeout_timer: current_time + App::SETTINGS.rekey_timeout as i64, @@ -750,19 +756,22 @@ pub(crate) fn received_c1_trans( let just_establised = is_other && matches!(&zeta.beta, ZsspAutomata::A3 { .. }); if is_other { if let ZsspAutomata::A3 { .. } | ZsspAutomata::R2 { .. } = &zeta.beta { - if !zeta.ratchet_states[1].is_null() { + if zeta.ratchet_state2.is_some() { let result = app.save_ratchet_state( &zeta.s_remote, &zeta.application_data, - [&zeta.ratchet_states[0], &zeta.ratchet_states[1]], - [&zeta.ratchet_states[0], &RatchetState::Null], + &zeta.ratchet_state1, + None, + None, + zeta.ratchet_state2.as_ref(), + None, ); if let Err(e) = result { return Err(ReceiveError::RatchetIoError(e)); } } - zeta.ratchet_states[1] = RatchetState::Null; + zeta.ratchet_state2 = None; zeta.key_index ^= true; zeta.timeout_timer = app.time() + App::SETTINGS @@ -928,7 +937,7 @@ fn timeout_trans( } let new_kid_recv = remap(session, &zeta, &ctx.rng, &ctx.session_map); - if let Some(a1) = create_a1_state(&ctx.rng, &zeta.s_remote, new_kid_recv, &zeta.ratchet_states, identity.clone()) { + if let Some(a1) = create_a1_state(&ctx.rng, &zeta.s_remote, new_kid_recv, &zeta.ratchet_state1, zeta.ratchet_state2.as_ref(), identity.clone()) { let (hk_recv, hk_send) = a1.noise.get_ask(LABEL_HEADER_KEY); let packet = a1.packet.clone(); @@ -958,7 +967,7 @@ fn timeout_trans( noise.mix_hash(&ctx.s_secret.public_key_bytes()); noise.mix_hash(&zeta.s_remote.to_bytes()); // Process message pattern 1 psk0 token. - noise.mix_key_and_hash(zeta.ratchet_states[0].key().unwrap()); + noise.mix_key_and_hash(zeta.ratchet_state1.key.as_ref()); // Process message pattern 1 e token. let e_secret = noise.write_e(&ctx.rng, &mut k1); // Process message pattern 1 es token. @@ -1056,7 +1065,7 @@ pub(crate) fn received_k1_trans( noise.mix_hash(&zeta.s_remote.to_bytes()); noise.mix_hash(&s_secret.public_key_bytes()); // Process message pattern 1 psk0 token. - noise.mix_key_and_hash(zeta.ratchet_states[0].key().unwrap()); + noise.mix_key_and_hash(zeta.ratchet_state1.key.as_ref()); // Process message pattern 1 e token. let e_remote = noise.read_e(&mut i, &k1).ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 1 es token. @@ -1086,12 +1095,15 @@ pub(crate) fn received_k1_trans( noise.encrypt_and_hash_in_place(to_nonce(PACKET_TYPE_REKEY_COMPLETE, 0), i, &mut k2); let (rk, rf) = noise.get_ask(LABEL_RATCHET_STATE); - let new_ratchet_state = RatchetState::new_nonempty(rk, rf, zeta.ratchet_states[0].chain_len() + 1); + let new_ratchet_state = RatchetState::new(rk, rf, zeta.ratchet_state1.chain_len + 1); let result = app.save_ratchet_state( &zeta.s_remote, &zeta.application_data, - [&zeta.ratchet_states[0], &zeta.ratchet_states[1]], - [&new_ratchet_state, &zeta.ratchet_states[0]], + &new_ratchet_state, + Some(&zeta.ratchet_state1), + Some(&new_ratchet_state), + zeta.ratchet_state2.as_ref(), + None, ); if let Err(e) = result { return Err(ReceiveError::RatchetIoError(e)); @@ -1105,8 +1117,8 @@ pub(crate) fn received_k1_trans( zeta.key_mut(true).recv.kid = Some(new_kid_recv); zeta.key_mut(true).recv.kek = Some(kek_recv); zeta.key_mut(true).recv.nk = Some(nk_recv); - zeta.ratchet_states[1] = zeta.ratchet_states[0].clone(); - zeta.ratchet_states[0] = new_ratchet_state; + zeta.ratchet_state2 = Some(zeta.ratchet_state1.clone()); + zeta.ratchet_state1 = new_ratchet_state; let current_time = app.time(); zeta.key_creation_counter = zeta.send_counter; zeta.timeout_timer = current_time + App::SETTINGS.rekey_timeout as i64; @@ -1179,12 +1191,15 @@ pub(crate) fn received_k2_trans( let kid_send = NonZeroU32::new(u32::from_be_bytes(k2[i..j].try_into().unwrap())).ok_or(byzantine_fault!(InvalidPacket, true))?; let (rk, rf) = noise.get_ask(LABEL_RATCHET_STATE); - let new_ratchet_state = RatchetState::new_nonempty(rk, rf, zeta.ratchet_states[0].chain_len() + 1); + let new_ratchet_state = RatchetState::new(rk, rf, zeta.ratchet_state1.chain_len + 1); let result = app.save_ratchet_state( &zeta.s_remote, &zeta.application_data, - [&zeta.ratchet_states[0], &zeta.ratchet_states[1]], - [&new_ratchet_state, &RatchetState::Null], + &new_ratchet_state, + None, + Some(&new_ratchet_state), + Some(&zeta.ratchet_state1), + zeta.ratchet_state2.as_ref(), ); if let Err(e) = result { return Err(ReceiveError::RatchetIoError(e)); @@ -1197,7 +1212,7 @@ pub(crate) fn received_k2_trans( zeta.key_mut(true).send.nk = Some(nk_send); zeta.key_mut(true).recv.kek = Some(kek_recv); zeta.key_mut(true).recv.nk = Some(nk_recv); - zeta.ratchet_states[0] = new_ratchet_state; + zeta.ratchet_state1 = new_ratchet_state; zeta.key_index ^= true; let current_time = app.time(); zeta.key_creation_counter = zeta.send_counter; From 56ee4002a46b708b87e15a744e4758334d80c6ff Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 2 Aug 2023 16:42:35 -0400 Subject: [PATCH 02/37] fixed error --- examples/basic_test_copy.rs | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/examples/basic_test_copy.rs b/examples/basic_test_copy.rs index 4199295..943303b 100644 --- a/examples/basic_test_copy.rs +++ b/examples/basic_test_copy.rs @@ -8,6 +8,7 @@ use std::collections::HashMap; use std::iter::ExactSizeIterator; +use std::ops::Deref; use std::str::FromStr; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{mpsc, Arc, Mutex}; @@ -36,6 +37,14 @@ struct Ratchets { rf_map: HashMap<[u8; RATCHET_SIZE], RatchetState>, peer_map: HashMap)>, } +impl Ratchets { + fn new() -> Self { + Self { + rf_map: HashMap::new(), + peer_map: HashMap::new(), + } + } +} #[allow(unused)] impl zssp_proto::ApplicationLayer for &TestApplication { @@ -78,7 +87,7 @@ impl zssp_proto::ApplicationLayer for &TestApplication { fn restore_by_identity(&self, remote_static_key: &Self::PublicKey, application_data: &Self::Data) -> Result<(RatchetState, Option), Self::DiskError> { let ratchets = self.ratchets.lock().unwrap(); - Ok(ratchets.peer_map.get(application_data).cloned()) + Ok(ratchets.peer_map.get(application_data).cloned().unwrap_or_else(|| (RatchetState::empty(), None))) } fn save_ratchet_state( @@ -92,11 +101,18 @@ impl zssp_proto::ApplicationLayer for &TestApplication { state_deleted2: Option<&RatchetState>, ) -> Result<(), Self::DiskError> { let mut ratchets = self.ratchets.lock().unwrap(); - ratchets.0 = state1.clone(); - ratchets.1 = state2.cloned(); + ratchets.peer_map.insert(*application_data, (state1.clone(), state2.cloned())); + // TODO: change the API to get rid of unwrap. if let Some(rs) = state_added { + ratchets.rf_map.insert(*rs.fingerprint.as_ref().unwrap().deref(), rs.clone()); println!("[{}] new ratchet #{}", self.name, rs.chain_len); } + if let Some(rs) = state_deleted1 { + ratchets.rf_map.remove(rs.fingerprint.as_ref().unwrap().deref()); + } + if let Some(rs) = state_deleted2 { + ratchets.rf_map.remove(rs.fingerprint.as_ref().unwrap().deref()); + } Ok(()) } @@ -131,7 +147,7 @@ fn alice_main( up = false; alice_session = Some( context - .open(alice_app, |b| alice_out.send(b).is_ok(), TEST_MTU, bob_pubkey.clone(), (), Vec::new()) + .open(alice_app, |b| alice_out.send(b).is_ok(), TEST_MTU, bob_pubkey.clone(), 0, Vec::new()) .unwrap(), ); println!("[alice] opening session"); @@ -286,19 +302,18 @@ fn bob_main( fn core(time: u64, packet_success_rate: u32) { let run = &AtomicBool::new(true); - let shared_ratchet_state = RatchetState::new_from_otp::(b"password1"); let alice_keypair = EphemeralSecret::random(&mut OsRng); let alice_app = TestApplication { time: Instant::now(), name: "alice", - ratchets: Mutex::new((shared_ratchet_state.clone(), None)), + ratchets: Mutex::new(Ratchets::new()), }; let bob_keypair = EphemeralSecret::random(&mut OsRng); let bob_pubkey = bob_keypair.public_key(); let bob_app = TestApplication { time: Instant::now(), name: "bob", - ratchets: Mutex::new((shared_ratchet_state, None)), + ratchets: Mutex::new(Ratchets::new()), }; let (alice_out, bob_in) = mpsc::sync_channel::>(256); From e8a85d484bd6b22ee9c982f6e057aa014e8a5134 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Thu, 3 Aug 2023 11:24:27 -0400 Subject: [PATCH 03/37] finished new api --- examples/basic_test.rs | 53 ++++-- examples/basic_test_copy.rs | 363 ------------------------------------ src/applicationlayer.rs | 15 +- src/ratchet_state.rs | 21 ++- src/zeta.rs | 8 +- 5 files changed, 69 insertions(+), 391 deletions(-) delete mode 100644 examples/basic_test_copy.rs diff --git a/examples/basic_test.rs b/examples/basic_test.rs index b24402c..8a031a1 100644 --- a/examples/basic_test.rs +++ b/examples/basic_test.rs @@ -6,6 +6,7 @@ * https://www.zerotier.com/ */ +use std::collections::HashMap; use std::iter::ExactSizeIterator; use std::str::FromStr; use std::sync::atomic::{AtomicBool, Ordering}; @@ -28,7 +29,20 @@ const TEST_MTU: usize = 1500; struct TestApplication { time: Instant, name: &'static str, - ratchets: Mutex<(RatchetState, Option)>, + ratchets: Mutex, +} + +struct Ratchets { + rf_map: HashMap<[u8; RATCHET_SIZE], RatchetState>, + peer_map: HashMap)>, +} +impl Ratchets { + fn new() -> Self { + Self { + rf_map: HashMap::new(), + peer_map: HashMap::new(), + } + } } #[allow(unused)] @@ -51,10 +65,10 @@ impl zssp_proto::ApplicationLayer for &TestApplication { type Kem = [u8; pqc_kyber::KYBER_SECRETKEYBYTES]; type DiskError = (); - type Data = (); + type Data = u128; fn hello_requires_recognized_ratchet(&self) -> bool { - true + false } fn initiator_disallows_downgrade(&self, session: &Arc>) -> bool { @@ -62,22 +76,17 @@ impl zssp_proto::ApplicationLayer for &TestApplication { } fn check_accept_session(&self, remote_static_key: &Self::PublicKey, identity: &[u8]) -> (Option<(bool, Self::Data)>, bool) { - (Some((true, ())), true) + (Some((true, 1)), true) } fn restore_by_fingerprint(&self, ratchet_fingerprint: &[u8; RATCHET_SIZE]) -> Result, Self::DiskError> { let ratchets = self.ratchets.lock().unwrap(); - if ratchets.0.fingerprint_eq(ratchet_fingerprint) { - return Ok(Some(ratchets.0.clone())); - } - if ratchets.1.as_ref().map_or(false, |rs| rs.fingerprint_eq(ratchet_fingerprint)) { - return Ok(ratchets.1.clone()); - } - Ok(None) + Ok(ratchets.rf_map.get(ratchet_fingerprint).cloned()) } fn restore_by_identity(&self, remote_static_key: &Self::PublicKey, application_data: &Self::Data) -> Result<(RatchetState, Option), Self::DiskError> { - Ok(self.ratchets.lock().unwrap().clone()) + let ratchets = self.ratchets.lock().unwrap(); + Ok(ratchets.peer_map.get(application_data).cloned().unwrap_or_else(|| RatchetState::new_initial_states())) } fn save_ratchet_state( @@ -91,11 +100,18 @@ impl zssp_proto::ApplicationLayer for &TestApplication { state_deleted2: Option<&RatchetState>, ) -> Result<(), Self::DiskError> { let mut ratchets = self.ratchets.lock().unwrap(); - ratchets.0 = state1.clone(); - ratchets.1 = state2.cloned(); + ratchets.peer_map.insert(*application_data, (state1.clone(), state2.cloned())); + if let Some(rs) = state_added { + ratchets.rf_map.insert(*rs.fingerprint().unwrap(), rs.clone()); println!("[{}] new ratchet #{}", self.name, rs.chain_len); } + if let Some(Some(rf)) = state_deleted1.map(|rs| rs.fingerprint()) { + ratchets.rf_map.remove(rf); + } + if let Some(Some(rf)) = state_deleted1.map(|rs| rs.fingerprint()) { + ratchets.rf_map.remove(rf); + } Ok(()) } @@ -130,7 +146,7 @@ fn alice_main( up = false; alice_session = Some( context - .open(alice_app, |b| alice_out.send(b).is_ok(), TEST_MTU, bob_pubkey.clone(), (), Vec::new()) + .open(alice_app, |b| alice_out.send(b).is_ok(), TEST_MTU, bob_pubkey.clone(), 0, Vec::new()) .unwrap(), ); println!("[alice] opening session"); @@ -194,7 +210,7 @@ fn alice_main( thread::sleep(Duration::from_millis(10)); } // TODO: we need to more comprehensively test if re-opening the session works - if OsRng.next_u32() <= ((u32::MAX as f64) * 0.0000025) as u32 { + if OsRng.next_u32() <= ((u32::MAX as f64) * 0.000005) as u32 { alice_session = None; } @@ -285,19 +301,18 @@ fn bob_main( fn core(time: u64, packet_success_rate: u32) { let run = &AtomicBool::new(true); - let shared_ratchet_state = RatchetState::new_from_otp::(b"password1"); let alice_keypair = EphemeralSecret::random(&mut OsRng); let alice_app = TestApplication { time: Instant::now(), name: "alice", - ratchets: Mutex::new((shared_ratchet_state.clone(), None)), + ratchets: Mutex::new(Ratchets::new()), }; let bob_keypair = EphemeralSecret::random(&mut OsRng); let bob_pubkey = bob_keypair.public_key(); let bob_app = TestApplication { time: Instant::now(), name: "bob", - ratchets: Mutex::new((shared_ratchet_state, None)), + ratchets: Mutex::new(Ratchets::new()), }; let (alice_out, bob_in) = mpsc::sync_channel::>(256); diff --git a/examples/basic_test_copy.rs b/examples/basic_test_copy.rs deleted file mode 100644 index 943303b..0000000 --- a/examples/basic_test_copy.rs +++ /dev/null @@ -1,363 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ - -use std::collections::HashMap; -use std::iter::ExactSizeIterator; -use std::ops::Deref; -use std::str::FromStr; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{mpsc, Arc, Mutex}; -use std::thread; -use std::time::{Duration, Instant}; - -use aes::Aes256; -use aes_gcm::Aes256Gcm; -use p384::{ecdh::EphemeralSecret, PublicKey}; -use rand_core::OsRng; -use rand_core::RngCore; -use sha2::Sha512; - -use zssp_proto::ratchet_state::RatchetState; -use zssp_proto::{Session, Settings, RATCHET_SIZE}; - -const TEST_MTU: usize = 1500; - -struct TestApplication { - time: Instant, - name: &'static str, - ratchets: Mutex, -} - -struct Ratchets { - rf_map: HashMap<[u8; RATCHET_SIZE], RatchetState>, - peer_map: HashMap)>, -} -impl Ratchets { - fn new() -> Self { - Self { - rf_map: HashMap::new(), - peer_map: HashMap::new(), - } - } -} - -#[allow(unused)] -impl zssp_proto::ApplicationLayer for &TestApplication { - const SETTINGS: Settings = Settings { - initial_offer_timeout: Settings::INITIAL_OFFER_TIMEOUT_MS, - rekey_timeout: 60 * 1000, - rekey_after_time: 3000, - rekey_time_max_jitter: 1000, - rekey_after_key_uses: Settings::REKEY_AFTER_KEY_USES, - resend_time: 250, - fragment_assembly_timeout: Settings::FRAGMENT_ASSEMBLY_TIMEOUT_MS, - }; - type Rng = OsRng; - type Prp = Aes256; - type Aead = Aes256Gcm; - type Hash = Sha512; - type PublicKey = PublicKey; - type KeyPair = EphemeralSecret; - type Kem = [u8; pqc_kyber::KYBER_SECRETKEYBYTES]; - - type DiskError = (); - type Data = u128; - - fn hello_requires_recognized_ratchet(&self) -> bool { - true - } - - fn initiator_disallows_downgrade(&self, session: &Arc>) -> bool { - true - } - - fn check_accept_session(&self, remote_static_key: &Self::PublicKey, identity: &[u8]) -> (Option<(bool, Self::Data)>, bool) { - (Some((true, 0)), true) - } - - fn restore_by_fingerprint(&self, ratchet_fingerprint: &[u8; RATCHET_SIZE]) -> Result, Self::DiskError> { - let ratchets = self.ratchets.lock().unwrap(); - Ok(ratchets.rf_map.get(ratchet_fingerprint).cloned()) - } - - fn restore_by_identity(&self, remote_static_key: &Self::PublicKey, application_data: &Self::Data) -> Result<(RatchetState, Option), Self::DiskError> { - let ratchets = self.ratchets.lock().unwrap(); - Ok(ratchets.peer_map.get(application_data).cloned().unwrap_or_else(|| (RatchetState::empty(), None))) - } - - fn save_ratchet_state( - &self, - remote_static_key: &Self::PublicKey, - application_data: &Self::Data, - state1: &RatchetState, - state2: Option<&RatchetState>, - state_added: Option<&RatchetState>, - state_deleted1: Option<&RatchetState>, - state_deleted2: Option<&RatchetState>, - ) -> Result<(), Self::DiskError> { - let mut ratchets = self.ratchets.lock().unwrap(); - ratchets.peer_map.insert(*application_data, (state1.clone(), state2.cloned())); - // TODO: change the API to get rid of unwrap. - if let Some(rs) = state_added { - ratchets.rf_map.insert(*rs.fingerprint.as_ref().unwrap().deref(), rs.clone()); - println!("[{}] new ratchet #{}", self.name, rs.chain_len); - } - if let Some(rs) = state_deleted1 { - ratchets.rf_map.remove(rs.fingerprint.as_ref().unwrap().deref()); - } - if let Some(rs) = state_deleted2 { - ratchets.rf_map.remove(rs.fingerprint.as_ref().unwrap().deref()); - } - Ok(()) - } - - fn time(&self) -> i64 { - self.time.elapsed().as_millis() as i64 - } - - fn event_log(&self, event: zssp_proto::LogEvent) { - println!(">[{}] {:?}", self.name, event); - } -} - -fn alice_main( - run: &AtomicBool, - packet_success_rate: u32, - alice_app: &TestApplication, - alice_out: mpsc::SyncSender>, - alice_in: mpsc::Receiver>, - recursive_out: mpsc::SyncSender>, - alice_keypair: EphemeralSecret, - bob_pubkey: PublicKey, -) { - let startup_time = std::time::Instant::now(); - let context = zssp_proto::Context::<&TestApplication>::new(alice_keypair, OsRng); - let mut next_service = startup_time.elapsed().as_millis() as i64 + 500; - let test_data = [1u8; TEST_MTU * 10]; - let mut up = false; - let mut alice_session = None; - - while run.load(Ordering::Relaxed) { - if alice_session.is_none() { - up = false; - alice_session = Some( - context - .open(alice_app, |b| alice_out.send(b).is_ok(), TEST_MTU, bob_pubkey.clone(), 0, Vec::new()) - .unwrap(), - ); - println!("[alice] opening session"); - } - let current_time = startup_time.elapsed().as_millis() as i64; - loop { - let pkt = alice_in.try_recv(); - if let Ok(pkt) = pkt { - if OsRng.next_u32() <= packet_success_rate { - use zssp_proto::result::ReceiveError::*; - use zssp_proto::result::ReceiveOk::*; - use zssp_proto::result::SessionEvent::*; - match context.receive( - alice_app, - |b| alice_out.send(b).is_ok(), - TEST_MTU, - |_| Some((|b| alice_out.send(b).is_ok(), TEST_MTU)), - &0, - pkt, - ) { - Ok(Unassociated) => { - //println!("[alice] ok"); - } - Ok(Session(_, event)) => match event { - Established => { - up = true; - } - Data(data) => { - assert!(!data.is_empty()); - //println!("[alice] received {}", data.len()); - } - NewSession => panic!(), - Rejected => panic!(), - Control => (), - }, - Err(e) => { - println!("[alice] ERROR {:?}", e); - if let ByzantineFault { unnatural, .. } = e { - assert!(!unnatural) - } - } - } - } else if OsRng.next_u32() | 1 > 0 { - let _ = recursive_out.send(pkt); - } - } else { - break; - } - } - - if up { - context - .send( - alice_session.as_ref().unwrap(), - |b| alice_out.send(b).is_ok(), - TEST_MTU, - test_data[..1400 + ((OsRng.next_u64() as usize) % (test_data.len() - 1400))].to_vec(), - ) - .unwrap(); - } else { - thread::sleep(Duration::from_millis(10)); - } - // TODO: we need to more comprehensively test if re-opening the session works - if OsRng.next_u32() <= ((u32::MAX as f64) * 0.0000025) as u32 { - alice_session = None; - } - - if current_time >= next_service { - next_service = current_time + context.service(alice_app, |_| Some((|b| alice_out.send(b).is_ok(), TEST_MTU))); - } - } -} - -fn bob_main( - run: &AtomicBool, - packet_success_rate: u32, - bob_app: &TestApplication, - bob_out: mpsc::SyncSender>, - bob_in: mpsc::Receiver>, - recursive_out: mpsc::SyncSender>, - bob_keypair: EphemeralSecret, -) { - let startup_time = std::time::Instant::now(); - let context = zssp_proto::Context::<&TestApplication>::new(bob_keypair, OsRng); - let mut last_speed_metric = startup_time.elapsed().as_millis() as i64; - let mut next_service = last_speed_metric + 500; - let mut transferred = 0u64; - - let mut bob_session = None; - - while run.load(Ordering::Relaxed) { - let pkt = bob_in.recv_timeout(Duration::from_millis(100)); - let current_time = startup_time.elapsed().as_millis() as i64; - - if let Ok(pkt) = pkt { - if OsRng.next_u32() <= packet_success_rate { - use zssp_proto::result::ReceiveError::*; - use zssp_proto::result::ReceiveOk::*; - use zssp_proto::result::SessionEvent::*; - match context.receive( - bob_app, - |b| bob_out.send(b).is_ok(), - TEST_MTU, - |_| Some((|b| bob_out.send(b).is_ok(), TEST_MTU)), - &0, - pkt, - ) { - Ok(Unassociated) => {} - Ok(Session(s, event)) => match event { - NewSession => { - println!("[bob] new session, took {}s", current_time as f32 / 1000.0); - let _ = bob_session.replace(s); - } - Data(data) => { - assert!(!data.is_empty()); - //println!("[bob] received {}", data.len()); - transferred += data.len() as u64 * 2; // *2 because we are also sending this many bytes back - context.send(&s, |b| bob_out.send(b).is_ok(), TEST_MTU, data).unwrap(); - } - Established => panic!(), - Rejected => panic!(), - Control => (), - }, - Err(e) => { - println!("[bob] ERROR {:?}", e); - if let ByzantineFault { unnatural, .. } = e { - assert!(!unnatural) - } - } - } - } else if OsRng.next_u32() | 1 > 0 { - let _ = recursive_out.try_send(pkt); - } - } - - let speed_metric_elapsed = current_time - last_speed_metric; - if speed_metric_elapsed >= 10000 { - last_speed_metric = current_time; - println!( - "[bob] throughput: {} MiB/sec (combined input and output)", - ((transferred as f64) / 1048576.0) / ((speed_metric_elapsed as f64) / 1000.0) - ); - transferred = 0; - } - - if current_time >= next_service { - next_service = current_time + context.service(bob_app, |_| Some((|b| bob_out.send(b).is_ok(), TEST_MTU))); - } - } -} - -fn core(time: u64, packet_success_rate: u32) { - let run = &AtomicBool::new(true); - - let alice_keypair = EphemeralSecret::random(&mut OsRng); - let alice_app = TestApplication { - time: Instant::now(), - name: "alice", - ratchets: Mutex::new(Ratchets::new()), - }; - let bob_keypair = EphemeralSecret::random(&mut OsRng); - let bob_pubkey = bob_keypair.public_key(); - let bob_app = TestApplication { - time: Instant::now(), - name: "bob", - ratchets: Mutex::new(Ratchets::new()), - }; - - let (alice_out, bob_in) = mpsc::sync_channel::>(256); - let (bob_out, alice_in) = mpsc::sync_channel::>(256); - - thread::scope(|ts| { - { - let alice_out = alice_out.clone(); - let bob_out = bob_out.clone(); - ts.spawn(move || { - alice_main( - run, - packet_success_rate, - &alice_app, - alice_out, - alice_in, - bob_out, - alice_keypair, - bob_pubkey, - ) - }); - } - ts.spawn(move || bob_main(run, packet_success_rate, &bob_app, bob_out, bob_in, alice_out, bob_keypair)); - - thread::sleep(Duration::from_secs(time)); - - run.store(false, Ordering::SeqCst); - println!("finished"); - }); -} - -fn main() { - let args = std::env::args(); - let packet_success_rate = if args.len() <= 1 { - let default_success_rate = 1.0; - ((u32::MAX as f64) * default_success_rate) as u32 - } else { - ((u32::MAX as f64) * f64::from_str(args.last().unwrap().as_str()).unwrap()) as u32 - }; - - core(60 * 60, packet_success_rate) -} - -#[test] -fn test_main() { - core(2, u32::MAX / 2) -} diff --git a/src/applicationlayer.rs b/src/applicationlayer.rs index 2d0d78a..86f7245 100644 --- a/src/applicationlayer.rs +++ b/src/applicationlayer.rs @@ -166,8 +166,15 @@ pub trait ApplicationLayer: Sized { /// This function will be called whenever Alice attempts to open a session, or Bob attempts /// to verify Alice's identity. fn restore_by_identity(&self, remote_static_key: &Self::PublicKey, application_data: &Self::Data) -> Result<(RatchetState, Option), Self::DiskError>; - /// Atomically save the given `new_ratchet_states` to persistent storage. - /// `pre_ratchet_states` contains what should be the previous contents of persistent storage. + /// Atomically save `current_state1` and `current_state2` so that them and only them can be + /// restored with `restore_by_identity` and `restore_by_fingerprint` through a system restart. + /// Theses should overwrite the previous ratchet states 1 and 2 saved to storage. + /// + /// `state_added` will be equal to the brand new ratchet state that was added in this update, + /// or `None` if there is not a new ratchet state this update. `state_deleted1` and + /// `state_deleted2` will be equal to any ratchet states that are to be deleted and overwritten + /// as a result of this update, or `None` if there is not one to be deleted. + /// `state_added` will always have a non-empty (`Some()`) ratchet fingerprint. /// /// If this returns `Err(IoError)`, the packet which triggered this function to be called will be /// dropped, and no session state will be mutated, preserving synchronization. The remote peer @@ -186,8 +193,8 @@ pub trait ApplicationLayer: Sized { &self, remote_static_key: &Self::PublicKey, application_data: &Self::Data, - state1: &RatchetState, - state2: Option<&RatchetState>, + current_state1: &RatchetState, + current_state2: Option<&RatchetState>, state_added: Option<&RatchetState>, state_deleted1: Option<&RatchetState>, state_deleted2: Option<&RatchetState>, diff --git a/src/ratchet_state.rs b/src/ratchet_state.rs index 8c26086..ef73229 100644 --- a/src/ratchet_state.rs +++ b/src/ratchet_state.rs @@ -24,10 +24,12 @@ impl RatchetState { pub fn new(key: Zeroizing<[u8; RATCHET_SIZE]>, fingerprint: Zeroizing<[u8; RATCHET_SIZE]>, chain_len: u64) -> Self { RatchetState { key, fingerprint: Some(fingerprint), chain_len } } + pub fn new_raw(key: [u8; RATCHET_SIZE], fingerprint: [u8; RATCHET_SIZE], chain_len: u64) -> Self { + RatchetState { key: Zeroizing::new(key), fingerprint: Some(Zeroizing::new(fingerprint)), chain_len } + } pub fn empty() -> Self { RatchetState { key: Zeroizing::new([0u8; RATCHET_SIZE]), fingerprint: None, chain_len: 0 } } - pub fn new_from_otp(otp: &[u8]) -> RatchetState { let mut buffer = Vec::new(); buffer.push(1); @@ -43,10 +45,27 @@ impl RatchetState { 1, ) } + + pub fn new_initial_states() -> (RatchetState, Option) { + ( + RatchetState::empty(), + None + ) + } + pub fn new_otp_states(otp: &[u8]) -> (RatchetState, Option) { + ( + RatchetState::new_from_otp::(otp), + None + ) + } + pub fn is_empty(&self) -> bool { self.fingerprint.is_none() } pub fn fingerprint_eq(&self, rf: &[u8; RATCHET_SIZE]) -> bool { self.fingerprint.as_ref().map_or(false, |rf0| secure_eq(rf0, rf)) } + pub fn fingerprint(&self) -> Option<&[u8; RATCHET_SIZE]> { + self.fingerprint.as_deref() + } } diff --git a/src/zeta.rs b/src/zeta.rs index 0537945..c6d6019 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -232,10 +232,10 @@ fn create_a1_state( noise.encrypt_and_hash_in_place(to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, 0), i, &mut x1); // Process message pattern 1 payload. let i = x1.len(); - if let Some(rf) = ratchet_state1.fingerprint.as_ref() { + if let Some(rf) = ratchet_state1.fingerprint() { x1.extend(rf.as_ref()); } - if let Some(Some(rf)) = ratchet_state2.map(|rs| rs.fingerprint.as_ref()) { + if let Some(Some(rf)) = ratchet_state2.map(|rs| rs.fingerprint()) { x1.extend(rf.as_ref()); } noise.encrypt_and_hash_in_place(to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, 1), i, &mut x1); @@ -640,7 +640,7 @@ pub(crate) fn received_x3_trans( match result { Ok((ratchet_state1, ratchet_state2)) => { if (&zeta.ratchet_state != &ratchet_state1) & (Some(&zeta.ratchet_state) != ratchet_state2.as_ref()) { - if !responder_disallows_downgrade && zeta.ratchet_state.fingerprint.is_none() { + if !responder_disallows_downgrade && zeta.ratchet_state.fingerprint().is_none() { // TODO: add some kind of warning callback or signal. } else { if !responder_silently_rejects { @@ -652,7 +652,7 @@ pub(crate) fn received_x3_trans( let (rk, rf) = noise.get_ask(LABEL_RATCHET_STATE); // We must make sure the ratchet key is saved before we transition. - let new_ratchet_state = RatchetState{ key: rk, fingerprint: Some(rf), chain_len: zeta.ratchet_state.chain_len + 1,}; + let new_ratchet_state = RatchetState::new(rk, rf, zeta.ratchet_state.chain_len + 1); let result = app.save_ratchet_state( &s_remote, &application_data, From 5c92d541470b7c709c9aa5506da0b8e3f24d5367 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Thu, 3 Aug 2023 11:26:08 -0400 Subject: [PATCH 04/37] added docs --- src/applicationlayer.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/applicationlayer.rs b/src/applicationlayer.rs index 86f7245..133297a 100644 --- a/src/applicationlayer.rs +++ b/src/applicationlayer.rs @@ -174,7 +174,8 @@ pub trait ApplicationLayer: Sized { /// or `None` if there is not a new ratchet state this update. `state_deleted1` and /// `state_deleted2` will be equal to any ratchet states that are to be deleted and overwritten /// as a result of this update, or `None` if there is not one to be deleted. - /// `state_added` will always have a non-empty (`Some()`) ratchet fingerprint. + /// `state_added` will always have a non-empty (`Some()`) ratchet fingerprint, and it will + /// always be equal to `current_state1`. /// /// If this returns `Err(IoError)`, the packet which triggered this function to be called will be /// dropped, and no session state will be mutated, preserving synchronization. The remote peer From 14db9cd756829011ac290f8cbd1e6564b5e97704 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Thu, 3 Aug 2023 12:33:24 -0400 Subject: [PATCH 05/37] added docs --- src/applicationlayer.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/applicationlayer.rs b/src/applicationlayer.rs index 133297a..a9dae59 100644 --- a/src/applicationlayer.rs +++ b/src/applicationlayer.rs @@ -162,9 +162,17 @@ pub trait ApplicationLayer: Sized { /// to the empty ratchet key, restarting the ratchet chain. /// If `RatchetAction::FailAuthentication` is returned Alice's connection will be silently dropped. fn restore_by_fingerprint(&self, ratchet_fingerprint: &[u8; RATCHET_SIZE]) -> Result, Self::DiskError>; - /// Lookup a specific ratchet state based on the identity of the peer being communicated with. + /// Lookup the specific ratchet states based on the identity of the peer being communicated with. /// This function will be called whenever Alice attempts to open a session, or Bob attempts /// to verify Alice's identity. + /// + /// If the peer's ratchet states could not be could, this function should return either + /// `RatchetState::new_initial_states()`, or `RatchetState::new_otp_states(...)` if a one time + /// password has been shared with this peer. + /// + /// This function is not responsible for deciding whether or not to connect to this remote peer. + /// Filtering peers should be done by the caller to `Context::open` as well as by the + /// function `ApplicationLayer::check_accept_session`. fn restore_by_identity(&self, remote_static_key: &Self::PublicKey, application_data: &Self::Data) -> Result<(RatchetState, Option), Self::DiskError>; /// Atomically save `current_state1` and `current_state2` so that them and only them can be /// restored with `restore_by_identity` and `restore_by_fingerprint` through a system restart. From fc1e86778ce49ce651e52d1c3eef4129f919cedb Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Thu, 3 Aug 2023 12:46:23 -0400 Subject: [PATCH 06/37] improved docs --- src/applicationlayer.rs | 9 ++++++--- src/zeta.rs | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/applicationlayer.rs b/src/applicationlayer.rs index a9dae59..2e1e113 100644 --- a/src/applicationlayer.rs +++ b/src/applicationlayer.rs @@ -166,9 +166,12 @@ pub trait ApplicationLayer: Sized { /// This function will be called whenever Alice attempts to open a session, or Bob attempts /// to verify Alice's identity. /// - /// If the peer's ratchet states could not be could, this function should return either - /// `RatchetState::new_initial_states()`, or `RatchetState::new_otp_states(...)` if a one time - /// password has been shared with this peer. + /// If the peer's ratchet states could not be could, this function should return + /// `RatchetState::new_initial_states()`. + /// + /// If a one-time-password has been pre-shared with this peer, `RatchetState::new_otp_states(...)` + /// should be pre-saved to the storage backend as if it is a normal ratchet state. + /// This is to ensure it can both be restored and eventually deleted when it is used. /// /// This function is not responsible for deciding whether or not to connect to this remote peer. /// Filtering peers should be done by the caller to `Context::open` as well as by the diff --git a/src/zeta.rs b/src/zeta.rs index c6d6019..3542103 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -524,7 +524,7 @@ pub(crate) fn received_x2_trans( noise.encrypt_and_hash_in_place(to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 1), i, &mut x3); // Process message pattern 3 se token. noise.mix_dh(&ctx.s_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; - // Process message pattern 3 payload token. + // Process message pattern 3 payload. let i = x3.len(); x3.extend(identity); noise.encrypt_and_hash_in_place(to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 0), i, &mut x3); @@ -980,7 +980,7 @@ fn timeout_trans( zeta.expire(); return; } - // Process message pattern 1 payload token. + // Process message pattern 1 payload. let i = k1.len(); k1.extend(&new_kid_recv.get().to_be_bytes()); noise.encrypt_and_hash_in_place(to_nonce(PACKET_TYPE_REKEY_INIT, 0), i, &mut k1); From 76eb9636ea261fa5b6092e409a03467a1e5aaf93 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Thu, 3 Aug 2023 12:53:16 -0400 Subject: [PATCH 07/37] realized vec is zeroizing --- src/symmetric_state.rs | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index 946e53a..3c49ca6 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -25,13 +25,6 @@ impl Clone for SymmetricState { } } -const KBKDF_LABEL_START: usize = 1; -const KBKDF_LABEL_END: usize = KBKDF_LABEL_START + 4; -const KBKDF_CONTEXT_START: usize = KBKDF_LABEL_END + 1; -const KBKDF_LENGTH_START: usize = KBKDF_CONTEXT_START + HASHLEN; -const KBKDF_INPUT_SIZE: usize = KBKDF_LENGTH_START + 2; -const HASHLEN_BITS: usize = HASHLEN * 8; - impl SymmetricState { /// HMAC-SHA512 key derivation based on KBKDF Counter Mode: /// https://csrc.nist.gov/publications/detail/sp/800-108/rev-1/final. @@ -53,13 +46,12 @@ impl SymmetricState { output2: Option<&mut [u8; HASHLEN]>, output3: Option<&mut [u8; HASHLEN]>, ) { - let mut buffer = Zeroizing::new([0u8; KBKDF_INPUT_SIZE]); - let buffer: &mut [u8] = buffer.as_mut(); - buffer[0] = 1; - buffer[KBKDF_LABEL_START..KBKDF_LABEL_END].copy_from_slice(label); - buffer[KBKDF_LABEL_END] = 0x00; - buffer[KBKDF_CONTEXT_START..KBKDF_LENGTH_START].copy_from_slice(self.ck.as_ref()); - buffer[KBKDF_LENGTH_START..].copy_from_slice(&(num_outputs * HASHLEN_BITS as u16).to_be_bytes()); + let mut buffer = Zeroizing::new(Vec::new()); + buffer.push(1); + buffer.extend(label); + buffer.push(0x00); + buffer.extend(self.ck.as_ref()); + buffer.extend(&(num_outputs * 8 * HASHLEN as u16).to_be_bytes()); debug_assert!(num_outputs >= 1); *output1 = App::Hash::hmac(input_key_material, &buffer); From 5070a638ed215ebf60aef38aa67ccf812ab4e239 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Thu, 3 Aug 2023 12:59:01 -0400 Subject: [PATCH 08/37] cargo fmt --- examples/basic_test.rs | 17 +++++++++++------ src/applicationlayer.rs | 6 +++++- src/ratchet_state.rs | 35 +++++++++++++++++++---------------- src/zeta.rs | 9 ++++++++- 4 files changed, 43 insertions(+), 24 deletions(-) diff --git a/examples/basic_test.rs b/examples/basic_test.rs index 8a031a1..2c0ef5c 100644 --- a/examples/basic_test.rs +++ b/examples/basic_test.rs @@ -38,10 +38,7 @@ struct Ratchets { } impl Ratchets { fn new() -> Self { - Self { - rf_map: HashMap::new(), - peer_map: HashMap::new(), - } + Self { rf_map: HashMap::new(), peer_map: HashMap::new() } } } @@ -84,9 +81,17 @@ impl zssp_proto::ApplicationLayer for &TestApplication { Ok(ratchets.rf_map.get(ratchet_fingerprint).cloned()) } - fn restore_by_identity(&self, remote_static_key: &Self::PublicKey, application_data: &Self::Data) -> Result<(RatchetState, Option), Self::DiskError> { + fn restore_by_identity( + &self, + remote_static_key: &Self::PublicKey, + application_data: &Self::Data, + ) -> Result<(RatchetState, Option), Self::DiskError> { let ratchets = self.ratchets.lock().unwrap(); - Ok(ratchets.peer_map.get(application_data).cloned().unwrap_or_else(|| RatchetState::new_initial_states())) + Ok(ratchets + .peer_map + .get(application_data) + .cloned() + .unwrap_or_else(|| RatchetState::new_initial_states())) } fn save_ratchet_state( diff --git a/src/applicationlayer.rs b/src/applicationlayer.rs index 2e1e113..2f8501d 100644 --- a/src/applicationlayer.rs +++ b/src/applicationlayer.rs @@ -176,7 +176,11 @@ pub trait ApplicationLayer: Sized { /// This function is not responsible for deciding whether or not to connect to this remote peer. /// Filtering peers should be done by the caller to `Context::open` as well as by the /// function `ApplicationLayer::check_accept_session`. - fn restore_by_identity(&self, remote_static_key: &Self::PublicKey, application_data: &Self::Data) -> Result<(RatchetState, Option), Self::DiskError>; + fn restore_by_identity( + &self, + remote_static_key: &Self::PublicKey, + application_data: &Self::Data, + ) -> Result<(RatchetState, Option), Self::DiskError>; /// Atomically save `current_state1` and `current_state2` so that them and only them can be /// restored with `restore_by_identity` and `restore_by_fingerprint` through a system restart. /// Theses should overwrite the previous ratchet states 1 and 2 saved to storage. diff --git a/src/ratchet_state.rs b/src/ratchet_state.rs index ef73229..22302ed 100644 --- a/src/ratchet_state.rs +++ b/src/ratchet_state.rs @@ -12,12 +12,13 @@ pub struct RatchetState { } impl PartialEq for RatchetState { fn eq(&self, other: &Self) -> bool { - secure_eq(&self.key, &other.key) & (self.chain_len == other.chain_len) & - match (self.fingerprint.as_ref(), other.fingerprint.as_ref()) { - (Some(rf1), Some(rf2)) => secure_eq(rf1, rf2), - (None, None) => true, - _ => false, - } + secure_eq(&self.key, &other.key) + & (self.chain_len == other.chain_len) + & match (self.fingerprint.as_ref(), other.fingerprint.as_ref()) { + (Some(rf1), Some(rf2)) => secure_eq(rf1, rf2), + (None, None) => true, + _ => false, + } } } impl RatchetState { @@ -25,10 +26,18 @@ impl RatchetState { RatchetState { key, fingerprint: Some(fingerprint), chain_len } } pub fn new_raw(key: [u8; RATCHET_SIZE], fingerprint: [u8; RATCHET_SIZE], chain_len: u64) -> Self { - RatchetState { key: Zeroizing::new(key), fingerprint: Some(Zeroizing::new(fingerprint)), chain_len } + RatchetState { + key: Zeroizing::new(key), + fingerprint: Some(Zeroizing::new(fingerprint)), + chain_len, + } } pub fn empty() -> Self { - RatchetState { key: Zeroizing::new([0u8; RATCHET_SIZE]), fingerprint: None, chain_len: 0 } + RatchetState { + key: Zeroizing::new([0u8; RATCHET_SIZE]), + fingerprint: None, + chain_len: 0, + } } pub fn new_from_otp(otp: &[u8]) -> RatchetState { let mut buffer = Vec::new(); @@ -47,16 +56,10 @@ impl RatchetState { } pub fn new_initial_states() -> (RatchetState, Option) { - ( - RatchetState::empty(), - None - ) + (RatchetState::empty(), None) } pub fn new_otp_states(otp: &[u8]) -> (RatchetState, Option) { - ( - RatchetState::new_from_otp::(otp), - None - ) + (RatchetState::new_from_otp::(otp), None) } pub fn is_empty(&self) -> bool { diff --git a/src/zeta.rs b/src/zeta.rs index 3542103..94bc566 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -937,7 +937,14 @@ fn timeout_trans( } let new_kid_recv = remap(session, &zeta, &ctx.rng, &ctx.session_map); - if let Some(a1) = create_a1_state(&ctx.rng, &zeta.s_remote, new_kid_recv, &zeta.ratchet_state1, zeta.ratchet_state2.as_ref(), identity.clone()) { + if let Some(a1) = create_a1_state( + &ctx.rng, + &zeta.s_remote, + new_kid_recv, + &zeta.ratchet_state1, + zeta.ratchet_state2.as_ref(), + identity.clone(), + ) { let (hk_recv, hk_send) = a1.noise.get_ask(LABEL_HEADER_KEY); let packet = a1.packet.clone(); From 988ba4723b09f44ff76187cd7782c4472819e514 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Thu, 3 Aug 2023 14:10:36 -0400 Subject: [PATCH 09/37] fixed accidental secret leaking --- examples/basic_test.rs | 3 ++- src/crypto_impl/kyber1024.rs | 7 ++++--- src/ratchet_state.rs | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/examples/basic_test.rs b/examples/basic_test.rs index 2c0ef5c..89b8624 100644 --- a/examples/basic_test.rs +++ b/examples/basic_test.rs @@ -21,6 +21,7 @@ use rand_core::OsRng; use rand_core::RngCore; use sha2::Sha512; +use zeroize::Zeroizing; use zssp_proto::ratchet_state::RatchetState; use zssp_proto::{Session, Settings, RATCHET_SIZE}; @@ -59,7 +60,7 @@ impl zssp_proto::ApplicationLayer for &TestApplication { type Hash = Sha512; type PublicKey = PublicKey; type KeyPair = EphemeralSecret; - type Kem = [u8; pqc_kyber::KYBER_SECRETKEYBYTES]; + type Kem = Zeroizing<[u8; pqc_kyber::KYBER_SECRETKEYBYTES]>; type DiskError = (); type Data = u128; diff --git a/src/crypto_impl/kyber1024.rs b/src/crypto_impl/kyber1024.rs index a17ba38..8cd11e2 100644 --- a/src/crypto_impl/kyber1024.rs +++ b/src/crypto_impl/kyber1024.rs @@ -1,11 +1,12 @@ use rand_core::{CryptoRng, RngCore}; +use zeroize::Zeroizing; use crate::crypto::*; -impl PrivateKeyKyber1024 for [u8; pqc_kyber::KYBER_SECRETKEYBYTES] { +impl PrivateKeyKyber1024 for Zeroizing<[u8; pqc_kyber::KYBER_SECRETKEYBYTES]> { fn generate(rng: &mut Rng) -> (Self, [u8; KYBER_PUBLIC_KEY_SIZE]) { let keypair = pqc_kyber::keypair(rng); - (keypair.secret, keypair.public) + (Zeroizing::new(keypair.secret), keypair.public) } fn encapsulate(rng: &mut Rng, public_key: &[u8; KYBER_PUBLIC_KEY_SIZE]) -> Option<([u8; KYBER_CIPHERTEXT_SIZE], [u8; KYBER_PLAINTEXT_SIZE])> { @@ -13,6 +14,6 @@ impl PrivateKeyKyber1024 for [u8; pqc_kyber::KYBE } fn decapsulate(&self, ciphertext: &[u8; KYBER_CIPHERTEXT_SIZE]) -> Option<[u8; KYBER_PLAINTEXT_SIZE]> { - pqc_kyber::decapsulate(ciphertext, self).ok() + pqc_kyber::decapsulate(ciphertext, self.as_ref()).ok() } } diff --git a/src/ratchet_state.rs b/src/ratchet_state.rs index 22302ed..829b6f3 100644 --- a/src/ratchet_state.rs +++ b/src/ratchet_state.rs @@ -43,7 +43,7 @@ impl RatchetState { let mut buffer = Vec::new(); buffer.push(1); buffer.extend(LABEL_OTP_TO_RATCHET); - buffer.push(0); + buffer.push(0x00); buffer.extend((2u16 * 512u16).to_be_bytes()); let r1 = Hmac::hmac(otp, &buffer); buffer[0] = 2; From c6726babdf59fa8566cdeb4b748195f6bc1490b6 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Thu, 3 Aug 2023 14:12:59 -0400 Subject: [PATCH 10/37] added typedef --- examples/basic_test.rs | 3 ++- src/crypto_impl/kyber1024.rs | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/basic_test.rs b/examples/basic_test.rs index 89b8624..32cf16b 100644 --- a/examples/basic_test.rs +++ b/examples/basic_test.rs @@ -22,6 +22,7 @@ use rand_core::RngCore; use sha2::Sha512; use zeroize::Zeroizing; +use zssp_proto::crypto_impl::kyber1024::PqcKyberSecretKey; use zssp_proto::ratchet_state::RatchetState; use zssp_proto::{Session, Settings, RATCHET_SIZE}; @@ -60,7 +61,7 @@ impl zssp_proto::ApplicationLayer for &TestApplication { type Hash = Sha512; type PublicKey = PublicKey; type KeyPair = EphemeralSecret; - type Kem = Zeroizing<[u8; pqc_kyber::KYBER_SECRETKEYBYTES]>; + type Kem = PqcKyberSecretKey; type DiskError = (); type Data = u128; diff --git a/src/crypto_impl/kyber1024.rs b/src/crypto_impl/kyber1024.rs index 8cd11e2..7e6ce5d 100644 --- a/src/crypto_impl/kyber1024.rs +++ b/src/crypto_impl/kyber1024.rs @@ -3,7 +3,9 @@ use zeroize::Zeroizing; use crate::crypto::*; -impl PrivateKeyKyber1024 for Zeroizing<[u8; pqc_kyber::KYBER_SECRETKEYBYTES]> { +pub type PqcKyberSecretKey = Zeroizing<[u8; pqc_kyber::KYBER_SECRETKEYBYTES]>; + +impl PrivateKeyKyber1024 for PqcKyberSecretKey { fn generate(rng: &mut Rng) -> (Self, [u8; KYBER_PUBLIC_KEY_SIZE]) { let keypair = pqc_kyber::keypair(rng); (Zeroizing::new(keypair.secret), keypair.public) From cf1804e65a0a17c02e3cd1671b3e63b8e45f66d6 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Thu, 3 Aug 2023 15:07:58 -0400 Subject: [PATCH 11/37] added a lot of docs --- examples/basic_test.rs | 5 ++--- src/applicationlayer.rs | 41 ++++++++++++++++++++++++++++++------ src/crypto/aes.rs | 17 ++++++++++++--- src/crypto/kyber1024.rs | 24 +++++++++++++++++++-- src/crypto/p384.rs | 21 ++++++++++-------- src/crypto/sha512.rs | 14 ++++++------ src/crypto_impl/kyber1024.rs | 2 ++ src/crypto_impl/mod.rs | 10 +++++---- src/lib.rs | 21 +++++++++++++++--- 9 files changed, 118 insertions(+), 37 deletions(-) diff --git a/examples/basic_test.rs b/examples/basic_test.rs index 32cf16b..31512ed 100644 --- a/examples/basic_test.rs +++ b/examples/basic_test.rs @@ -21,9 +21,8 @@ use rand_core::OsRng; use rand_core::RngCore; use sha2::Sha512; -use zeroize::Zeroizing; -use zssp_proto::crypto_impl::kyber1024::PqcKyberSecretKey; -use zssp_proto::ratchet_state::RatchetState; +use zssp_proto::crypto_impl::PqcKyberSecretKey; +use zssp_proto::RatchetState; use zssp_proto::{Session, Settings, RATCHET_SIZE}; const TEST_MTU: usize = 1500; diff --git a/src/applicationlayer.rs b/src/applicationlayer.rs index 2f8501d..7912450 100644 --- a/src/applicationlayer.rs +++ b/src/applicationlayer.rs @@ -92,22 +92,49 @@ pub trait ApplicationLayer: Sized { /// the protocol will tend to default to the smaller constants. const SETTINGS: Settings = Settings::new_ms(); + /// The random number generator that ZSSP should use. + /// + /// FIPS compliance requires use of a FIPS certified implementation. type Rng: CryptoRng + RngCore; - + /// The implementation of AES-256 that ZSSP should use. + /// We provide an optional implementation for this trait using the `aes` crate. + /// + /// FIPS compliance requires use of a FIPS certified implementation. type Prp: PrpAes256; - + /// The implementation of AES-GCM-256 that ZSSP should use. + /// The efficiency and security of ZSSP is very closely tied to the efficiency and security of + /// this implementation. + /// We provide an optional implementation for this trait using the `aes-gcm` crate. + /// + /// FIPS compliance requires a FIPS certified implementation. type Aead: AeadAesGcm; - + /// The implementation of SHA-512 that ZSSP should use. + /// We provide an optional implementation for this trait using the `sha2` crate. + /// + /// FIPS compliance requires use of a FIPS certified implementation. type Hash: HashSha512; - + /// The implementation of P-384 public keys that ZSSP should use. + /// We provide an optional implementation for this trait using the `p384` crate. + /// + /// FIPS compliance requires a FIPS certified implementation. type PublicKey: PublicKeyP384; + /// The implementation of P-384 private keys that ZSSP should use. + /// We provide an optional implementation for this trait using the `p384` crate. + /// + /// FIPS compliance requires use of a FIPS certified implementation. type KeyPair: KeyPairP384; + /// The implementation of Kyber1024 that ZSSP should use. + /// We provide an optional implementation for this trait using the `pqc_kyber` crate. + /// + /// No implementation of Kyber1024 can be FIPS certified, but this is not required + /// for ZSSP to achieve FIPS compliance. type Kem: PrivateKeyKyber1024; + /// A user-defined error returned when the `ApplicationLayer` fails to access persistent storage + /// for a peer's ratchet states. type DiskError: std::fmt::Debug; - /// Type for arbitrary opaque object for use by the application that is attached to - /// each session. + /// An arbitrary opaque object for use by the application that is attached to each session. type Data; /// Should return the current time in milliseconds. Does not have to be monotonic, nor synced @@ -220,5 +247,5 @@ pub trait ApplicationLayer: Sized { /// These are provided for debugging, logging or metrics purposes, and must be used for /// nothing else. Do not base protocol-level decisions upon the events passed to this function. #[cfg(feature = "logging")] - fn event_log(&self, event: LogEvent); + fn event_log(&self, event: LogEvent<'_, Self>); } diff --git a/src/crypto/aes.rs b/src/crypto/aes.rs index 5e8f41a..d5f1d56 100644 --- a/src/crypto/aes.rs +++ b/src/crypto/aes.rs @@ -1,24 +1,35 @@ +/// The size of an AES block, which is 16 bytes, or 128 bits. pub const AES_256_BLOCK_SIZE: usize = 16; +/// The size of an AES-256 key, which is 32 bytes, or 256 bits. pub const AES_256_KEY_SIZE: usize = 32; +/// The size of an AES-GCM authentication tag, which is 16 bytes, or 128 bits. +/// Some implementations of AES-GCM allow use of smaller tags, but ZSSP will only accept 16 byte tags. pub const AES_GCM_TAG_SIZE: usize = 16; +/// The size of an AES-GCM IV, or nonce, which is 12 bytes, or 96 bits. pub const AES_GCM_IV_SIZE: usize = 12; /// A trait for encrypting individual blocks of plaintext using AES-256. /// It is used for header authentication, for which we have a standard model proof that our /// algorithm is secure. pub trait PrpAes256 { - /// Decrypt the given `block` of plaintext directly using the AES block cipher + /// Encrypt the given `block` of plaintext directly using the AES block cipher /// (i.e. AES-256 in zero-padding ECB mode). - /// The ciphertext should be written directly back out to `block`. + /// The ciphertext should be written directly back to `block`. fn encrypt_in_place(key: &[u8; AES_256_KEY_SIZE], block: &mut [u8; AES_256_BLOCK_SIZE]); /// Decrypt the given `block` of ciphertext directly using the AES 256 block cipher /// (i.e. AES-256 in zero-padding ECB mode). - /// The plaintext should be written directly back out to `block`. + /// The plaintext should be written directly back to `block`. fn decrypt_in_place(key: &[u8; AES_256_KEY_SIZE], block: &mut [u8; AES_256_BLOCK_SIZE]); } +/// A trait accessing AES-GCM-256 encryption and decryption as a set of pure-functions. +/// These should be trivial to implement for most implementations of AES-GCM. pub trait AeadAesGcm { + /// Encrypt the given `buffer` of plaintext using AES-GCM-256, with the given `key`, `iv` and `aad`. + /// The ciphertext should be written directly back to `buffer`, and the GCM tag should be returned. fn encrypt_in_place(key: &[u8; AES_256_KEY_SIZE], iv: [u8; AES_GCM_IV_SIZE], aad: Option<&[u8]>, buffer: &mut [u8]) -> [u8; AES_GCM_TAG_SIZE]; + /// Decrypt the given `buffer` of ciphertext using AES-GCM-256, with the given `key`, `iv` and `aad`. + /// The ciphertext should be written directly back to `buffer`, and the GCM tag should be returned. fn decrypt_in_place( key: &[u8; AES_256_KEY_SIZE], iv: [u8; AES_GCM_IV_SIZE], diff --git a/src/crypto/kyber1024.rs b/src/crypto/kyber1024.rs index 1d6bc82..a844ff9 100644 --- a/src/crypto/kyber1024.rs +++ b/src/crypto/kyber1024.rs @@ -1,14 +1,34 @@ use rand_core::{CryptoRng, RngCore}; +/// The size of a Kyber1024 public key, which is 1568 bytes. pub const KYBER_PUBLIC_KEY_SIZE: usize = 1568; +/// The size of a Kyber1024 KEM ciphertext, which is 1568 bytes. pub const KYBER_CIPHERTEXT_SIZE: usize = 1568; +/// The size of a Kyber1024 KEM plaintext, which is 32 bytes. pub const KYBER_PLAINTEXT_SIZE: usize = 32; /// Instances must securely delete the private key when dropped. pub trait PrivateKeyKyber1024: Sized + Send + Sync { + /// Generate a Kyber1024 private key and public key pair, and return the raw bytes of the public + /// key. + /// The private key will be temporarily held in memory but the public key will be immediately + /// sent to the remote peer. + /// + /// This function may use the provided RNG or its own, so long as the output is cryptographically random. fn generate(rng: &mut Rng) -> (Self, [u8; KYBER_PUBLIC_KEY_SIZE]); - + /// Generate a Kyber1024 key encapsulation based on the given public key, and return the + /// raw bytes of the generated ciphertext and plaintext. The ciphertext is immediately sent to + /// the remote peer and the plaintext is immediately hashed, both are quickly deleted. + /// + /// This function may use the provided RNG or its own, so long as the output is cryptographically random. + /// + /// This must return `None` if the given public_key is invalid in any way according to the + /// Kyber1024 spec. fn encapsulate(rng: &mut Rng, public_key: &[u8; KYBER_PUBLIC_KEY_SIZE]) -> Option<([u8; KYBER_CIPHERTEXT_SIZE], [u8; KYBER_PLAINTEXT_SIZE])>; - + /// Decapsulate a Kyber1024 ciphertext received from the remote peer, retreiving + /// the raw bytes of the original plaintext. This plaintext is immediately hashed and deleted. + /// + /// This must return `None` if the given ciphertext is invalid in any way according to the + /// Kyber1024 spec. fn decapsulate(&self, ciphertext: &[u8; KYBER_CIPHERTEXT_SIZE]) -> Option<[u8; KYBER_PLAINTEXT_SIZE]>; } diff --git a/src/crypto/p384.rs b/src/crypto/p384.rs index c7ed465..161f5ee 100644 --- a/src/crypto/p384.rs +++ b/src/crypto/p384.rs @@ -1,19 +1,21 @@ use rand_core::{CryptoRng, RngCore}; +/// The size in bytes of a P-384 public key when in compressed SEC1-encoded format. pub const P384_PUBLIC_KEY_SIZE: usize = 49; +/// The size in bytes of the raw output of ECDH between a P-384 public and private key. pub const P384_ECDH_SHARED_SECRET_SIZE: usize = 48; /// A NIST P-384 ECDH/ECDSA public key. pub trait PublicKeyP384: Sized + Send + Sync { - /// Create a p384 public key from raw bytes. + /// Create a P-384 public key from raw bytes. /// - /// **CRITICAL**: This function must return `None` if the input `raw_key` is not on the P384 curve, - /// or if it breaks the P384 standard in any other way. + /// **CRITICAL**: This function must return `None` if the input `raw_key` is not on the P-384 + /// curve, or if it breaks the P-384 spec in any other way. fn from_bytes(raw_key: &[u8; P384_PUBLIC_KEY_SIZE]) -> Option; /// Get the raw bytes that uniquely define the public key. /// - /// This must output the compressed 49 byte NIST encoding of P384 public keys. + /// This must output the compressed SEC1 NIST encoding of P-384 public keys. fn to_bytes(&self) -> [u8; P384_PUBLIC_KEY_SIZE]; } @@ -21,22 +23,23 @@ pub trait PublicKeyP384: Sized + Send + Sync { /// /// Instances must securely delete the private key when dropped. pub trait KeyPairP384 { + /// The `PublicKeyP384` implementation which matches this `KeyPairP384` implementation. type PublicKey: PublicKeyP384; - /// Randomly generate a new p384 keypair. - /// This function may use the provided RNG or it's own, - /// so long as the produced keys are cryptographically random. + /// Randomly generate a new P-384 keypair. + /// + /// This function may use the provided RNG or its own, so long as the output is cryptographically random. fn generate(rng: &mut Rng) -> Self; /// Get the raw bytes that uniquely define the public key. /// - /// This must output the compressed 49 byte NIST encoding of P384 public keys. + /// This must output the compressed SEC1 NIST encoding of P-384 public keys. fn public_key_bytes(&self) -> [u8; P384_PUBLIC_KEY_SIZE]; /// Perform ECDH key agreement, writing the raw (un-hashed!) ECDH secret to `output`. /// /// **CRITICAL**: This function must return `None` if key agreement between this private key and /// the input `public_key` key would result in an invalid, non-standard or predictable ECDH secret. - /// Please refer to the NIST spec for P384 ECDH key agreement, or better yet use a peer reviewed + /// Please refer to the NIST spec for P-384 ECDH key agreement, or better yet use a peer reviewed /// library that has already implemented this correctly. fn agree(&self, public_key: &Self::PublicKey) -> Option<[u8; P384_ECDH_SHARED_SECRET_SIZE]>; } diff --git a/src/crypto/sha512.rs b/src/crypto/sha512.rs index 0ff187e..2eb32a2 100644 --- a/src/crypto/sha512.rs +++ b/src/crypto/sha512.rs @@ -1,15 +1,17 @@ +/// The size of a SHA-512 hash, which of course is 64 bytes, or 512 bits. pub const SHA512_HASH_SIZE: usize = 64; -/// Opaque SHA-512 implementation. -/// Does not need to be threadsafe. +/// A SHA-512 and HMAC-SHA-512 implementation. pub trait HashSha512 { - /// Allocate memory on the stack or heap for Sha512. - /// An instance of Sha512 will only ever be held on the stack. + /// Create a new instance of SHA-512 for streaming data to. fn new() -> Self; - + /// Update the instance of SHA-512 with input `data`. + /// This must update the state of SHA-512 as if `data` was appended to the previous input. fn update(&mut self, data: &[u8]); - /// Finish hashing the input and write the final hash to output. + /// Finish streaming input and output the final hash. fn finish(self) -> [u8; SHA512_HASH_SIZE]; + /// Produce a HMAC-SHA-512 hash based on the given `key` and `data`. + /// This is a pure function and does not need to support streaming. fn hmac(key: &[u8], data: &[u8]) -> [u8; SHA512_HASH_SIZE]; } diff --git a/src/crypto_impl/kyber1024.rs b/src/crypto_impl/kyber1024.rs index 7e6ce5d..4c5ed15 100644 --- a/src/crypto_impl/kyber1024.rs +++ b/src/crypto_impl/kyber1024.rs @@ -3,6 +3,8 @@ use zeroize::Zeroizing; use crate::crypto::*; +/// A wrapper for a buffer the size of a pqc_kyber secret key. +/// The crate `pqc_kyber` is low level and operates directly on buffers of bytes. pub type PqcKyberSecretKey = Zeroizing<[u8; pqc_kyber::KYBER_SECRETKEYBYTES]>; impl PrivateKeyKyber1024 for PqcKyberSecretKey { diff --git a/src/crypto_impl/mod.rs b/src/crypto_impl/mod.rs index 0a9dd5a..1f9d7ef 100644 --- a/src/crypto_impl/mod.rs +++ b/src/crypto_impl/mod.rs @@ -1,11 +1,11 @@ #[cfg(feature = "aes-gcm")] -pub mod aes_impl; +mod aes_impl; #[cfg(feature = "pqc_kyber")] -pub mod kyber1024; +mod kyber1024; #[cfg(feature = "p384")] -pub mod p384_impl; +mod p384_impl; #[cfg(feature = "sha2")] -pub mod sha512; +mod sha512; #[cfg(feature = "aes")] pub use aes; @@ -17,5 +17,7 @@ pub use hmac; pub use p384; #[cfg(feature = "pqc_kyber")] pub use pqc_kyber; +#[cfg(feature = "pqc_kyber")] +pub use kyber1024::PqcKyberSecretKey; #[cfg(feature = "sha2")] pub use sha2; diff --git a/src/lib.rs b/src/lib.rs index bc794bd..d3b627f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,7 @@ * (c) ZeroTier, Inc. * https://www.zerotier.com/ */ -//#![warn(missing_docs, rust_2018_idioms)] +#![warn(missing_docs, rust_2018_idioms)] //! TODO: docs mod applicationlayer; @@ -17,13 +17,28 @@ mod log_event; mod proto; mod symmetric_state; mod zeta; +mod ratchet_state; -#[macro_use] pub mod result; +/// A collection of implementation-independent traits for the various specific cryptographic +/// algorithms ZSSP depends on. +/// Each trait is hyper-specific about the semantics of the algorithms and the lengths of its inputs +/// and outputs as a forced sanity-check for anyone trying to use their own implementations. +/// +/// The `crypto_impl` module contains implementations of these traits in terms of popular Rust +/// implementations of these algorithms. pub mod crypto; +/// A module containing implementations of the ZSSP `crypto` traits in terms of popular Rust crates. +/// Some of these crates are not thoroughly audited, so use at your own risk. +/// +/// Note that none of these crates are FIPS certified, meaning a build of ZSSP using them will not +/// be FIPS compliant. However lack of FIPS compliance by no means implies lack of security or lack +/// of confidence. +/// +/// This module contains the trait implementations as well as re-exports of those crates. pub mod crypto_impl; -pub mod ratchet_state; +pub use ratchet_state::RatchetState; pub use applicationlayer::{ApplicationLayer, Settings}; pub use context::Context; #[cfg(feature = "logging")] From 2c33e447bd079c764088b47e36a1375226f532dc Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Thu, 3 Aug 2023 15:19:32 -0400 Subject: [PATCH 12/37] improved dependency handling --- Cargo.lock | 77 ----------------------------------------- Cargo.toml | 17 +++++---- src/crypto/kyber1024.rs | 12 +++---- src/crypto/p384.rs | 2 +- src/crypto_impl/mod.rs | 4 +-- src/lib.rs | 18 +++++----- 6 files changed, 29 insertions(+), 101 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f9170ea..c58728d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,12 +43,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" -[[package]] -name = "base64ct" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" - [[package]] name = "block-buffer" version = "0.10.4" @@ -108,7 +102,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", - "rand_core", "typenum", ] @@ -128,7 +121,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7ed52955ce76b1554f509074bb357d3fb8ac9b51288a65a3fd480d1dfba946" dependencies = [ "const-oid", - "pem-rfc7468", "zeroize", ] @@ -139,25 +131,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", - "const-oid", "crypto-common", "subtle", ] -[[package]] -name = "ecdsa" -version = "0.16.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" -dependencies = [ - "der", - "digest", - "elliptic-curve", - "rfc6979", - "signature", - "spki", -] - [[package]] name = "elliptic-curve" version = "0.13.5" @@ -171,8 +148,6 @@ dependencies = [ "generic-array", "group", "hkdf", - "pem-rfc7468", - "pkcs8", "rand_core", "sec1", "subtle", @@ -277,29 +252,8 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" dependencies = [ - "ecdsa", "elliptic-curve", "primeorder", - "sha2", -] - -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", ] [[package]] @@ -341,16 +295,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "rfc6979" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" -dependencies = [ - "hmac", - "subtle", -] - [[package]] name = "sec1" version = "0.7.3" @@ -360,7 +304,6 @@ dependencies = [ "base16ct", "der", "generic-array", - "pkcs8", "subtle", "zeroize", ] @@ -376,26 +319,6 @@ dependencies = [ "digest", ] -[[package]] -name = "signature" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" -dependencies = [ - "digest", - "rand_core", -] - -[[package]] -name = "spki" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" -dependencies = [ - "base64ct", - "der", -] - [[package]] name = "subtle" version = "2.5.0" diff --git a/Cargo.toml b/Cargo.toml index cd08fbc..4dbe805 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,15 +11,15 @@ path = "src/lib.rs" doc = true [dependencies] -rand_core = "0.6.4" -zeroize = { version = "1.6.0", default-features = false} +rand_core = { version = "0.6.4" } +zeroize = { version = "1.6.0" } pqc_kyber = { version = "0.6.0", default-features = false, features = ["kyber1024", "std"], optional = true } -aes-gcm = { version = "0.10.2", optional = true} -aes = { version = "0.8.3", optional = true} -p384 = { version = "0.13.0", optional = true} -sha2 = { version = "0.10.7", optional = true} -hmac = { version = "0.12.1", optional = true} +aes-gcm = { version = "0.10.2", default-features = false, features = ["aes"], optional = true } +aes = { version = "0.8.3", default-features = false, optional = true } +p384 = { version = "0.13.0", default-features = false, features = ["ecdh"], optional = true } +sha2 = { version = "0.10.7", default-features = false, optional = true } +hmac = { version = "0.12.1", default-features = false, optional = true } [features] default = ["debug", "aes-gcm", "p384", "sha2", "hmac", "pqc_kyber"] @@ -29,3 +29,6 @@ sha2 = ["dep:sha2"] hmac = ["dep:hmac", "sha2"] logging = [] debug = ["logging"] + +[dev-dependencies] +rand_core = { version = "0.6.4", features = ["getrandom"] } diff --git a/src/crypto/kyber1024.rs b/src/crypto/kyber1024.rs index a844ff9..185ab6b 100644 --- a/src/crypto/kyber1024.rs +++ b/src/crypto/kyber1024.rs @@ -16,19 +16,19 @@ pub trait PrivateKeyKyber1024: Sized + Send + Sync { /// /// This function may use the provided RNG or its own, so long as the output is cryptographically random. fn generate(rng: &mut Rng) -> (Self, [u8; KYBER_PUBLIC_KEY_SIZE]); - /// Generate a Kyber1024 key encapsulation based on the given public key, and return the + /// Generate a Kyber1024 key encapsulation based on the given `public_key`, and return the /// raw bytes of the generated ciphertext and plaintext. The ciphertext is immediately sent to /// the remote peer and the plaintext is immediately hashed, both are quickly deleted. /// /// This function may use the provided RNG or its own, so long as the output is cryptographically random. /// - /// This must return `None` if the given public_key is invalid in any way according to the - /// Kyber1024 spec. + /// **CRITICAL**: This must return `None` if the given `public_key` is invalid in any way + /// according to the Kyber1024 spec. fn encapsulate(rng: &mut Rng, public_key: &[u8; KYBER_PUBLIC_KEY_SIZE]) -> Option<([u8; KYBER_CIPHERTEXT_SIZE], [u8; KYBER_PLAINTEXT_SIZE])>; - /// Decapsulate a Kyber1024 ciphertext received from the remote peer, retreiving + /// Decapsulate a Kyber1024 `ciphertext` received from the remote peer, retreiving /// the raw bytes of the original plaintext. This plaintext is immediately hashed and deleted. /// - /// This must return `None` if the given ciphertext is invalid in any way according to the - /// Kyber1024 spec. + /// **CRITICAL**: This must return `None` if the given `ciphertext` is invalid in any way + /// according to the Kyber1024 spec. fn decapsulate(&self, ciphertext: &[u8; KYBER_CIPHERTEXT_SIZE]) -> Option<[u8; KYBER_PLAINTEXT_SIZE]>; } diff --git a/src/crypto/p384.rs b/src/crypto/p384.rs index 161f5ee..6d13387 100644 --- a/src/crypto/p384.rs +++ b/src/crypto/p384.rs @@ -26,7 +26,7 @@ pub trait KeyPairP384 { /// The `PublicKeyP384` implementation which matches this `KeyPairP384` implementation. type PublicKey: PublicKeyP384; /// Randomly generate a new P-384 keypair. - /// + /// /// This function may use the provided RNG or its own, so long as the output is cryptographically random. fn generate(rng: &mut Rng) -> Self; diff --git a/src/crypto_impl/mod.rs b/src/crypto_impl/mod.rs index 1f9d7ef..a08b8ef 100644 --- a/src/crypto_impl/mod.rs +++ b/src/crypto_impl/mod.rs @@ -13,11 +13,11 @@ pub use aes; pub use aes_gcm; #[cfg(feature = "hmac")] pub use hmac; +#[cfg(feature = "pqc_kyber")] +pub use kyber1024::PqcKyberSecretKey; #[cfg(feature = "p384")] pub use p384; #[cfg(feature = "pqc_kyber")] pub use pqc_kyber; -#[cfg(feature = "pqc_kyber")] -pub use kyber1024::PqcKyberSecretKey; #[cfg(feature = "sha2")] pub use sha2; diff --git a/src/lib.rs b/src/lib.rs index d3b627f..8892f1c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,7 @@ * (c) ZeroTier, Inc. * https://www.zerotier.com/ */ -#![warn(missing_docs, rust_2018_idioms)] +//#![warn(missing_docs, rust_2018_idioms)] //! TODO: docs mod applicationlayer; @@ -15,21 +15,22 @@ mod fragmentation; #[cfg(feature = "logging")] mod log_event; mod proto; +mod ratchet_state; mod symmetric_state; mod zeta; -mod ratchet_state; -pub mod result; /// A collection of implementation-independent traits for the various specific cryptographic /// algorithms ZSSP depends on. -/// Each trait is hyper-specific about the semantics of the algorithms and the lengths of its inputs -/// and outputs as a forced sanity-check for anyone trying to use their own implementations. +/// +/// Each trait is hyper-specific about the semantics of the algorithms and the lengths of their +/// inputs and outputs. +/// This is to enforced a basic sanity-check upon anyone trying to use their own implementations. /// /// The `crypto_impl` module contains implementations of these traits in terms of popular Rust /// implementations of these algorithms. pub mod crypto; -/// A module containing implementations of the ZSSP `crypto` traits in terms of popular Rust crates. -/// Some of these crates are not thoroughly audited, so use at your own risk. +/// A module containing optional implementations of the ZSSP `crypto` traits in terms of popular +/// Rust crates. Some of these crates are not thoroughly audited, so use at your own risk. /// /// Note that none of these crates are FIPS certified, meaning a build of ZSSP using them will not /// be FIPS compliant. However lack of FIPS compliance by no means implies lack of security or lack @@ -37,11 +38,12 @@ pub mod crypto; /// /// This module contains the trait implementations as well as re-exports of those crates. pub mod crypto_impl; +pub mod result; -pub use ratchet_state::RatchetState; pub use applicationlayer::{ApplicationLayer, Settings}; pub use context::Context; #[cfg(feature = "logging")] pub use log_event::LogEvent; pub use proto::{MIN_TRANSPORT_MTU, RATCHET_SIZE}; +pub use ratchet_state::RatchetState; pub use zeta::Session; From 6cb72db9344f9b0f8f89d43fbc2fc9b8b2e313a4 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Thu, 3 Aug 2023 15:20:48 -0400 Subject: [PATCH 13/37] removed unused default --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4dbe805..e41d91d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ sha2 = { version = "0.10.7", default-features = false, optional = true } hmac = { version = "0.12.1", default-features = false, optional = true } [features] -default = ["debug", "aes-gcm", "p384", "sha2", "hmac", "pqc_kyber"] +default = ["debug", "aes-gcm", "p384", "hmac", "pqc_kyber"] aes = ["dep:aes"] aes-gcm = ["dep:aes-gcm", "aes"] sha2 = ["dep:sha2"] From c8b088f4c7b31485528ae82048a55768be910b70 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Thu, 3 Aug 2023 15:35:48 -0400 Subject: [PATCH 14/37] changed error name --- examples/basic_test.rs | 8 ++++---- src/applicationlayer.rs | 8 ++++---- src/context.rs | 4 ++-- src/fragmentation.rs | 4 ++-- src/zeta.rs | 20 ++++++++++---------- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/examples/basic_test.rs b/examples/basic_test.rs index 31512ed..76ce3ca 100644 --- a/examples/basic_test.rs +++ b/examples/basic_test.rs @@ -62,7 +62,7 @@ impl zssp_proto::ApplicationLayer for &TestApplication { type KeyPair = EphemeralSecret; type Kem = PqcKyberSecretKey; - type DiskError = (); + type StorageError = (); type Data = u128; fn hello_requires_recognized_ratchet(&self) -> bool { @@ -77,7 +77,7 @@ impl zssp_proto::ApplicationLayer for &TestApplication { (Some((true, 1)), true) } - fn restore_by_fingerprint(&self, ratchet_fingerprint: &[u8; RATCHET_SIZE]) -> Result, Self::DiskError> { + fn restore_by_fingerprint(&self, ratchet_fingerprint: &[u8; RATCHET_SIZE]) -> Result, Self::StorageError> { let ratchets = self.ratchets.lock().unwrap(); Ok(ratchets.rf_map.get(ratchet_fingerprint).cloned()) } @@ -86,7 +86,7 @@ impl zssp_proto::ApplicationLayer for &TestApplication { &self, remote_static_key: &Self::PublicKey, application_data: &Self::Data, - ) -> Result<(RatchetState, Option), Self::DiskError> { + ) -> Result<(RatchetState, Option), Self::StorageError> { let ratchets = self.ratchets.lock().unwrap(); Ok(ratchets .peer_map @@ -104,7 +104,7 @@ impl zssp_proto::ApplicationLayer for &TestApplication { state_added: Option<&RatchetState>, state_deleted1: Option<&RatchetState>, state_deleted2: Option<&RatchetState>, - ) -> Result<(), Self::DiskError> { + ) -> Result<(), Self::StorageError> { let mut ratchets = self.ratchets.lock().unwrap(); ratchets.peer_map.insert(*application_data, (state1.clone(), state2.cloned())); diff --git a/src/applicationlayer.rs b/src/applicationlayer.rs index 7912450..6c00d67 100644 --- a/src/applicationlayer.rs +++ b/src/applicationlayer.rs @@ -132,7 +132,7 @@ pub trait ApplicationLayer: Sized { /// A user-defined error returned when the `ApplicationLayer` fails to access persistent storage /// for a peer's ratchet states. - type DiskError: std::fmt::Debug; + type StorageError: std::fmt::Debug; /// An arbitrary opaque object for use by the application that is attached to each session. type Data; @@ -188,7 +188,7 @@ pub trait ApplicationLayer: Sized { /// If `RatchetAction::DowngradeRatchet` is returned we will attempt to convince Alice to downgrade /// to the empty ratchet key, restarting the ratchet chain. /// If `RatchetAction::FailAuthentication` is returned Alice's connection will be silently dropped. - fn restore_by_fingerprint(&self, ratchet_fingerprint: &[u8; RATCHET_SIZE]) -> Result, Self::DiskError>; + fn restore_by_fingerprint(&self, ratchet_fingerprint: &[u8; RATCHET_SIZE]) -> Result, Self::StorageError>; /// Lookup the specific ratchet states based on the identity of the peer being communicated with. /// This function will be called whenever Alice attempts to open a session, or Bob attempts /// to verify Alice's identity. @@ -207,7 +207,7 @@ pub trait ApplicationLayer: Sized { &self, remote_static_key: &Self::PublicKey, application_data: &Self::Data, - ) -> Result<(RatchetState, Option), Self::DiskError>; + ) -> Result<(RatchetState, Option), Self::StorageError>; /// Atomically save `current_state1` and `current_state2` so that them and only them can be /// restored with `restore_by_identity` and `restore_by_fingerprint` through a system restart. /// Theses should overwrite the previous ratchet states 1 and 2 saved to storage. @@ -241,7 +241,7 @@ pub trait ApplicationLayer: Sized { state_added: Option<&RatchetState>, state_deleted1: Option<&RatchetState>, state_deleted2: Option<&RatchetState>, - ) -> Result<(), Self::DiskError>; + ) -> Result<(), Self::StorageError>; /// Receives a stream of events that occur during an execution of ZSSP. /// These are provided for debugging, logging or metrics purposes, and must be used for diff --git a/src/context.rs b/src/context.rs index ab8a010..182ef30 100644 --- a/src/context.rs +++ b/src/context.rs @@ -102,7 +102,7 @@ impl Context { static_remote_key: App::PublicKey, application_data: App::Data, identity: Vec, - ) -> Result>, OpenError> { + ) -> Result>, OpenError> { mtu = mtu.max(MIN_TRANSPORT_MTU); let ctx = &self.0; @@ -136,7 +136,7 @@ impl Context { send_to: impl FnOnce(&Arc>) -> Option<(SendFn, usize)>, remote_address: &impl Hash, raw_fragment: Vec, - ) -> Result, ReceiveError> { + ) -> Result, ReceiveError> { use crate::result::FaultType::*; send_unassociated_mtu = send_unassociated_mtu.max(MIN_TRANSPORT_MTU); let ctx = &self.0; diff --git a/src/fragmentation.rs b/src/fragmentation.rs index 64d3e7c..96ed746 100644 --- a/src/fragmentation.rs +++ b/src/fragmentation.rs @@ -76,8 +76,8 @@ impl DefragBuffer { &self, mut raw_fragment: Vec, current_time: i64, - vrfy: impl FnOnce(&[u8; PACKET_NONCE_SIZE], usize, usize) -> Result<(), ReceiveError>, - ) -> Result)>, ReceiveError> { + vrfy: impl FnOnce(&[u8; PACKET_NONCE_SIZE], usize, usize) -> Result<(), ReceiveError>, + ) -> Result)>, ReceiveError> { use crate::result::FaultType::*; if raw_fragment.len() < HEADER_AUTH_END { return Err(byzantine_fault!(InvalidPacket, true)); diff --git a/src/zeta.rs b/src/zeta.rs index 94bc566..748099b 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -258,7 +258,7 @@ pub(crate) fn trans_to_a1( application_data: App::Data, identity: Vec, send: impl FnOnce(&Packet), -) -> Result>, OpenError> { +) -> Result>, OpenError> { let (ratchet_state1, ratchet_state2) = app .restore_by_identity(&s_remote, &application_data) .map_err(|e| OpenError::RatchetIoError(e))?; @@ -316,7 +316,7 @@ pub(crate) fn received_x1_trans( n: [u8; AES_GCM_IV_SIZE], mut x1: Vec, send: impl FnOnce(&Packet, &[u8; AES_256_KEY_SIZE]), -) -> Result<(), ReceiveError> { +) -> Result<(), ReceiveError> { use FaultType::*; // <- s // ... @@ -436,7 +436,7 @@ pub(crate) fn received_x2_trans( n: [u8; AES_GCM_IV_SIZE], mut x2: Vec, send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), -) -> Result<(), ReceiveError> { +) -> Result<(), ReceiveError> { use FaultType::*; // <- e, ee, ekem1, psk // -> s, se @@ -588,7 +588,7 @@ pub(crate) fn received_x3_trans( kid: NonZeroU32, mut x3: Vec, send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), -) -> Result>, ReceiveError> { +) -> Result>, ReceiveError> { use FaultType::*; // -> s, se if x3.len() < HANDSHAKE_COMPLETION_MIN_SIZE { @@ -727,7 +727,7 @@ pub(crate) fn received_c1_trans( n: [u8; AES_GCM_IV_SIZE], c1: Vec, send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), -) -> Result> { +) -> Result> { use FaultType::*; if c1.len() != KEY_CONFIRMATION_SIZE { @@ -800,7 +800,7 @@ pub(crate) fn received_c2_trans( kid: NonZeroU32, n: [u8; AES_GCM_IV_SIZE], c2: Vec, -) -> Result<(), ReceiveError> { +) -> Result<(), ReceiveError> { use FaultType::*; if c2.len() != ACKNOWLEDGEMENT_SIZE { @@ -837,7 +837,7 @@ pub(crate) fn received_d_trans( kid: NonZeroU32, n: [u8; AES_GCM_IV_SIZE], d: Vec, -) -> Result<(), ReceiveError> { +) -> Result<(), ReceiveError> { use FaultType::*; if d.len() != SESSION_REJECTED_SIZE { @@ -1030,7 +1030,7 @@ pub(crate) fn received_k1_trans( n: [u8; AES_GCM_IV_SIZE], mut k1: Vec, send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), -) -> Result<(), ReceiveError> { +) -> Result<(), ReceiveError> { use FaultType::*; // -> s // <- s @@ -1153,7 +1153,7 @@ pub(crate) fn received_k2_trans( n: [u8; AES_GCM_IV_SIZE], mut k2: Vec, send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), -) -> Result<(), ReceiveError> { +) -> Result<(), ReceiveError> { use FaultType::*; // <- e, ee, se if k2.len() != REKEY_SIZE { @@ -1284,7 +1284,7 @@ pub(crate) fn received_payload_in_place( kid: NonZeroU32, n: [u8; AES_GCM_IV_SIZE], payload: &mut Vec, -) -> Result<(), ReceiveError> { +) -> Result<(), ReceiveError> { use FaultType::*; if payload.len() < AES_GCM_TAG_SIZE { From b1e39a42a5f46b7c11bbc9167fb5d2308e7c3657 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Fri, 4 Aug 2023 01:40:20 -0400 Subject: [PATCH 15/37] refactored --- README.md | 37 ----------------- comparison.md | 37 +++++++++++++++++ examples/basic_test.rs | 36 ++++++++--------- src/applicationlayer.rs | 26 +++++++----- src/context.rs | 7 ++-- src/lib.rs | 2 +- src/symmetric_state.rs | 2 +- src/zeta.rs | 85 +++++++++++++++++++++++----------------- whitepaper.pdf | Bin 0 -> 420496 bytes 9 files changed, 124 insertions(+), 108 deletions(-) create mode 100644 comparison.md create mode 100644 whitepaper.pdf diff --git a/README.md b/README.md index 95f2984..f043379 100644 --- a/README.md +++ b/README.md @@ -21,40 +21,3 @@ Further information can be found in the ZSSP whitepaper (pending official releas - **KBKDF**: Key mixing, sub-key derivation - **AES-256**: 128-bit PRP for AES-256-GCM and for authenticated encryption of header to harden fragmentation against DOS (see section on header protection) - **AES-256-GCM**: Authenticated encryption - -## Security Properties - -| | Persistent ZSSP | Opportunistic ZSSP| WireGuard | ZeroTier Legacy Transport | -| --- | --- | --- | --- | --- | -|**Construction**|Noise\_XKhfs+psk2|Noise\_XKhfs+psk2|Noise\_IKpsk2|Static Diffie-Helman| -|**Perfect Forward Secrecy**|Yes|Yes|Yes|No| -|**Forward Secret Identity Hiding**|Yes|Yes|No|No| -|**Quantum Forward Secret**|Yes|Yes|No|No| -|**Ratcheted Forward Secrecy**|Yes|Yes|No|No| -|**Silence is a Virtue**|Yes|No|Yes|No| -|**Key-Compromise Impersonation**|Resistant|Resistant|Resistant|Vulnerable| -|**Compromise-and-Impersonate**|Resistant|Detectable|Vulnerable|Vulnerable| -|**Single Key-Compromise MitM**|Resistant|Resistant|Resistant|Vulnerable| -|**Double Key-Compromise MitM**|Resistant|Detectable|Vulnerable|Vulnerable| -|**DOS Mitigation**|Yes|Yes|Yes|No| -|**Supports Fragmentation**|Yes|Yes|No|Yes| -|**FIPS Compliant**|Yes|Yes|No|No| -|**Small Code Footprint**|Yes|Yes|Yes|No| -|**RTT**|2|2|1|1| - -### Definitions - -* **Construction**: The mathematical construction the protocol is based upon. -* **Perfect Forward Secrecy**: An attacker with the static private keys of both party cannot decrypt recordings of messages sent between those parties. -* **Forward Secret Identity Hiding**: An attacker with the static private key of one or more parties cannot determine the identity of everyone they have previously communicated with. -* **Quantum Forward Secret**: A quantum computer powerful enough to break Elliptic-curve cryptography is not sufficient in order to decrypt recordings of messages sent between parties. -* **Ratcheted Forward Secrecy**: In order to break forward secrecy an attacker must record and break every single key exchange two parties perform, in order, starting from the first time they began communicating. Improves secrecy under weak or compromised RNG. -* **Silence is a Virtue**: A server running the protocol can be configured in such a way that it will not respond to an unauthenticated, anonymous or replayed message. -* **Key-Compromise Impersonation**: The attacker has a memory image of a single party, and attempts to create a brand new session with that party, pretending to be someone else. -* **Compromise-and-Impersonate**: The attacker has a memory image of a single party, and attempts to impersonate them on a brand new session with the other party. -* **Single Key-Compromise MitM**: The attacker has a memory image of a single party, and attempts to become a Man-in-the-Middle between them and any other party. -* **Double Key-Compromise MitM**: The attacker has a memory image of both parties, and attempts to become a Man-in-the-Middle between them. -* **Supports Fragmentation**: Transmission data can be fragmented into smaller units to support jumbo-sized data or MTU discovery. -* **FIPS Compliant**: The protocol uses FIPS approved cryptographic algorithms. -* **Small Code Footprint**: The Codebase implementing the protocol can be easily audited by anyone on the internet. -* **RTT**: "Round-Trip-Time" - How many round trips from initiator to responder it takes to establish a session. diff --git a/comparison.md b/comparison.md new file mode 100644 index 0000000..abcdd50 --- /dev/null +++ b/comparison.md @@ -0,0 +1,37 @@ + +## Security Properties + +| | Persistent ZSSP | Opportunistic ZSSP| WireGuard | ZeroTier Legacy Transport | +| --- | --- | --- | --- | --- | +|**Construction**|Noise\_XKhfs+psk2|Noise\_XKhfs+psk2|Noise\_IKpsk2|Static Diffie-Helman| +|**Perfect Forward Secrecy**|Yes|Yes|Yes|No| +|**Forward Secret Identity Hiding**|Yes|Yes|No|No| +|**Quantum Forward Secret**|Yes|Yes|No|No| +|**Ratcheted Forward Secrecy**|Yes|Yes|No|No| +|**Silence is a Virtue**|Yes|No|Yes|No| +|**Key-Compromise Impersonation**|Resistant|Resistant|Resistant|Vulnerable| +|**Compromise-and-Impersonate**|Resistant|Detectable|Vulnerable|Vulnerable| +|**Single Key-Compromise MitM**|Resistant|Resistant|Resistant|Vulnerable| +|**Double Key-Compromise MitM**|Resistant|Detectable|Vulnerable|Vulnerable| +|**DOS Mitigation**|Yes|Yes|Yes|No| +|**Supports Fragmentation**|Yes|Yes|No|Yes| +|**FIPS Compliant**|Yes|Yes|No|No| +|**Small Code Footprint**|Yes|Yes|Yes|No| +|**RTT**|2|2|1|1| + +### Definitions + +* **Construction**: The mathematical construction the protocol is based upon. +* **Perfect Forward Secrecy**: An attacker with the static private keys of both party cannot decrypt recordings of messages sent between those parties. +* **Forward Secret Identity Hiding**: An attacker with the static private key of one or more parties cannot determine the identity of everyone they have previously communicated with. +* **Quantum Forward Secret**: A quantum computer powerful enough to break Elliptic-curve cryptography is not sufficient in order to decrypt recordings of messages sent between parties. +* **Ratcheted Forward Secrecy**: In order to break forward secrecy an attacker must record and break every single key exchange two parties perform, in order, starting from the first time they began communicating. Improves secrecy under weak or compromised RNG. +* **Silence is a Virtue**: A server running the protocol can be configured in such a way that it will not respond to an unauthenticated, anonymous or replayed message. +* **Key-Compromise Impersonation**: The attacker has a memory image of a single party, and attempts to create a brand new session with that party, pretending to be someone else. +* **Compromise-and-Impersonate**: The attacker has a memory image of a single party, and attempts to impersonate them on a brand new session with the other party. +* **Single Key-Compromise MitM**: The attacker has a memory image of a single party, and attempts to become a Man-in-the-Middle between them and any other party. +* **Double Key-Compromise MitM**: The attacker has a memory image of both parties, and attempts to become a Man-in-the-Middle between them. +* **Supports Fragmentation**: Transmission data can be fragmented into smaller units to support jumbo-sized data or MTU discovery. +* **FIPS Compliant**: The protocol uses FIPS approved cryptographic algorithms. +* **Small Code Footprint**: The Codebase implementing the protocol can be easily audited by anyone on the internet. +* **RTT**: "Round-Trip-Time" - How many round trips from initiator to responder it takes to establish a session. diff --git a/examples/basic_test.rs b/examples/basic_test.rs index 76ce3ca..8aaafeb 100644 --- a/examples/basic_test.rs +++ b/examples/basic_test.rs @@ -22,7 +22,7 @@ use rand_core::RngCore; use sha2::Sha512; use zssp_proto::crypto_impl::PqcKyberSecretKey; -use zssp_proto::RatchetState; +use zssp_proto::{RatchetState, RatchetUpdate}; use zssp_proto::{Session, Settings, RATCHET_SIZE}; const TEST_MTU: usize = 1500; @@ -62,8 +62,8 @@ impl zssp_proto::ApplicationLayer for &TestApplication { type KeyPair = EphemeralSecret; type Kem = PqcKyberSecretKey; - type StorageError = (); - type Data = u128; + type StorageError = std::convert::Infallible; + type SessionData = u128; fn hello_requires_recognized_ratchet(&self) -> bool { false @@ -73,7 +73,7 @@ impl zssp_proto::ApplicationLayer for &TestApplication { true } - fn check_accept_session(&self, remote_static_key: &Self::PublicKey, identity: &[u8]) -> (Option<(bool, Self::Data)>, bool) { + fn check_accept_session(&self, remote_static_key: &Self::PublicKey, identity: &[u8]) -> (Option<(bool, Self::SessionData)>, bool) { (Some((true, 1)), true) } @@ -85,12 +85,12 @@ impl zssp_proto::ApplicationLayer for &TestApplication { fn restore_by_identity( &self, remote_static_key: &Self::PublicKey, - application_data: &Self::Data, + session_data: &Self::SessionData, ) -> Result<(RatchetState, Option), Self::StorageError> { let ratchets = self.ratchets.lock().unwrap(); Ok(ratchets .peer_map - .get(application_data) + .get(session_data) .cloned() .unwrap_or_else(|| RatchetState::new_initial_states())) } @@ -98,24 +98,24 @@ impl zssp_proto::ApplicationLayer for &TestApplication { fn save_ratchet_state( &self, remote_static_key: &Self::PublicKey, - application_data: &Self::Data, - state1: &RatchetState, - state2: Option<&RatchetState>, - state_added: Option<&RatchetState>, - state_deleted1: Option<&RatchetState>, - state_deleted2: Option<&RatchetState>, + session_data: &Self::SessionData, + update_data: RatchetUpdate<'_>, ) -> Result<(), Self::StorageError> { let mut ratchets = self.ratchets.lock().unwrap(); - ratchets.peer_map.insert(*application_data, (state1.clone(), state2.cloned())); + ratchets + .peer_map + .insert(*session_data, (update_data.state1.clone(), update_data.state2.cloned())); - if let Some(rs) = state_added { - ratchets.rf_map.insert(*rs.fingerprint().unwrap(), rs.clone()); - println!("[{}] new ratchet #{}", self.name, rs.chain_len); + if update_data.state1_was_just_added { + ratchets + .rf_map + .insert(*update_data.state1.fingerprint().unwrap(), update_data.state1.clone()); + println!("[{}] new ratchet #{}", self.name, update_data.state1.chain_len); } - if let Some(Some(rf)) = state_deleted1.map(|rs| rs.fingerprint()) { + if let Some(Some(rf)) = update_data.state_deleted1.map(|rs| rs.fingerprint()) { ratchets.rf_map.remove(rf); } - if let Some(Some(rf)) = state_deleted1.map(|rs| rs.fingerprint()) { + if let Some(Some(rf)) = update_data.state_deleted2.map(|rs| rs.fingerprint()) { ratchets.rf_map.remove(rf); } Ok(()) diff --git a/src/applicationlayer.rs b/src/applicationlayer.rs index 6c00d67..8476414 100644 --- a/src/applicationlayer.rs +++ b/src/applicationlayer.rs @@ -132,10 +132,10 @@ pub trait ApplicationLayer: Sized { /// A user-defined error returned when the `ApplicationLayer` fails to access persistent storage /// for a peer's ratchet states. - type StorageError: std::fmt::Debug; + type StorageError: std::error::Error; /// An arbitrary opaque object for use by the application that is attached to each session. - type Data; + type SessionData; /// Should return the current time in milliseconds. Does not have to be monotonic, nor synced /// with remote peers (although both of these properties would help reliability slightly). @@ -174,7 +174,7 @@ pub trait ApplicationLayer: Sized { /// must verify this identity is associated with the remote peer's static key. /// To prevent desync, if this function returns (Some(_), _), no other open session with the /// same remote peer must exist. Drop or call expire on any pre-existing sessions before returning. - fn check_accept_session(&self, remote_static_key: &Self::PublicKey, identity: &[u8]) -> (Option<(bool, Self::Data)>, bool); + fn check_accept_session(&self, remote_static_key: &Self::PublicKey, identity: &[u8]) -> (Option<(bool, Self::SessionData)>, bool); /// Lookup a specific ratchet state based on its ratchet fingerprint. /// This function will be called whenever Alice attempts to connect to us with a non-empty @@ -206,7 +206,7 @@ pub trait ApplicationLayer: Sized { fn restore_by_identity( &self, remote_static_key: &Self::PublicKey, - application_data: &Self::Data, + session_data: &Self::SessionData, ) -> Result<(RatchetState, Option), Self::StorageError>; /// Atomically save `current_state1` and `current_state2` so that them and only them can be /// restored with `restore_by_identity` and `restore_by_fingerprint` through a system restart. @@ -235,12 +235,8 @@ pub trait ApplicationLayer: Sized { fn save_ratchet_state( &self, remote_static_key: &Self::PublicKey, - application_data: &Self::Data, - current_state1: &RatchetState, - current_state2: Option<&RatchetState>, - state_added: Option<&RatchetState>, - state_deleted1: Option<&RatchetState>, - state_deleted2: Option<&RatchetState>, + session_data: &Self::SessionData, + update_data: RatchetUpdate<'_>, ) -> Result<(), Self::StorageError>; /// Receives a stream of events that occur during an execution of ZSSP. @@ -249,3 +245,13 @@ pub trait ApplicationLayer: Sized { #[cfg(feature = "logging")] fn event_log(&self, event: LogEvent<'_, Self>); } + +pub trait CryptoLayer {} + +pub struct RatchetUpdate<'a> { + pub state1: &'a RatchetState, + pub state2: Option<&'a RatchetState>, + pub state1_was_just_added: bool, + pub state_deleted1: Option<&'a RatchetState>, + pub state_deleted2: Option<&'a RatchetState>, +} diff --git a/src/context.rs b/src/context.rs index 182ef30..f4ff1cd 100644 --- a/src/context.rs +++ b/src/context.rs @@ -59,7 +59,6 @@ fn to_packet_nonce(n: &[u8; AES_GCM_IV_SIZE]) -> &[u8; PACKET_NONCE_SIZE] { (&n[n.len() - PACKET_NONCE_SIZE..]).try_into().unwrap() } -#[allow(unused)] impl Context { /// Create a new session context. pub fn new(static_secret_key: App::KeyPair, mut rng: App::Rng) -> Self { @@ -90,7 +89,7 @@ impl Context { /// contacted /// * `mtu` - MTU for initial packets /// * `static_remote_key` - Remote side's static public NIST P-384 key - /// * `application_data` - Arbitrary data meaningful to the application to include with session + /// * `session_data` - Arbitrary data meaningful to the application to include with session /// object /// * `identity` - Payload to be sent to Bob that contains the information necessary /// for the upper protocol to authenticate and approve of Alice's identity @@ -100,7 +99,7 @@ impl Context { send: impl FnMut(Vec) -> bool, mut mtu: usize, static_remote_key: App::PublicKey, - application_data: App::Data, + session_data: App::SessionData, identity: Vec, ) -> Result>, OpenError> { mtu = mtu.max(MIN_TRANSPORT_MTU); @@ -111,7 +110,7 @@ impl Context { app, &ctx, static_remote_key, - application_data, + session_data, identity, |Packet(kid, nonce, payload): &Packet| { // Process fragmentation layer. diff --git a/src/lib.rs b/src/lib.rs index 8892f1c..680a598 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,7 +40,7 @@ pub mod crypto; pub mod crypto_impl; pub mod result; -pub use applicationlayer::{ApplicationLayer, Settings}; +pub use applicationlayer::{ApplicationLayer, RatchetUpdate, Settings}; pub use context::Context; #[cfg(feature = "logging")] pub use log_event::LogEvent; diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index 3c49ca6..516c3bf 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -12,7 +12,7 @@ pub struct SymmetricState { h: [u8; HASHLEN], /// If anyone knows a better way to get rid of the "parameter `App` is never used" error please /// let me know. - _app: PhantomData App::Data>, + _app: PhantomData App::SessionData>, } impl Clone for SymmetricState { fn clone(&self) -> Self { diff --git a/src/zeta.rs b/src/zeta.rs index 748099b..b33bf53 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -5,6 +5,7 @@ use std::ops::{Deref, DerefMut}; use std::sync::{Arc, Mutex, Weak}; use zeroize::Zeroizing; +use crate::applicationlayer::RatchetUpdate; use crate::challenge::{gen_null_response, respond_to_challenge_in_place}; use crate::context::{log, ContextInner, SessionMap}; use crate::crypto::*; @@ -40,7 +41,7 @@ pub(crate) fn from_nonce(n: &[u8]) -> (u8, u64) { pub(crate) struct Zeta { ctx: Weak>, /// An arbitrary application defined object associated with each session. - pub application_data: App::Data, + pub session_data: App::SessionData, /// Is true if the local peer acted as Bob, the responder in the initial key exchange. pub was_bob: bool, @@ -255,12 +256,12 @@ pub(crate) fn trans_to_a1( app: App, ctx: &Arc>, s_remote: App::PublicKey, - application_data: App::Data, + session_data: App::SessionData, identity: Vec, send: impl FnOnce(&Packet), ) -> Result>, OpenError> { let (ratchet_state1, ratchet_state2) = app - .restore_by_identity(&s_remote, &application_data) + .restore_by_identity(&s_remote, &session_data) .map_err(|e| OpenError::RatchetIoError(e))?; let mut session_map = ctx.session_map.lock().unwrap(); @@ -274,7 +275,7 @@ pub(crate) fn trans_to_a1( let current_time = app.time(); let mut zeta = Zeta { ctx: Arc::downgrade(ctx), - application_data, + session_data, was_bob: false, s_remote, send_counter: INIT_COUNTER, @@ -539,12 +540,14 @@ pub(crate) fn received_x2_trans( }; let result = app.save_ratchet_state( &zeta.s_remote, - &zeta.application_data, - &new_ratchet_state, - ratchet_to_preserve, - Some(&new_ratchet_state), - ratchet_to_delete, - None, + &zeta.session_data, + RatchetUpdate { + state1: &new_ratchet_state, + state2: ratchet_to_preserve, + state1_was_just_added: true, + state_deleted1: ratchet_to_delete, + state_deleted2: None, + }, ); if let Err(e) = result { return Err(ReceiveError::RatchetIoError(e)); @@ -635,8 +638,8 @@ pub(crate) fn received_x3_trans( // handshake is being dropped, so nonce reuse can't happen. Packet(zeta.kid_send.get(), n, d) }; - if let Some((responder_disallows_downgrade, application_data)) = responder_disallows_downgrade { - let result = app.restore_by_identity(&s_remote, &application_data); + if let Some((responder_disallows_downgrade, session_data)) = responder_disallows_downgrade { + let result = app.restore_by_identity(&s_remote, &session_data); match result { Ok((ratchet_state1, ratchet_state2)) => { if (&zeta.ratchet_state != &ratchet_state1) & (Some(&zeta.ratchet_state) != ratchet_state2.as_ref()) { @@ -655,12 +658,14 @@ pub(crate) fn received_x3_trans( let new_ratchet_state = RatchetState::new(rk, rf, zeta.ratchet_state.chain_len + 1); let result = app.save_ratchet_state( &s_remote, - &application_data, - &new_ratchet_state, - None, - Some(&new_ratchet_state), - Some(&ratchet_state1), - ratchet_state2.as_ref(), + &session_data, + RatchetUpdate { + state1: &new_ratchet_state, + state2: None, + state1_was_just_added: true, + state_deleted1: Some(&ratchet_state1), + state_deleted2: ratchet_state2.as_ref(), + }, ); if let Err(e) = result { return Err(ReceiveError::RatchetIoError(e)); @@ -688,7 +693,7 @@ pub(crate) fn received_x3_trans( }; let session = Arc::new(Session(Mutex::new(Zeta { ctx: Arc::downgrade(ctx), - application_data, + session_data, was_bob: true, s_remote, send_counter: INIT_COUNTER + 1, @@ -759,12 +764,14 @@ pub(crate) fn received_c1_trans( if zeta.ratchet_state2.is_some() { let result = app.save_ratchet_state( &zeta.s_remote, - &zeta.application_data, - &zeta.ratchet_state1, - None, - None, - zeta.ratchet_state2.as_ref(), - None, + &zeta.session_data, + RatchetUpdate { + state1: &zeta.ratchet_state1, + state2: None, + state1_was_just_added: false, + state_deleted1: zeta.ratchet_state2.as_ref(), + state_deleted2: None, + }, ); if let Err(e) = result { return Err(ReceiveError::RatchetIoError(e)); @@ -1105,12 +1112,14 @@ pub(crate) fn received_k1_trans( let new_ratchet_state = RatchetState::new(rk, rf, zeta.ratchet_state1.chain_len + 1); let result = app.save_ratchet_state( &zeta.s_remote, - &zeta.application_data, - &new_ratchet_state, - Some(&zeta.ratchet_state1), - Some(&new_ratchet_state), - zeta.ratchet_state2.as_ref(), - None, + &zeta.session_data, + RatchetUpdate { + state1: &new_ratchet_state, + state2: Some(&zeta.ratchet_state1), + state1_was_just_added: true, + state_deleted1: zeta.ratchet_state2.as_ref(), + state_deleted2: None, + }, ); if let Err(e) = result { return Err(ReceiveError::RatchetIoError(e)); @@ -1201,12 +1210,14 @@ pub(crate) fn received_k2_trans( let new_ratchet_state = RatchetState::new(rk, rf, zeta.ratchet_state1.chain_len + 1); let result = app.save_ratchet_state( &zeta.s_remote, - &zeta.application_data, - &new_ratchet_state, - None, - Some(&new_ratchet_state), - Some(&zeta.ratchet_state1), - zeta.ratchet_state2.as_ref(), + &zeta.session_data, + RatchetUpdate { + state1: &new_ratchet_state, + state2: None, + state1_was_just_added: true, + state_deleted1: Some(&zeta.ratchet_state1), + state_deleted2: zeta.ratchet_state2.as_ref(), + }, ); if let Err(e) = result { return Err(ReceiveError::RatchetIoError(e)); diff --git a/whitepaper.pdf b/whitepaper.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c9231b70d133b24a21c573fde1826e1a3e17e6c6 GIT binary patch literal 420496 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ⅆpsq-BkEV*+gebK&*_aSeZ6#kVLw@*_o zjr${cyT$g#RM{kvnoZYkrYzmOIIU*$Mwb_Dj+4mSKD|WTYgSC?rF=7sjS5G< zH%*wa(XqGa_}aLfHJiihUa6&Y37ZFdHN9j!%G0CD>ha{1(uF5~6Ik4`o7NuTw|3)x8DG zyKZE!+wk;jz}Gk?{VOl@SEaKso&00JXkLZ6%Bxo2BI(!=dG<~dwc|VYbWUG5kwsmR z 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 z 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 kx9uvjG72aLHxhnkQl^2WTizc37vel0= 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 J__#Sf>>G+Y5)@utCs&U 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

<26(_7I>YQ^<=-Twkzg#-*{b5-v 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 D_+`j6 literal 0 HcmV?d00001 From f8487425c5718b34b04413c1b0c67cb31a1acb8e Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Fri, 4 Aug 2023 11:25:35 -0400 Subject: [PATCH 16/37] integrated suggestions --- examples/basic_test.rs | 14 +++++++++----- src/applicationlayer.rs | 15 ++++++++++----- src/context.rs | 2 +- src/fragmentation.rs | 2 +- src/lib.rs | 7 ++----- src/log_event.rs | 3 ++- src/result.rs | 3 ++- src/symmetric_state.rs | 2 +- src/zeta.rs | 9 ++++++--- 9 files changed, 34 insertions(+), 23 deletions(-) diff --git a/examples/basic_test.rs b/examples/basic_test.rs index 8aaafeb..650721b 100644 --- a/examples/basic_test.rs +++ b/examples/basic_test.rs @@ -21,9 +21,9 @@ use rand_core::OsRng; use rand_core::RngCore; use sha2::Sha512; +use zssp_proto::applicationlayer::{AcceptAction, ApplicationLayer, RatchetState, RatchetUpdate, Settings, RATCHET_SIZE}; use zssp_proto::crypto_impl::PqcKyberSecretKey; -use zssp_proto::{RatchetState, RatchetUpdate}; -use zssp_proto::{Session, Settings, RATCHET_SIZE}; +use zssp_proto::Session; const TEST_MTU: usize = 1500; @@ -44,7 +44,7 @@ impl Ratchets { } #[allow(unused)] -impl zssp_proto::ApplicationLayer for &TestApplication { +impl ApplicationLayer for &TestApplication { const SETTINGS: Settings = Settings { initial_offer_timeout: Settings::INITIAL_OFFER_TIMEOUT_MS, rekey_timeout: 60 * 1000, @@ -73,8 +73,12 @@ impl zssp_proto::ApplicationLayer for &TestApplication { true } - fn check_accept_session(&self, remote_static_key: &Self::PublicKey, identity: &[u8]) -> (Option<(bool, Self::SessionData)>, bool) { - (Some((true, 1)), true) + fn check_accept_session(&self, remote_static_key: &Self::PublicKey, identity: &[u8]) -> AcceptAction { + AcceptAction { + session_data: Some(1), + responder_disallows_downgrade: true, + responder_silently_rejects: false, + } } fn restore_by_fingerprint(&self, ratchet_fingerprint: &[u8; RATCHET_SIZE]) -> Result, Self::StorageError> { diff --git a/src/applicationlayer.rs b/src/applicationlayer.rs index 8476414..9856b4b 100644 --- a/src/applicationlayer.rs +++ b/src/applicationlayer.rs @@ -2,12 +2,13 @@ use rand_core::{CryptoRng, RngCore}; use std::sync::Arc; use crate::crypto::{AeadAesGcm, HashSha512, KeyPairP384, PrivateKeyKyber1024, PrpAes256, PublicKeyP384}; -use crate::proto::RATCHET_SIZE; -use crate::ratchet_state::RatchetState; #[cfg(feature = "logging")] use crate::LogEvent; use crate::Session; +pub use crate::proto::RATCHET_SIZE; +pub use crate::ratchet_state::RatchetState; + /// A container for a vast majority of the dynamic settings within ZSSP, including all time-based settings. /// If the user wishes to measure time in units other than milliseconds for some reason, then they can /// create an adjusted version of this struct with those units, and use it instead of the default. @@ -174,7 +175,7 @@ pub trait ApplicationLayer: Sized { /// must verify this identity is associated with the remote peer's static key. /// To prevent desync, if this function returns (Some(_), _), no other open session with the /// same remote peer must exist. Drop or call expire on any pre-existing sessions before returning. - fn check_accept_session(&self, remote_static_key: &Self::PublicKey, identity: &[u8]) -> (Option<(bool, Self::SessionData)>, bool); + fn check_accept_session(&self, remote_static_key: &Self::PublicKey, identity: &[u8]) -> AcceptAction; /// Lookup a specific ratchet state based on its ratchet fingerprint. /// This function will be called whenever Alice attempts to connect to us with a non-empty @@ -246,8 +247,6 @@ pub trait ApplicationLayer: Sized { fn event_log(&self, event: LogEvent<'_, Self>); } -pub trait CryptoLayer {} - pub struct RatchetUpdate<'a> { pub state1: &'a RatchetState, pub state2: Option<&'a RatchetState>, @@ -255,3 +254,9 @@ pub struct RatchetUpdate<'a> { pub state_deleted1: Option<&'a RatchetState>, pub state_deleted2: Option<&'a RatchetState>, } + +pub struct AcceptAction { + pub session_data: Option, + pub responder_disallows_downgrade: bool, + pub responder_silently_rejects: bool, +} diff --git a/src/context.rs b/src/context.rs index f4ff1cd..5309902 100644 --- a/src/context.rs +++ b/src/context.rs @@ -5,12 +5,12 @@ use std::hash::Hash; use std::num::NonZeroU32; use std::sync::{Arc, Mutex, Weak}; +use crate::applicationlayer::ApplicationLayer; use crate::crypto::{AES_256_KEY_SIZE, AES_GCM_IV_SIZE}; use crate::fragmentation::{send_with_fragmentation, DefragBuffer}; use crate::proto::*; use crate::result::{byzantine_fault, ReceiveError, ReceiveOk, SendError, SessionEvent}; use crate::zeta::*; -use crate::ApplicationLayer; #[cfg(feature = "logging")] use crate::LogEvent::*; use crate::{challenge::ChallengeContext, result::OpenError}; diff --git a/src/fragmentation.rs b/src/fragmentation.rs index 96ed746..409eff6 100644 --- a/src/fragmentation.rs +++ b/src/fragmentation.rs @@ -4,10 +4,10 @@ use std::sync::Mutex; use zeroize::Zeroizing; +use crate::applicationlayer::ApplicationLayer; use crate::crypto::{PrpAes256, AES_256_KEY_SIZE}; use crate::proto::*; use crate::result::{byzantine_fault, ReceiveError}; -use crate::ApplicationLayer; fn create_fragment_header(kid_send: u32, fragment_count: usize, fragment_no: usize, n: &[u8; PACKET_NONCE_SIZE]) -> [u8; HEADER_SIZE] { debug_assert!(fragment_count > 0); diff --git a/src/lib.rs b/src/lib.rs index 680a598..4a67dec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,8 +7,6 @@ */ //#![warn(missing_docs, rust_2018_idioms)] //! TODO: docs - -mod applicationlayer; mod challenge; mod context; mod fragmentation; @@ -19,6 +17,7 @@ mod ratchet_state; mod symmetric_state; mod zeta; +pub mod applicationlayer; /// A collection of implementation-independent traits for the various specific cryptographic /// algorithms ZSSP depends on. /// @@ -40,10 +39,8 @@ pub mod crypto; pub mod crypto_impl; pub mod result; -pub use applicationlayer::{ApplicationLayer, RatchetUpdate, Settings}; pub use context::Context; #[cfg(feature = "logging")] pub use log_event::LogEvent; -pub use proto::{MIN_TRANSPORT_MTU, RATCHET_SIZE}; -pub use ratchet_state::RatchetState; +pub use proto::MIN_TRANSPORT_MTU; pub use zeta::Session; diff --git a/src/log_event.rs b/src/log_event.rs index c95252f..b7b9428 100644 --- a/src/log_event.rs +++ b/src/log_event.rs @@ -1,6 +1,7 @@ use std::sync::Arc; -use crate::{ApplicationLayer, Session}; +use crate::applicationlayer::ApplicationLayer; +use crate::Session; /// ZSSP events that might be interesting to log or aggregate into metrics. pub enum LogEvent<'a, App: ApplicationLayer> { diff --git a/src/result.rs b/src/result.rs index 9537b77..c9fd0d6 100644 --- a/src/result.rs +++ b/src/result.rs @@ -1,6 +1,7 @@ use std::sync::Arc; -use crate::{ApplicationLayer, Session}; +use crate::applicationlayer::ApplicationLayer; +use crate::Session; /// An error that can occur when attempting to open a session. /// Depending on the error type trying again may not work. diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index 516c3bf..2d20cb7 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -2,9 +2,9 @@ use std::marker::PhantomData; use zeroize::Zeroizing; +use crate::applicationlayer::ApplicationLayer; use crate::crypto::{AeadAesGcm, HashSha512, AES_256_KEY_SIZE, AES_GCM_IV_SIZE, AES_GCM_TAG_SIZE}; use crate::proto::{HASHLEN, LABEL_KBKDF_CHAIN}; -use crate::ApplicationLayer; pub struct SymmetricState { k: Zeroizing<[u8; AES_256_KEY_SIZE]>, diff --git a/src/zeta.rs b/src/zeta.rs index b33bf53..8edee5b 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -5,6 +5,7 @@ use std::ops::{Deref, DerefMut}; use std::sync::{Arc, Mutex, Weak}; use zeroize::Zeroizing; +use crate::applicationlayer::ApplicationLayer; use crate::applicationlayer::RatchetUpdate; use crate::challenge::{gen_null_response, respond_to_challenge_in_place}; use crate::context::{log, ContextInner, SessionMap}; @@ -14,7 +15,6 @@ use crate::proto::*; use crate::ratchet_state::RatchetState; use crate::result::{byzantine_fault, FaultType, OpenError, ReceiveError, SendError}; use crate::symmetric_state::SymmetricState; -use crate::ApplicationLayer; #[cfg(feature = "logging")] use crate::LogEvent::*; @@ -627,7 +627,10 @@ pub(crate) fn received_x3_trans( let (kek_send, kek_recv) = noise.get_ask(LABEL_KEX_KEY); let c = INIT_COUNTER; - let (responder_disallows_downgrade, responder_silently_rejects) = app.check_accept_session(&s_remote, &x3[identity_start..identity_end]); + let action = app.check_accept_session(&s_remote, &x3[identity_start..identity_end]); + let responder_disallows_downgrade = action.responder_disallows_downgrade; + let responder_silently_rejects = action.responder_silently_rejects; + let session_data = action.session_data; let create_reject = || { let mut d = Vec::::new(); let n = to_nonce(PACKET_TYPE_SESSION_REJECTED, c); @@ -638,7 +641,7 @@ pub(crate) fn received_x3_trans( // handshake is being dropped, so nonce reuse can't happen. Packet(zeta.kid_send.get(), n, d) }; - if let Some((responder_disallows_downgrade, session_data)) = responder_disallows_downgrade { + if let Some(session_data) = session_data { let result = app.restore_by_identity(&s_remote, &session_data); match result { Ok((ratchet_state1, ratchet_state2)) => { From f01e60bbc2ebfb1687a6e5302209edeaf4c0cb57 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Fri, 4 Aug 2023 18:40:23 -0400 Subject: [PATCH 17/37] added paper references --- README.md | 2 + src/challenge.rs | 4 +- src/context.rs | 4 +- src/fragmentation.rs | 3 ++ src/proto.rs | 2 + src/ratchet_state.rs | 2 + src/symmetric_state.rs | 7 +++ src/zeta.rs | 100 +++++++++++++++++++++++++---------------- 8 files changed, 83 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index f043379..d164830 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ ZeroTier Secure Sessions Protocol ## Introduction +An in-depth guide to the full protocol specification can be found in the [protocol whitepaper](./whitepaper.pdf) provided in this repo. This implementation references it heavily. + ZeroTier Secure Socket Protocol (ZSSP) is a [Noise](http://noiseprotocol.org) protocol implementation using NIST/FIPS/CfSC compliant cryptographic primitives plus post-quantum forward secrecy via [Kyber1024](https://pq-crystals.org/kyber/). It also includes built-in support for fragmentation and defragmentation of large messages with strong resistance against denial of service attacks targeted against the fragmentation protocol. Specifically ZSSP implements the [Noise XK](http://noiseprotocol.org/noise.html#interactive-handshake-patterns-fundamental) interactive handshake pattern which provides strong forward secrecy not only for data but for the identities of the two participants in the session. The XK pattern was chosen instead of the more popular IK pattern used in popular Noise implementations like Wireguard due to ZeroTier identities being long lived and potentially tied to the real world identity of the user. As a result a Noise pattern providing identity forward secrecy was considered preferable as it offers some level of deniability for recorded traffic even after secret key compromise. diff --git a/src/challenge.rs b/src/challenge.rs index 5162656..26a695b 100644 --- a/src/challenge.rs +++ b/src/challenge.rs @@ -12,12 +12,13 @@ pub struct ChallengeContext { salt: [u8; SALT_SIZE], } +/// Corresponds to Algorithm 11 found in Section 5. pub fn gen_null_response(rng: &mut Rng) -> [u8; CHALLENGE_SIZE] { let mut response = [0u8; CHALLENGE_SIZE]; response[POW_START..].copy_from_slice(&rng.next_u64().to_be_bytes()); response } - +/// Corresponds to Algorithm 13 found in Section 5. pub fn respond_to_challenge_in_place( rng: &mut Rng, challenge: &[u8; CHALLENGE_SIZE], @@ -47,6 +48,7 @@ impl ChallengeContext { salt, } } + /// Corresponds to Algorithm 12 found in Section 5. pub fn process_hello( &mut self, addr: &impl std::hash::Hash, diff --git a/src/context.rs b/src/context.rs index 5309902..6b80a09 100644 --- a/src/context.rs +++ b/src/context.rs @@ -50,11 +50,13 @@ pub(crate) struct ContextInner { challenge: Mutex, } +/// Corresponds to Figure 10 found in Section 4.3. fn to_aes_nonce(pn: &[u8; PACKET_NONCE_SIZE]) -> [u8; AES_GCM_IV_SIZE] { let mut an = [0u8; AES_GCM_IV_SIZE]; an[2..].copy_from_slice(pn); an } +/// Corresponds to Figure 14 found near Section 6. fn to_packet_nonce(n: &[u8; AES_GCM_IV_SIZE]) -> &[u8; PACKET_NONCE_SIZE] { (&n[n.len() - PACKET_NONCE_SIZE..]).try_into().unwrap() } @@ -153,7 +155,7 @@ impl Context { log!(app, ReceivedRawFragment(p, c, frag_no, frag_count)); } if p == PACKET_TYPE_HANDSHAKE_RESPONSE { - if !matches!(&zeta.beta, ZsspAutomata::A1(_)) { + if !matches!(&zeta.beta, ZetaAutomata::A1(_)) { // A resent handshake response from Bob may have arrived out of order, // after we already received one. return Err(byzantine_fault!(OutOfSequence, false)); diff --git a/src/fragmentation.rs b/src/fragmentation.rs index 409eff6..2e0db42 100644 --- a/src/fragmentation.rs +++ b/src/fragmentation.rs @@ -9,6 +9,7 @@ use crate::crypto::{PrpAes256, AES_256_KEY_SIZE}; use crate::proto::*; use crate::result::{byzantine_fault, ReceiveError}; +/// Corresponds to Figure 13 found in Section 6. fn create_fragment_header(kid_send: u32, fragment_count: usize, fragment_no: usize, n: &[u8; PACKET_NONCE_SIZE]) -> [u8; HEADER_SIZE] { debug_assert!(fragment_count > 0); debug_assert!(fragment_count <= MAX_FRAGMENTS); @@ -21,6 +22,7 @@ fn create_fragment_header(kid_send: u32, fragment_count: usize, fragment_no: usi header } +/// Corresponds to the fragmentation algorithm described in Section 6. pub fn send_with_fragmentation( mut send: impl FnMut(Vec) -> bool, mtu: usize, @@ -72,6 +74,7 @@ impl DefragBuffer { Self { fragment_map: Mutex::new(HashMap::new()), hk_recv } } + /// Corresponds to the authentication and defragmentation algorithm described in Section 6.1. pub fn received_fragment( &self, mut raw_fragment: Vec, diff --git a/src/proto.rs b/src/proto.rs index 0b7a5fc..e7fc4ed 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -3,6 +3,8 @@ use crate::crypto::{AES_GCM_TAG_SIZE, KYBER_CIPHERTEXT_SIZE, KYBER_PUBLIC_KEY_SI /* Common constants */ /// Minimum physical MTU for ZSSP to function. +/// If an MTU is passed to ZSSP that is lower than this, it will be ignored and instead this value +/// will be used. pub const MIN_TRANSPORT_MTU: usize = 128; pub(crate) const KID_SIZE: usize = 4; diff --git a/src/ratchet_state.rs b/src/ratchet_state.rs index 829b6f3..ec5babe 100644 --- a/src/ratchet_state.rs +++ b/src/ratchet_state.rs @@ -4,6 +4,8 @@ use crate::crypto::{secure_eq, HashSha512}; use crate::proto::*; /// A ratchet key and fingerprint, /// along with the length of the ratchet chain the keys were derived from. +/// +/// Corresponds to the Ratchet Key and Ratchet Fingerprint described in Section 3. #[derive(Clone, Eq)] pub struct RatchetState { pub key: Zeroizing<[u8; RATCHET_SIZE]>, diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index 2d20cb7..bc4a965 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -37,6 +37,7 @@ impl SymmetricState { /// * L = `num_outputs*512u16` /// We have intentionally made every input small and fixed size to avoid unnecessary complexity /// and data representation ambiguity. + /// Corresponds to Noise `HKDF`. fn kbkdf( &self, input_key_material: &[u8], @@ -69,6 +70,7 @@ impl SymmetricState { } } + /// Corresponds to Noise `Initialize` on a SymmetricState. pub fn initialize(h: [u8; HASHLEN]) -> Self { Self { k: Zeroizing::default(), @@ -77,6 +79,7 @@ impl SymmetricState { _app: PhantomData, } } + /// Corresponds to Noise `MixKey`. pub fn mix_key(&mut self, input_key_material: &[u8]) { let mut next_ck = [0u8; HASHLEN]; let mut temp_k = [0u8; HASHLEN]; @@ -86,12 +89,14 @@ impl SymmetricState { *self.ck = next_ck; self.k.clone_from_slice(&temp_k[..AES_256_KEY_SIZE]); } + /// Corresponds to Noise `MixHash`. pub fn mix_hash(&mut self, data: &[u8]) { let mut hash = App::Hash::new(); hash.update(&self.h); hash.update(data); self.h = hash.finish(); } + /// Corresponds to Noise `MixKeyAndHash`. pub fn mix_key_and_hash(&mut self, input_key_material: &[u8]) { let mut next_ck = [0u8; HASHLEN]; let mut temp_h = [0u8; HASHLEN]; @@ -110,6 +115,7 @@ impl SymmetricState { self.mix_hash(&temp_h); self.k.clone_from_slice(&temp_k[..AES_256_KEY_SIZE]); } + /// Corresponds to Noise `EncryptAndHash`. pub fn encrypt_and_hash_in_place(&mut self, iv: [u8; AES_GCM_IV_SIZE], plaintext_start: usize, buffer: &mut Vec) { let tag = App::Aead::encrypt_in_place(&self.k, iv, Some(&self.h), &mut buffer[plaintext_start..]); buffer.extend(&tag); @@ -117,6 +123,7 @@ impl SymmetricState { hash.update(&buffer[plaintext_start..]); self.h = hash.finish(); } + /// Corresponds to Noise `DecryptAndHash`. #[must_use] pub fn decrypt_and_hash_in_place(&mut self, iv: [u8; AES_GCM_IV_SIZE], buffer: &mut [u8], tag: [u8; AES_GCM_TAG_SIZE]) -> bool { let mut hash = App::Hash::new(); diff --git a/src/zeta.rs b/src/zeta.rs index 8edee5b..18c5259 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -38,6 +38,7 @@ pub(crate) fn from_nonce(n: &[u8]) -> (u8, u64) { (n[c_start - 1], u64::from_be_bytes(n[c_start..].try_into().unwrap())) } +/// Corresponds to the Zeta State Machine found in Section 4.1. pub(crate) struct Zeta { ctx: Weak>, /// An arbitrary application defined object associated with each session. @@ -57,7 +58,7 @@ pub(crate) struct Zeta { resend_timer: i64, timeout_timer: i64, - pub beta: ZsspAutomata, + pub beta: ZetaAutomata, pub counter_antireplay_window: [u64; COUNTER_WINDOW_MAX_OOO], pub defrag: DefragBuffer, @@ -67,6 +68,7 @@ pub(crate) struct Zeta { /// A FIPS/NIST compliant variant of Noise_XK with hybrid Kyber1024 PQ data forward secrecy. pub struct Session(pub(crate) Mutex>); +/// Corresponds to State B_2 of the Zeta State Machine found in Section 4.1 - Definition 3. pub(crate) struct StateB2 { ratchet_state: RatchetState, kid_send: NonZeroU32, @@ -90,9 +92,11 @@ pub(crate) struct Keys { kid: Option, } +/// Corresponds to the tuple of values the Transition Algorithms send to the remote peer in Section 4.3. #[derive(Clone)] pub(crate) struct Packet(pub u32, pub [u8; AES_GCM_IV_SIZE], pub Vec); +/// Corresponds to State A_1 of the Zeta State Machine found in Section 4.1. #[derive(Clone)] pub(crate) struct StateA1 { noise: SymmetricState, @@ -102,7 +106,8 @@ pub(crate) struct StateA1 { packet: Packet, } -pub(crate) enum ZsspAutomata { +/// Corresponds to the ZKE Automata found in Section 4.1 - Definition 2. +pub(crate) enum ZetaAutomata { Null, A1(StateA1), A3 { @@ -186,7 +191,7 @@ impl Zeta { fn expire(&mut self) { self.resend_timer = i64::MAX; self.timeout_timer = i64::MAX; - self.beta = ZsspAutomata::Null; + self.beta = ZetaAutomata::Null; if let Some(ctx) = self.ctx.upgrade() { let mut session_map = ctx.session_map.lock().unwrap(); let mut sessions = ctx.sessions.lock().unwrap(); @@ -252,6 +257,7 @@ fn create_a1_state( packet: Packet(0, to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, c), x1), }) } +/// Corresponds to Transition Algorithm 1 found in Section 4.3. pub(crate) fn trans_to_a1( app: App, ctx: &Arc>, @@ -289,7 +295,7 @@ pub(crate) fn trans_to_a1( hk_send, resend_timer: current_time + App::SETTINGS.resend_time as i64, timeout_timer: current_time + App::SETTINGS.initial_offer_timeout as i64, - beta: ZsspAutomata::A1(a1), + beta: ZetaAutomata::A1(a1), }; zeta.key_mut(true).recv.kid = Some(kid_recv); @@ -301,8 +307,9 @@ pub(crate) fn trans_to_a1( Ok(session) } +/// Corresponds to Algorithm 13 found in Section 5. pub(crate) fn respond_to_challenge(zeta: &mut Zeta, rng: &Mutex, challenge: &[u8; CHALLENGE_SIZE]) { - if let ZsspAutomata::A1(StateA1 { packet: Packet(_, _, x1), .. }) = &mut zeta.beta { + if let ZetaAutomata::A1(StateA1 { packet: Packet(_, _, x1), .. }) = &mut zeta.beta { let response_start = x1.len() - CHALLENGE_SIZE; respond_to_challenge_in_place::( rng.lock().unwrap().deref_mut(), @@ -311,6 +318,7 @@ pub(crate) fn respond_to_challenge(zeta: &mut Zeta, ); } } +/// Corresponds to Transition Algorithm 2 found in Section 4.3. pub(crate) fn received_x1_trans( app: &App, ctx: &ContextInner, @@ -428,6 +436,7 @@ pub(crate) fn received_x1_trans( send(&Packet(kid_send.get(), to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, c), x2), &hk_send); Ok(()) } +/// Corresponds to Transition Algorithm 3 found in Section 4.3. pub(crate) fn received_x2_trans( zeta: &mut Zeta, session: &Arc>, @@ -452,7 +461,7 @@ pub(crate) fn received_x2_trans( return Err(byzantine_fault!(FailedAuth, true)); } let result = (|| { - if let ZsspAutomata::A1(StateA1 { noise, e_secret, e1_secret, identity, .. }) = &zeta.beta { + if let ZetaAutomata::A1(StateA1 { noise, e_secret, e1_secret, identity, .. }) = &zeta.beta { let mut noise = noise.clone(); let mut i = 0; // Process message pattern 2 e token. @@ -570,7 +579,7 @@ pub(crate) fn received_x2_trans( zeta.resend_timer = current_time + App::SETTINGS.resend_time as i64; zeta.timeout_timer = current_time + App::SETTINGS.initial_offer_timeout as i64; let packet = Packet(kid_send.get(), n, x3); - zeta.beta = ZsspAutomata::A3 { identity, packet: packet.clone() }; + zeta.beta = ZetaAutomata::A3 { identity, packet: packet.clone() }; Ok(packet) } else { @@ -584,6 +593,7 @@ pub(crate) fn received_x2_trans( } result.map(|_| ()) } +/// Corresponds to Transition Algorithm 4 found in Section 4.3. pub(crate) fn received_x3_trans( zeta: StateB2, app: &App, @@ -708,7 +718,7 @@ pub(crate) fn received_x3_trans( hk_send: zeta.hk_send.clone(), resend_timer: current_time + App::SETTINGS.resend_time as i64, timeout_timer: current_time + App::SETTINGS.rekey_timeout as i64, - beta: ZsspAutomata::S1, + beta: ZetaAutomata::S1, counter_antireplay_window: std::array::from_fn(|_| 0), defrag: zeta.defrag, }))); @@ -727,6 +737,7 @@ pub(crate) fn received_x3_trans( Err(byzantine_fault!(FailedAuth, true)) } } +/// Corresponds to Transition Algorithm 5 found in Section 4.3. pub(crate) fn received_c1_trans( zeta: &mut Zeta, app: &App, @@ -761,9 +772,9 @@ pub(crate) fn received_c1_trans( return Err(byzantine_fault!(ExpiredCounter, true)); } - let just_establised = is_other && matches!(&zeta.beta, ZsspAutomata::A3 { .. }); + let just_establised = is_other && matches!(&zeta.beta, ZetaAutomata::A3 { .. }); if is_other { - if let ZsspAutomata::A3 { .. } | ZsspAutomata::R2 { .. } = &zeta.beta { + if let ZetaAutomata::A3 { .. } | ZetaAutomata::R2 { .. } = &zeta.beta { if zeta.ratchet_state2.is_some() { let result = app.save_ratchet_state( &zeta.s_remote, @@ -788,7 +799,7 @@ pub(crate) fn received_c1_trans( .rekey_after_time .saturating_sub(rng.lock().unwrap().next_u64() % App::SETTINGS.rekey_time_max_jitter) as i64; zeta.resend_timer = i64::MAX; - zeta.beta = ZsspAutomata::S2; + zeta.beta = ZetaAutomata::S2; } } let mut c2 = Vec::new(); @@ -803,6 +814,8 @@ pub(crate) fn received_c1_trans( send(&Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, c2), Some(&zeta.hk_send)); Ok(just_establised) } +/// Corresponds to the trivial Transition Algorithm described for processing C_2 packets found in +/// Section 4.3. pub(crate) fn received_c2_trans( zeta: &mut Zeta, app: &App, @@ -820,7 +833,7 @@ pub(crate) fn received_c2_trans( // Some acknowledgement may have arrived extremely delayed. return Err(byzantine_fault!(UnknownLocalKeyId, false)); } - if !matches!(&zeta.beta, ZsspAutomata::S1) { + if !matches!(&zeta.beta, ZetaAutomata::S1) { // Some acknowledgement may have arrived extremely delayed. return Err(byzantine_fault!(OutOfSequence, false)); } @@ -839,9 +852,11 @@ pub(crate) fn received_c2_trans( .rekey_after_time .saturating_sub(rng.lock().unwrap().next_u64() % App::SETTINGS.rekey_time_max_jitter) as i64; zeta.resend_timer = i64::MAX; - zeta.beta = ZsspAutomata::S2; + zeta.beta = ZetaAutomata::S2; Ok(()) } +/// Corresponds to the trivial Transition Algorithm described for processing D packets found in +/// Section 4.3. pub(crate) fn received_d_trans( zeta: &mut Zeta, kid: NonZeroU32, @@ -853,7 +868,7 @@ pub(crate) fn received_d_trans( if d.len() != SESSION_REJECTED_SIZE { return Err(byzantine_fault!(InvalidPacket, true)); } - if Some(kid) != zeta.key_ref(true).recv.kid || !matches!(&zeta.beta, ZsspAutomata::A3 { .. }) { + if Some(kid) != zeta.key_ref(true).recv.kid || !matches!(&zeta.beta, ZetaAutomata::A3 { .. }) { return Err(byzantine_fault!(OutOfSequence, true)); } @@ -869,6 +884,7 @@ pub(crate) fn received_d_trans( zeta.expire(); Ok(()) } +/// Corresponds to the timer rules of the Zeta State Machine found in Section 4.1 - Definition 3. pub(crate) fn service( zeta: &mut Zeta, session: &Arc>, @@ -880,28 +896,29 @@ pub(crate) fn service( if zeta.timeout_timer <= current_time { timeout_trans(zeta, session, app, ctx, current_time, send); } else if zeta.resend_timer <= current_time { + // Corresponds to the resend timer rules found in Section 4.1 - Definition 3. zeta.resend_timer = current_time + App::SETTINGS.resend_time as i64; let (p, mut control_payload) = match &zeta.beta { - ZsspAutomata::Null => return, - ZsspAutomata::A1(StateA1 { packet, .. }) => { + ZetaAutomata::Null => return, + ZetaAutomata::A1(StateA1 { packet, .. }) => { log!(app, ResentX1(session)); return send(packet, None); } - ZsspAutomata::A3 { packet, .. } => { + ZetaAutomata::A3 { packet, .. } => { log!(app, ResentX3(session)); return send(packet, Some(&zeta.hk_send)); } - ZsspAutomata::S1 => { + ZetaAutomata::S1 => { log!(app, ResentKeyConfirm(session)); (PACKET_TYPE_KEY_CONFIRM, Vec::new()) } - ZsspAutomata::S2 => return, - ZsspAutomata::R1 { k1, .. } => { + ZetaAutomata::S2 => return, + ZetaAutomata::R1 { k1, .. } => { log!(app, ResentK1(session)); (PACKET_TYPE_REKEY_INIT, k1.clone()) } - ZsspAutomata::R2 { k2, .. } => { + ZetaAutomata::R2 { k2, .. } => { log!(app, ResentK2(session)); (PACKET_TYPE_REKEY_COMPLETE, k2.clone()) } @@ -928,7 +945,7 @@ fn remap(session: &Arc>, zeta: &Zeta, r session_map.insert(new_kid_recv, weak); new_kid_recv } -#[allow(unused)] +/// Corresponds to the timeout timer Transition Algorithm described in Section 4.1 - Definition 3. fn timeout_trans( zeta: &mut Zeta, session: &Arc>, @@ -938,9 +955,9 @@ fn timeout_trans( send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), ) { match &zeta.beta { - ZsspAutomata::Null => {} - ZsspAutomata::A1(StateA1 { identity, .. }) | ZsspAutomata::A3 { identity, .. } => { - if matches!(&zeta.beta, ZsspAutomata::A1(_)) { + ZetaAutomata::Null => {} + ZetaAutomata::A1(StateA1 { identity, .. }) | ZetaAutomata::A3 { identity, .. } => { + if matches!(&zeta.beta, ZetaAutomata::A1(_)) { log!(app, TimeoutX1(session)); } else { log!(app, TimeoutX3(session)); @@ -963,7 +980,7 @@ fn timeout_trans( zeta.key_mut(true).recv.kid = Some(new_kid_recv); zeta.resend_timer = current_time + App::SETTINGS.resend_time as i64; zeta.timeout_timer = current_time + App::SETTINGS.initial_offer_timeout as i64; - zeta.beta = ZsspAutomata::A1(a1); + zeta.beta = ZetaAutomata::A1(a1); zeta.defrag = DefragBuffer::new(Some(hk_recv)); send(&packet, None); @@ -971,7 +988,8 @@ fn timeout_trans( zeta.expire(); } } - ZsspAutomata::S2 => { + ZetaAutomata::S2 => { + // Corresponds to Transition Algorithm 6 found in Section 4.3. log!(app, StartedRekeyingSentK1(session)); let new_kid_recv = remap(session, &zeta, &ctx.rng, &ctx.session_map); // -> s @@ -1005,7 +1023,7 @@ fn timeout_trans( zeta.key_mut(true).recv.kid = Some(new_kid_recv); zeta.timeout_timer = current_time + App::SETTINGS.rekey_timeout as i64; zeta.resend_timer = current_time + App::SETTINGS.resend_time as i64; - zeta.beta = ZsspAutomata::R1 { noise, e_secret, k1: k1.clone() }; + zeta.beta = ZetaAutomata::R1 { noise, e_secret, k1: k1.clone() }; let c = zeta.send_counter; zeta.send_counter += 1; @@ -1015,20 +1033,21 @@ fn timeout_trans( send(&Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, k1), Some(&zeta.hk_send)); } - ZsspAutomata::S1 { .. } => { + ZetaAutomata::S1 { .. } => { log!(app, TimeoutKeyConfirm(session)); zeta.expire(); } - ZsspAutomata::R1 { .. } => { + ZetaAutomata::R1 { .. } => { log!(app, TimeoutK1(session)); zeta.expire(); } - ZsspAutomata::R2 { .. } => { + ZetaAutomata::R2 { .. } => { log!(app, TimeoutK2(session)); zeta.expire(); } } } +/// Corresponds to Transition Algorithm 7 found in Section 4.3. pub(crate) fn received_k1_trans( zeta: &mut Zeta, session: &Arc>, @@ -1055,8 +1074,8 @@ pub(crate) fn received_k1_trans( return Err(byzantine_fault!(UnknownLocalKeyId, false)); } let should_rekey_as_bob = match &zeta.beta { - ZsspAutomata::S2 { .. } => true, - ZsspAutomata::R1 { .. } => zeta.was_bob, + ZetaAutomata::S2 { .. } => true, + ZetaAutomata::R1 { .. } => zeta.was_bob, _ => false, }; if !should_rekey_as_bob { @@ -1142,7 +1161,7 @@ pub(crate) fn received_k1_trans( zeta.key_creation_counter = zeta.send_counter; zeta.timeout_timer = current_time + App::SETTINGS.rekey_timeout as i64; zeta.resend_timer = current_time + App::SETTINGS.resend_time as i64; - zeta.beta = ZsspAutomata::R2 { k2: k2.clone() }; + zeta.beta = ZetaAutomata::R2 { k2: k2.clone() }; let c = zeta.send_counter; zeta.send_counter += 1; @@ -1158,6 +1177,7 @@ pub(crate) fn received_k1_trans( } result } +/// Corresponds to Transition Algorithm 8 found in Section 4.3. pub(crate) fn received_k2_trans( zeta: &mut Zeta, app: &App, @@ -1175,7 +1195,7 @@ pub(crate) fn received_k2_trans( // Some rekey packet may have arrived extremely delayed. return Err(byzantine_fault!(UnknownLocalKeyId, false)); } - if !matches!(&zeta.beta, ZsspAutomata::R1 { .. }) { + if !matches!(&zeta.beta, ZetaAutomata::R1 { .. }) { // Some rekey packet may have arrived extremely delayed. return Err(byzantine_fault!(OutOfSequence, false)); } @@ -1191,7 +1211,7 @@ pub(crate) fn received_k2_trans( } k2.truncate(i); let result = (|| { - if let ZsspAutomata::R1 { noise, e_secret, .. } = &zeta.beta { + if let ZetaAutomata::R1 { noise, e_secret, .. } = &zeta.beta { let mut noise = noise.clone(); let mut i = 0; // Process message pattern 2 e token. @@ -1239,7 +1259,7 @@ pub(crate) fn received_k2_trans( zeta.key_creation_counter = zeta.send_counter; zeta.timeout_timer = current_time + App::SETTINGS.rekey_timeout as i64; zeta.resend_timer = current_time + App::SETTINGS.resend_time as i64; - zeta.beta = ZsspAutomata::S1; + zeta.beta = ZetaAutomata::S1; let mut c1 = Vec::new(); let c = zeta.send_counter; @@ -1259,6 +1279,7 @@ pub(crate) fn received_k2_trans( } result } +/// Corresponds to Algorithm 9 found in Section 4.3. pub(crate) fn send_payload( zeta: &mut Zeta, mut payload: Vec, @@ -1266,12 +1287,12 @@ pub(crate) fn send_payload( ) -> Result<(), SendError> { use SendError::*; - if matches!(&zeta.beta, ZsspAutomata::Null) { + if matches!(&zeta.beta, ZetaAutomata::Null) { return Err(SessionExpired); } if !matches!( &zeta.beta, - ZsspAutomata::S1 | ZsspAutomata::S2 | ZsspAutomata::R1 { .. } | ZsspAutomata::R2 { .. } + ZetaAutomata::S1 | ZetaAutomata::S2 | ZetaAutomata::R1 { .. } | ZetaAutomata::R2 { .. } ) { return Err(SessionNotEstablished); } @@ -1293,6 +1314,7 @@ pub(crate) fn send_payload( send(&Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, payload), Some(&zeta.hk_send)); Ok(()) } +/// Corresponds to Algorithm 10 found in Section 4.3. pub(crate) fn received_payload_in_place( zeta: &mut Zeta, kid: NonZeroU32, From d8e417338c237e598ca5e88fca6d1c9d9ee03805 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Fri, 4 Aug 2023 18:41:15 -0400 Subject: [PATCH 18/37] added docs --- src/zeta.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/zeta.rs b/src/zeta.rs index 18c5259..3b9aa39 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -25,6 +25,8 @@ use crate::LogEvent::*; /// it as effectively AAD. Other elements of the header are either not authenticated, /// like fragmentation info, or their authentication is implied via key exchange like /// the key id. +/// +/// Corresponds to Figure 10 found in Section 4.3. pub(crate) fn to_nonce(packet_type: u8, counter: u64) -> [u8; AES_GCM_IV_SIZE] { let mut ret = [0u8; AES_GCM_IV_SIZE]; ret[3] = packet_type; @@ -32,6 +34,7 @@ pub(crate) fn to_nonce(packet_type: u8, counter: u64) -> [u8; AES_GCM_IV_SIZE] { ret[4..].copy_from_slice(&counter.to_be_bytes()); ret } +/// Corresponds to Figure 10 found in Section 4.3. pub(crate) fn from_nonce(n: &[u8]) -> (u8, u64) { assert!(n.len() >= PACKET_NONCE_SIZE); let c_start = n.len() - 8; From 3717fdfd62a4dcb05e2821724875bc578070652c Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Fri, 4 Aug 2023 18:41:45 -0400 Subject: [PATCH 19/37] added docs --- src/zeta.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zeta.rs b/src/zeta.rs index 3b9aa39..19b655d 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -34,7 +34,7 @@ pub(crate) fn to_nonce(packet_type: u8, counter: u64) -> [u8; AES_GCM_IV_SIZE] { ret[4..].copy_from_slice(&counter.to_be_bytes()); ret } -/// Corresponds to Figure 10 found in Section 4.3. +/// Corresponds to Figure 10 and Figure 14 found in Section 4.3. pub(crate) fn from_nonce(n: &[u8]) -> (u8, u64) { assert!(n.len() >= PACKET_NONCE_SIZE); let c_start = n.len() - 8; From 778d9679e15ac62523ef7eb282783c8ec9bcc6ab Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Sun, 6 Aug 2023 17:06:24 -0400 Subject: [PATCH 20/37] fixed major handshake bug --- src/symmetric_state.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index bc4a965..878769c 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -119,14 +119,13 @@ impl SymmetricState { pub fn encrypt_and_hash_in_place(&mut self, iv: [u8; AES_GCM_IV_SIZE], plaintext_start: usize, buffer: &mut Vec) { let tag = App::Aead::encrypt_in_place(&self.k, iv, Some(&self.h), &mut buffer[plaintext_start..]); buffer.extend(&tag); - let mut hash = App::Hash::new(); - hash.update(&buffer[plaintext_start..]); - self.h = hash.finish(); + self.mix_hash(&buffer[plaintext_start..]); } /// Corresponds to Noise `DecryptAndHash`. #[must_use] pub fn decrypt_and_hash_in_place(&mut self, iv: [u8; AES_GCM_IV_SIZE], buffer: &mut [u8], tag: [u8; AES_GCM_TAG_SIZE]) -> bool { let mut hash = App::Hash::new(); + hash.update(&self.h); hash.update(buffer); hash.update(&tag); let ret = App::Aead::decrypt_in_place(&self.k, iv, Some(&self.h), buffer, tag); From 75bc95079ba1972738c28d00ec51e2c86bb6a548 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 7 Aug 2023 09:37:16 -0400 Subject: [PATCH 21/37] added missing constants --- src/challenge.rs | 2 +- src/context.rs | 6 +++--- src/fragmentation.rs | 2 +- src/proto.rs | 33 +++++++++++++++++++++++++++++---- src/result.rs | 2 ++ src/symmetric_state.rs | 6 +++--- src/zeta.rs | 2 +- 7 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/challenge.rs b/src/challenge.rs index 26a695b..1a4b68b 100644 --- a/src/challenge.rs +++ b/src/challenge.rs @@ -8,7 +8,7 @@ use crate::proto::*; pub struct ChallengeContext { pub enabled: bool, counter: u64, - antireplay_window: [u64; COUNTER_WINDOW_MAX_OOO], + antireplay_window: [u64; CHALLENGE_COUNTER_WINDOW_MAX_OOO], salt: [u8; SALT_SIZE], } diff --git a/src/context.rs b/src/context.rs index 6b80a09..6e7cc98 100644 --- a/src/context.rs +++ b/src/context.rs @@ -83,9 +83,6 @@ impl Context { /// Create a new session and send initial packet(s) to other side. /// - /// This will return SendError::DataTooLarge if the combined size of the metadata and the local - /// static public blob (as retrieved from the application layer) exceed MAX_INIT_PAYLOAD_SIZE. - /// /// * `app` - Application layer instance /// * `send` - Function to be called to send one or more initial packets to the remote being /// contacted @@ -105,6 +102,9 @@ impl Context { identity: Vec, ) -> Result>, OpenError> { mtu = mtu.max(MIN_TRANSPORT_MTU); + if identity.len() > MAX_IDENTITY_BLOB_SIZE { + return Err(OpenError::IdentityTooLarge); + } let ctx = &self.0; // Process zeta layer. diff --git a/src/fragmentation.rs b/src/fragmentation.rs index 2e0db42..a0ceda1 100644 --- a/src/fragmentation.rs +++ b/src/fragmentation.rs @@ -82,7 +82,7 @@ impl DefragBuffer { vrfy: impl FnOnce(&[u8; PACKET_NONCE_SIZE], usize, usize) -> Result<(), ReceiveError>, ) -> Result)>, ReceiveError> { use crate::result::FaultType::*; - if raw_fragment.len() < HEADER_AUTH_END { + if raw_fragment.len() < MIN_PACKET_SIZE { return Err(byzantine_fault!(InvalidPacket, true)); } diff --git a/src/proto.rs b/src/proto.rs index e7fc4ed..abd14d3 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -2,6 +2,8 @@ use crate::crypto::{AES_GCM_TAG_SIZE, KYBER_CIPHERTEXT_SIZE, KYBER_PUBLIC_KEY_SI /* Common constants */ +/// Minimum size of a valid physical ZSSP packet of any type. Anything smaller is discarded. +pub const MIN_PACKET_SIZE: usize = HEADER_SIZE + AES_GCM_TAG_SIZE; /// Minimum physical MTU for ZSSP to function. /// If an MTU is passed to ZSSP that is lower than this, it will be ignored and instead this value /// will be used. @@ -43,6 +45,8 @@ pub(crate) const PACKET_NONCE_START: usize = HEADER_SIZE - PACKET_NONCE_SIZE; pub(crate) const FRAGMENT_NO_IDX: usize = 4; pub(crate) const FRAGMENT_COUNT_IDX: usize = 5; +/// Maximum number of fragments a single packet may be split into. If a packet cannot fit +/// into this number of fragments it will be dropped. pub(crate) const MAX_FRAGMENTS: usize = 48; /* Key exchange constants */ @@ -66,9 +70,10 @@ pub(crate) const HASHLEN: usize = SHA512_HASH_SIZE; /// The size in bytes of both a ratchet key and a ratchet fingerprint. pub const RATCHET_SIZE: usize = 32; -pub(crate) const PROTOCOL_NAME_NOISE_XK: [u8; HASHLEN] = *b"Noise_XKhfs+psk2_P384+Kyber1024_AESGCM_SHA512\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; -pub(crate) const PROTOCOL_NAME_NOISE_KK: [u8; HASHLEN] = - *b"Noise_KKpsk0_P384_AESGCM_SHA512\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; +/// Initial value of 'h'. +pub(crate) const PROTOCOL_NAME_NOISE_XK: &[u8; HASHLEN] = b"Noise_XKhfs+psk2_P384+Kyber1024_AESGCM_SHA512\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; +/// Initial value of 'ck' for rekeying. +pub(crate) const PROTOCOL_NAME_NOISE_KK: &[u8; HASHLEN] = b"Noise_KKpsk0_P384_AESGCM_SHA512\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; pub(crate) const LABEL_OTP_TO_RATCHET: &[u8; 19] = b"ZSSP_OTP_TO_RATCHET"; pub(crate) const LABEL_KBKDF_CHAIN: &[u8; 4] = b"ZSSP"; @@ -77,9 +82,20 @@ pub(crate) const LABEL_HEADER_KEY: &[u8; 4] = b"ASKH"; pub(crate) const LABEL_KEX_KEY: &[u8; 4] = b"ASKK"; pub(crate) const INIT_COUNTER: u64 = 0; -pub(crate) const EXPIRE_AFTER_USES: u64 = 4294967295; +pub(crate) const EXPIRE_AFTER_USES: u64 = 1<<32 - 1; +/// Determines the number of counters a session will remember. If a counter arrives over +/// this amount out of order relative to other received counters, it is likely to be +/// rejected on the basis that the session can't remember if this counter was replayed. +/// Increasing this value makes a session consume more memory. pub(crate) const COUNTER_WINDOW_MAX_OOO: usize = 64; +/// Maximum number of counter steps that the counter is allowed to skip ahead. +/// This cannot be changed away from 2^24 without changing the header nonce handling code. pub(crate) const COUNTER_WINDOW_MAX_SKIP_AHEAD: u64 = 1 << 24; +/// Similar to `COUNTER_WINDOW_MAX_OOO`, except this governs the receive context challenge +/// counter rather than the session counter. +/// When Bob issues a challenge to Alice to mitigate DDOS, Bob will only accept Alice's +/// response once, and then its attached counter is added to the window. +pub(crate) const CHALLENGE_COUNTER_WINDOW_MAX_OOO: usize = 32; /* Packet constants */ @@ -101,9 +117,18 @@ pub(crate) const HANDSHAKE_HELLO_MAX_SIZE: usize = HANDSHAKE_HELLO_MIN_SIZE + RA pub(crate) const HANDSHAKE_RESPONSE_SIZE: usize = P384_PUBLIC_KEY_SIZE + KYBER_CIPHERTEXT_SIZE + AES_GCM_TAG_SIZE + KID_SIZE + AES_GCM_TAG_SIZE; pub(crate) const HANDSHAKE_COMPLETION_MIN_SIZE: usize = P384_PUBLIC_KEY_SIZE + AES_GCM_TAG_SIZE + 0 + AES_GCM_TAG_SIZE; +pub(crate) const HANDSHAKE_COMPLETION_MAX_SIZE: usize = HANDSHAKE_COMPLETION_MIN_SIZE + MAX_IDENTITY_BLOB_SIZE; pub(crate) const KEY_CONFIRMATION_SIZE: usize = AES_GCM_TAG_SIZE; pub(crate) const ACKNOWLEDGEMENT_SIZE: usize = AES_GCM_TAG_SIZE; pub(crate) const SESSION_REJECTED_SIZE: usize = AES_GCM_TAG_SIZE; pub(crate) const REKEY_SIZE: usize = P384_PUBLIC_KEY_SIZE + KID_SIZE + AES_GCM_TAG_SIZE + AES_GCM_TAG_SIZE; + +/// The application has the ability to attach a data payload to Alice's handshake. +/// It will be the first payload Bob receives from Alice. +/// The application also must attach a static public identity to their handshake. +/// The combined size of both in bytes must be at most this value. +/// +/// If not ZSSP will return `OpenError::DataTooLarge` and refuse to create a session object. +pub const MAX_IDENTITY_BLOB_SIZE: usize = 4096; diff --git a/src/result.rs b/src/result.rs index c9fd0d6..963017f 100644 --- a/src/result.rs +++ b/src/result.rs @@ -10,6 +10,8 @@ pub enum OpenError { /// An invalid parameter was supplied to the function. InvalidPublicKey, + IdentityTooLarge, + RatchetIoError(IoError), } diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index 878769c..702198a 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -71,11 +71,11 @@ impl SymmetricState { } /// Corresponds to Noise `Initialize` on a SymmetricState. - pub fn initialize(h: [u8; HASHLEN]) -> Self { + pub fn initialize(h: &[u8; HASHLEN]) -> Self { Self { k: Zeroizing::default(), - ck: Zeroizing::new(h), - h, + ck: Zeroizing::new(*h), + h: *h, _app: PhantomData, } } diff --git a/src/zeta.rs b/src/zeta.rs index 19b655d..7113133 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -607,7 +607,7 @@ pub(crate) fn received_x3_trans( ) -> Result>, ReceiveError> { use FaultType::*; // -> s, se - if x3.len() < HANDSHAKE_COMPLETION_MIN_SIZE { + if !(HANDSHAKE_COMPLETION_MIN_SIZE..=HANDSHAKE_COMPLETION_MAX_SIZE).contains(&x3.len()) { return Err(byzantine_fault!(InvalidPacket, true)); } if kid != zeta.kid_recv { From 3395f7b7cf6992dbda9cca10ef4bb4b76a9639f5 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 7 Aug 2023 09:41:06 -0400 Subject: [PATCH 22/37] cargo fmt --- src/context.rs | 2 +- src/proto.rs | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/context.rs b/src/context.rs index 6e7cc98..2974c51 100644 --- a/src/context.rs +++ b/src/context.rs @@ -102,7 +102,7 @@ impl Context { identity: Vec, ) -> Result>, OpenError> { mtu = mtu.max(MIN_TRANSPORT_MTU); - if identity.len() > MAX_IDENTITY_BLOB_SIZE { + if identity.len() > IDENTITY_MAX_SIZE { return Err(OpenError::IdentityTooLarge); } let ctx = &self.0; diff --git a/src/proto.rs b/src/proto.rs index abd14d3..d0ee289 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -73,7 +73,8 @@ pub const RATCHET_SIZE: usize = 32; /// Initial value of 'h'. pub(crate) const PROTOCOL_NAME_NOISE_XK: &[u8; HASHLEN] = b"Noise_XKhfs+psk2_P384+Kyber1024_AESGCM_SHA512\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; /// Initial value of 'ck' for rekeying. -pub(crate) const PROTOCOL_NAME_NOISE_KK: &[u8; HASHLEN] = b"Noise_KKpsk0_P384_AESGCM_SHA512\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; +pub(crate) const PROTOCOL_NAME_NOISE_KK: &[u8; HASHLEN] = + b"Noise_KKpsk0_P384_AESGCM_SHA512\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; pub(crate) const LABEL_OTP_TO_RATCHET: &[u8; 19] = b"ZSSP_OTP_TO_RATCHET"; pub(crate) const LABEL_KBKDF_CHAIN: &[u8; 4] = b"ZSSP"; @@ -82,7 +83,7 @@ pub(crate) const LABEL_HEADER_KEY: &[u8; 4] = b"ASKH"; pub(crate) const LABEL_KEX_KEY: &[u8; 4] = b"ASKK"; pub(crate) const INIT_COUNTER: u64 = 0; -pub(crate) const EXPIRE_AFTER_USES: u64 = 1<<32 - 1; +pub(crate) const EXPIRE_AFTER_USES: u64 = 1 << 32 - 1; /// Determines the number of counters a session will remember. If a counter arrives over /// this amount out of order relative to other received counters, it is likely to be /// rejected on the basis that the session can't remember if this counter was replayed. @@ -117,7 +118,7 @@ pub(crate) const HANDSHAKE_HELLO_MAX_SIZE: usize = HANDSHAKE_HELLO_MIN_SIZE + RA pub(crate) const HANDSHAKE_RESPONSE_SIZE: usize = P384_PUBLIC_KEY_SIZE + KYBER_CIPHERTEXT_SIZE + AES_GCM_TAG_SIZE + KID_SIZE + AES_GCM_TAG_SIZE; pub(crate) const HANDSHAKE_COMPLETION_MIN_SIZE: usize = P384_PUBLIC_KEY_SIZE + AES_GCM_TAG_SIZE + 0 + AES_GCM_TAG_SIZE; -pub(crate) const HANDSHAKE_COMPLETION_MAX_SIZE: usize = HANDSHAKE_COMPLETION_MIN_SIZE + MAX_IDENTITY_BLOB_SIZE; +pub(crate) const HANDSHAKE_COMPLETION_MAX_SIZE: usize = HANDSHAKE_COMPLETION_MIN_SIZE + IDENTITY_MAX_SIZE; pub(crate) const KEY_CONFIRMATION_SIZE: usize = AES_GCM_TAG_SIZE; pub(crate) const ACKNOWLEDGEMENT_SIZE: usize = AES_GCM_TAG_SIZE; @@ -131,4 +132,4 @@ pub(crate) const REKEY_SIZE: usize = P384_PUBLIC_KEY_SIZE + KID_SIZE + AES_GCM_T /// The combined size of both in bytes must be at most this value. /// /// If not ZSSP will return `OpenError::DataTooLarge` and refuse to create a session object. -pub const MAX_IDENTITY_BLOB_SIZE: usize = 4096; +pub const IDENTITY_MAX_SIZE: usize = 4096; From 1cbc455876210134d2a8a69b6fc4c6a135ce645d Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 7 Aug 2023 15:08:29 -0400 Subject: [PATCH 23/37] cargo fmt --- examples/basic_test.rs | 33 ++++- rustfmt.toml | 2 +- src/applicationlayer.rs | 5 +- src/challenge.rs | 5 +- src/context.rs | 239 ++++++++++++++++++++++------------- src/crypto/aes.rs | 7 +- src/crypto/kyber1024.rs | 5 +- src/crypto_impl/aes_impl.rs | 7 +- src/crypto_impl/kyber1024.rs | 5 +- src/crypto_impl/p384_impl.rs | 8 +- src/fragmentation.rs | 19 ++- src/proto.rs | 13 +- src/symmetric_state.rs | 23 +++- src/zeta.rs | 175 +++++++++++++++++++------ 14 files changed, 402 insertions(+), 144 deletions(-) diff --git a/examples/basic_test.rs b/examples/basic_test.rs index 650721b..8770adb 100644 --- a/examples/basic_test.rs +++ b/examples/basic_test.rs @@ -21,7 +21,9 @@ use rand_core::OsRng; use rand_core::RngCore; use sha2::Sha512; -use zssp_proto::applicationlayer::{AcceptAction, ApplicationLayer, RatchetState, RatchetUpdate, Settings, RATCHET_SIZE}; +use zssp_proto::applicationlayer::{ + AcceptAction, ApplicationLayer, RatchetState, RatchetUpdate, Settings, RATCHET_SIZE, +}; use zssp_proto::crypto_impl::PqcKyberSecretKey; use zssp_proto::Session; @@ -81,7 +83,10 @@ impl ApplicationLayer for &TestApplication { } } - fn restore_by_fingerprint(&self, ratchet_fingerprint: &[u8; RATCHET_SIZE]) -> Result, Self::StorageError> { + fn restore_by_fingerprint( + &self, + ratchet_fingerprint: &[u8; RATCHET_SIZE], + ) -> Result, Self::StorageError> { let ratchets = self.ratchets.lock().unwrap(); Ok(ratchets.rf_map.get(ratchet_fingerprint).cloned()) } @@ -156,7 +161,14 @@ fn alice_main( up = false; alice_session = Some( context - .open(alice_app, |b| alice_out.send(b).is_ok(), TEST_MTU, bob_pubkey.clone(), 0, Vec::new()) + .open( + alice_app, + |b| alice_out.send(b).is_ok(), + TEST_MTU, + bob_pubkey.clone(), + 0, + Vec::new(), + ) .unwrap(), ); println!("[alice] opening session"); @@ -225,7 +237,8 @@ fn alice_main( } if current_time >= next_service { - next_service = current_time + context.service(alice_app, |_| Some((|b| alice_out.send(b).is_ok(), TEST_MTU))); + next_service = + current_time + context.service(alice_app, |_| Some((|b| alice_out.send(b).is_ok(), TEST_MTU))); } } } @@ -345,7 +358,17 @@ fn core(time: u64, packet_success_rate: u32) { ) }); } - ts.spawn(move || bob_main(run, packet_success_rate, &bob_app, bob_out, bob_in, alice_out, bob_keypair)); + ts.spawn(move || { + bob_main( + run, + packet_success_rate, + &bob_app, + bob_out, + bob_in, + alice_out, + bob_keypair, + ) + }); thread::sleep(Duration::from_secs(time)); diff --git a/rustfmt.toml b/rustfmt.toml index 3a3929c..9c9fedd 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,4 +1,4 @@ -max_width = 150 +max_width = 120 edition = "2021" newline_style = "Unix" struct_lit_width = 60 diff --git a/src/applicationlayer.rs b/src/applicationlayer.rs index 9856b4b..53090d5 100644 --- a/src/applicationlayer.rs +++ b/src/applicationlayer.rs @@ -189,7 +189,10 @@ pub trait ApplicationLayer: Sized { /// If `RatchetAction::DowngradeRatchet` is returned we will attempt to convince Alice to downgrade /// to the empty ratchet key, restarting the ratchet chain. /// If `RatchetAction::FailAuthentication` is returned Alice's connection will be silently dropped. - fn restore_by_fingerprint(&self, ratchet_fingerprint: &[u8; RATCHET_SIZE]) -> Result, Self::StorageError>; + fn restore_by_fingerprint( + &self, + ratchet_fingerprint: &[u8; RATCHET_SIZE], + ) -> Result, Self::StorageError>; /// Lookup the specific ratchet states based on the identity of the peer being communicated with. /// This function will be called whenever Alice attempts to open a session, or Bob attempts /// to verify Alice's identity. diff --git a/src/challenge.rs b/src/challenge.rs index 1a4b68b..5249a7d 100644 --- a/src/challenge.rs +++ b/src/challenge.rs @@ -58,7 +58,10 @@ impl ChallengeContext { return Ok(false); } let c = u64::from_be_bytes(response[..COUNTER_SIZE].try_into().unwrap()); - if self.check_window(c) && secure_eq(&response[COUNTER_SIZE..POW_START], &self.create_mac::(c, addr)) && verify_pow::(response) { + if self.check_window(c) + && secure_eq(&response[COUNTER_SIZE..POW_START], &self.create_mac::(c, addr)) + && verify_pow::(response) + { self.update_window(c); Ok(true) } else { diff --git a/src/context.rs b/src/context.rs index 2974c51..ca4b4a9 100644 --- a/src/context.rs +++ b/src/context.rs @@ -149,43 +149,53 @@ impl Context { if let Some(Some(session)) = session { // Process recv fragmentation layer. let mut zeta = session.0.lock().unwrap(); - let result = zeta.defrag.received_fragment::(raw_fragment, app.time(), |n, frag_no, frag_count| { - let (p, c) = from_nonce(n); - if p != PACKET_TYPE_DATA { - log!(app, ReceivedRawFragment(p, c, frag_no, frag_count)); - } - if p == PACKET_TYPE_HANDSHAKE_RESPONSE { - if !matches!(&zeta.beta, ZetaAutomata::A1(_)) { - // A resent handshake response from Bob may have arrived out of order, - // after we already received one. - return Err(byzantine_fault!(OutOfSequence, false)); - } - if c >= COUNTER_WINDOW_MAX_SKIP_AHEAD { - return Err(byzantine_fault!(ExpiredCounter, true)); - } - Ok(()) - } else if PACKET_TYPE_USES_COUNTER_RANGE.contains(&p) { - if !zeta.check_counter_window(c) { - // The counter window has finite memory and so will occasionally give - // false positives on very out-of-order packets. - return Err(byzantine_fault!(ExpiredCounter, false)); - } - Ok(()) - } else if p == PACKET_TYPE_HANDSHAKE_COMPLETION { - // The handshake completion packet could have been resent. - return Err(byzantine_fault!(InvalidPacket, false)); - } else { - return Err(byzantine_fault!(InvalidPacket, true)); - } - })?; + let result = + zeta.defrag + .received_fragment::(raw_fragment, app.time(), |n, frag_no, frag_count| { + let (p, c) = from_nonce(n); + if p != PACKET_TYPE_DATA { + log!(app, ReceivedRawFragment(p, c, frag_no, frag_count)); + } + if p == PACKET_TYPE_HANDSHAKE_RESPONSE { + if !matches!(&zeta.beta, ZetaAutomata::A1(_)) { + // A resent handshake response from Bob may have arrived out of order, + // after we already received one. + return Err(byzantine_fault!(OutOfSequence, false)); + } + if c >= COUNTER_WINDOW_MAX_SKIP_AHEAD { + return Err(byzantine_fault!(ExpiredCounter, true)); + } + Ok(()) + } else if PACKET_TYPE_USES_COUNTER_RANGE.contains(&p) { + if !zeta.check_counter_window(c) { + // The counter window has finite memory and so will occasionally give + // false positives on very out-of-order packets. + return Err(byzantine_fault!(ExpiredCounter, false)); + } + Ok(()) + } else if p == PACKET_TYPE_HANDSHAKE_COMPLETION { + // The handshake completion packet could have been resent. + return Err(byzantine_fault!(InvalidPacket, false)); + } else { + return Err(byzantine_fault!(InvalidPacket, true)); + } + })?; if let Some((pn, mut assembled_packet)) = result { // Process recv zeta layer. - let send_associated = |Packet(kid, nonce, payload): &Packet, hk: Option<&[u8; AES_256_KEY_SIZE]>| { - if let Some((send_fragment, mut mtu)) = send_to(&session) { - mtu = mtu.max(MIN_TRANSPORT_MTU); - send_with_fragmentation::(send_fragment, mtu, *kid, to_packet_nonce(&nonce), payload, hk); - } - }; + let send_associated = + |Packet(kid, nonce, payload): &Packet, hk: Option<&[u8; AES_256_KEY_SIZE]>| { + if let Some((send_fragment, mut mtu)) = send_to(&session) { + mtu = mtu.max(MIN_TRANSPORT_MTU); + send_with_fragmentation::( + send_fragment, + mtu, + *kid, + to_packet_nonce(&nonce), + payload, + hk, + ); + } + }; let (p, _) = from_nonce(&pn); let ret = match p { @@ -210,8 +220,15 @@ impl Context { } PACKET_TYPE_KEY_CONFIRM => { log!(app, ReceivedRawKeyConfirm); - let result = - received_c1_trans(&mut zeta, &app, &ctx.rng, kid_recv, to_aes_nonce(&pn), assembled_packet, send_associated)?; + let result = received_c1_trans( + &mut zeta, + &app, + &ctx.rng, + kid_recv, + to_aes_nonce(&pn), + assembled_packet, + send_associated, + )?; log!(app, KeyConfirmIsAuthSentAck(&session)); if result { SessionEvent::Established @@ -221,7 +238,14 @@ impl Context { } PACKET_TYPE_ACK => { log!(app, ReceivedRawAck); - received_c2_trans(&mut zeta, &app, &ctx.rng, kid_recv, to_aes_nonce(&pn), assembled_packet)?; + received_c2_trans( + &mut zeta, + &app, + &ctx.rng, + kid_recv, + to_aes_nonce(&pn), + assembled_packet, + )?; log!(app, AckIsAuth(&session)); SessionEvent::Control } @@ -244,7 +268,14 @@ impl Context { } PACKET_TYPE_REKEY_COMPLETE => { log!(app, ReceivedRawK2); - received_k2_trans(&mut zeta, &app, kid_recv, to_aes_nonce(&pn), assembled_packet, send_associated)?; + received_k2_trans( + &mut zeta, + &app, + kid_recv, + to_aes_nonce(&pn), + assembled_packet, + send_associated, + )?; log!(app, K2IsAuthSentKeyConfirm(&session)); SessionEvent::Control } @@ -266,28 +297,37 @@ impl Context { if let Entry::Occupied(mut entry) = b2_map.entry(kid_recv) { let zeta = entry.get_mut(); // Process recv fragmentation layer. - let result = zeta.defrag.received_fragment::(raw_fragment, app.time(), |n, frag_no, frag_count| { - let (p, c) = from_nonce(n); - log!(app, ReceivedRawFragment(p, c, frag_no, frag_count)); - if p == PACKET_TYPE_HANDSHAKE_COMPLETION && c == 0 { - Ok(()) - } else { - Err(byzantine_fault!(InvalidPacket, true)) - } - })?; + let result = + zeta.defrag + .received_fragment::(raw_fragment, app.time(), |n, frag_no, frag_count| { + let (p, c) = from_nonce(n); + log!(app, ReceivedRawFragment(p, c, frag_no, frag_count)); + if p == PACKET_TYPE_HANDSHAKE_COMPLETION && c == 0 { + Ok(()) + } else { + Err(byzantine_fault!(InvalidPacket, true)) + } + })?; if let Some((_, assembled_packet)) = result { log!(app, ReceivedRawX3); let zeta = entry.remove(); - let session = received_x3_trans(zeta, &app, ctx, kid_recv, assembled_packet, |Packet(kid, nonce, payload), hk| { - send_with_fragmentation::( - send_unassociated_reply, - send_unassociated_mtu, - *kid, - to_packet_nonce(&nonce), - payload, - hk, - ); - })?; + let session = received_x3_trans( + zeta, + &app, + ctx, + kid_recv, + assembled_packet, + |Packet(kid, nonce, payload), hk| { + send_with_fragmentation::( + send_unassociated_reply, + send_unassociated_mtu, + *kid, + to_packet_nonce(&nonce), + payload, + hk, + ); + }, + )?; log!(app, X3IsAuthSentKeyConfirm(&session)); Ok(ReceiveOk::Session(session, SessionEvent::NewSession)) } else { @@ -301,11 +341,10 @@ impl Context { } } else { // Process recv fragmentation layer. - let result = ctx - .hello_defrag - .lock() - .unwrap() - .received_fragment::(raw_fragment, app.time(), |n, frag_no, frag_count| { + let result = ctx.hello_defrag.lock().unwrap().received_fragment::( + raw_fragment, + app.time(), + |n, frag_no, frag_count| { let (p, c) = from_nonce(n); log!(app, ReceivedRawFragment(p, c, frag_no, frag_count)); if p == PACKET_TYPE_HANDSHAKE_HELLO || p == PACKET_TYPE_CHALLENGE { @@ -313,18 +352,18 @@ impl Context { } else { Err(byzantine_fault!(InvalidPacket, true)) } - })?; + }, + )?; if let Some((n, mut assembled_packet)) = result { let (p, _) = from_nonce(&n); if p == PACKET_TYPE_HANDSHAKE_HELLO { log!(app, ReceivedRawX1); // Process recv challenge layer. let challenge_start = assembled_packet.len() - CHALLENGE_SIZE; - let result = ctx - .challenge - .lock() - .unwrap() - .process_hello::(remote_address, (&assembled_packet[challenge_start..]).try_into().unwrap()); + let result = ctx.challenge.lock().unwrap().process_hello::( + remote_address, + (&assembled_packet[challenge_start..]).try_into().unwrap(), + ); if let Err(challenge) = result { log!(app, X1FailedChallengeSentNewChallenge); let mut challenge_packet = Vec::new(); @@ -347,16 +386,22 @@ impl Context { assembled_packet.truncate(challenge_start); // Process recv zeta layer. - received_x1_trans(&app, &ctx, to_aes_nonce(&n), assembled_packet, |Packet(kid, nonce, payload), hk| { - send_with_fragmentation::( - send_unassociated_reply, - send_unassociated_mtu, - *kid, - to_packet_nonce(&nonce), - payload, - Some(hk), - ); - })?; + received_x1_trans( + &app, + &ctx, + to_aes_nonce(&n), + assembled_packet, + |Packet(kid, nonce, payload), hk| { + send_with_fragmentation::( + send_unassociated_reply, + send_unassociated_mtu, + *kid, + to_packet_nonce(&nonce), + payload, + Some(hk), + ); + }, + )?; log!(app, X1IsAuthSentX2); Ok(ReceiveOk::Unassociated) } else if p == PACKET_TYPE_CHALLENGE { @@ -365,10 +410,17 @@ impl Context { if assembled_packet.len() != KID_SIZE + CHALLENGE_SIZE { return Err(byzantine_fault!(InvalidPacket, true)); } - if let Some(kid_recv) = NonZeroU32::new(u32::from_be_bytes(assembled_packet[..KID_SIZE].try_into().unwrap())) { - if let Some(Some(session)) = ctx.session_map.lock().unwrap().get(&kid_recv).map(|r| r.upgrade()) { + if let Some(kid_recv) = + NonZeroU32::new(u32::from_be_bytes(assembled_packet[..KID_SIZE].try_into().unwrap())) + { + if let Some(Some(session)) = ctx.session_map.lock().unwrap().get(&kid_recv).map(|r| r.upgrade()) + { let mut zeta = session.0.lock().unwrap(); - respond_to_challenge(&mut zeta, &ctx.rng, &assembled_packet[KID_SIZE..].try_into().unwrap()); + respond_to_challenge( + &mut zeta, + &ctx.rng, + &assembled_packet[KID_SIZE..].try_into().unwrap(), + ); log!(app, ChallengeIsAuth(&session)); return Ok(ReceiveOk::Unassociated); } @@ -390,7 +442,13 @@ impl Context { /// slice of `data` /// * `mtu` - The MTU of the link, all packets passed to `send` will be at most `mtu` in length /// * `payload` - Data to send - pub fn send(&self, session: &Arc>, send: impl FnMut(Vec) -> bool, mut mtu: usize, payload: Vec) -> Result<(), SendError> { + pub fn send( + &self, + session: &Arc>, + send: impl FnMut(Vec) -> bool, + mut mtu: usize, + payload: Vec, + ) -> Result<(), SendError> { mtu = mtu.max(MIN_TRANSPORT_MTU); let mut zeta = session.0.lock().unwrap(); send_payload(&mut zeta, payload, |Packet(kid, nonce, payload), hk| { @@ -405,7 +463,11 @@ impl Context { /// a problem. /// /// * `send_to` - Function to get a sender and an MTU to send something over an active session - pub fn service) -> bool>(&self, app: App, mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>) -> i64 { + pub fn service) -> bool>( + &self, + app: App, + mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, + ) -> i64 { let ctx = &self.0; let sessions = ctx.sessions.lock().unwrap(); let current_time = app.time(); @@ -422,7 +484,14 @@ impl Context { |Packet(kid, nonce, payload): &Packet, hk| { if let Some((send_fragment, mut mtu)) = send_to(&session) { mtu = mtu.max(MIN_TRANSPORT_MTU); - send_with_fragmentation::(send_fragment, mtu, *kid, to_packet_nonce(&nonce), payload, hk); + send_with_fragmentation::( + send_fragment, + mtu, + *kid, + to_packet_nonce(&nonce), + payload, + hk, + ); } }, ); diff --git a/src/crypto/aes.rs b/src/crypto/aes.rs index d5f1d56..9a16d87 100644 --- a/src/crypto/aes.rs +++ b/src/crypto/aes.rs @@ -27,7 +27,12 @@ pub trait PrpAes256 { pub trait AeadAesGcm { /// Encrypt the given `buffer` of plaintext using AES-GCM-256, with the given `key`, `iv` and `aad`. /// The ciphertext should be written directly back to `buffer`, and the GCM tag should be returned. - fn encrypt_in_place(key: &[u8; AES_256_KEY_SIZE], iv: [u8; AES_GCM_IV_SIZE], aad: Option<&[u8]>, buffer: &mut [u8]) -> [u8; AES_GCM_TAG_SIZE]; + fn encrypt_in_place( + key: &[u8; AES_256_KEY_SIZE], + iv: [u8; AES_GCM_IV_SIZE], + aad: Option<&[u8]>, + buffer: &mut [u8], + ) -> [u8; AES_GCM_TAG_SIZE]; /// Decrypt the given `buffer` of ciphertext using AES-GCM-256, with the given `key`, `iv` and `aad`. /// The ciphertext should be written directly back to `buffer`, and the GCM tag should be returned. fn decrypt_in_place( diff --git a/src/crypto/kyber1024.rs b/src/crypto/kyber1024.rs index 185ab6b..9c5b5c5 100644 --- a/src/crypto/kyber1024.rs +++ b/src/crypto/kyber1024.rs @@ -24,7 +24,10 @@ pub trait PrivateKeyKyber1024: Sized + Send + Sync { /// /// **CRITICAL**: This must return `None` if the given `public_key` is invalid in any way /// according to the Kyber1024 spec. - fn encapsulate(rng: &mut Rng, public_key: &[u8; KYBER_PUBLIC_KEY_SIZE]) -> Option<([u8; KYBER_CIPHERTEXT_SIZE], [u8; KYBER_PLAINTEXT_SIZE])>; + fn encapsulate( + rng: &mut Rng, + public_key: &[u8; KYBER_PUBLIC_KEY_SIZE], + ) -> Option<([u8; KYBER_CIPHERTEXT_SIZE], [u8; KYBER_PLAINTEXT_SIZE])>; /// Decapsulate a Kyber1024 `ciphertext` received from the remote peer, retreiving /// the raw bytes of the original plaintext. This plaintext is immediately hashed and deleted. /// diff --git a/src/crypto_impl/aes_impl.rs b/src/crypto_impl/aes_impl.rs index 9a2edab..c19b26d 100644 --- a/src/crypto_impl/aes_impl.rs +++ b/src/crypto_impl/aes_impl.rs @@ -18,7 +18,12 @@ impl PrpAes256 for Aes256 { } impl AeadAesGcm for Aes256Gcm { - fn encrypt_in_place(key: &[u8; AES_256_KEY_SIZE], iv: [u8; AES_GCM_IV_SIZE], aad: Option<&[u8]>, buffer: &mut [u8]) -> [u8; AES_GCM_TAG_SIZE] { + fn encrypt_in_place( + key: &[u8; AES_256_KEY_SIZE], + iv: [u8; AES_GCM_IV_SIZE], + aad: Option<&[u8]>, + buffer: &mut [u8], + ) -> [u8; AES_GCM_TAG_SIZE] { let key = Key::::from_slice(key); let mut cipher = Aes256Gcm::new(&key); cipher diff --git a/src/crypto_impl/kyber1024.rs b/src/crypto_impl/kyber1024.rs index 4c5ed15..9f56dc3 100644 --- a/src/crypto_impl/kyber1024.rs +++ b/src/crypto_impl/kyber1024.rs @@ -13,7 +13,10 @@ impl PrivateKeyKyber1024 for PqcKyberSecretKey { (Zeroizing::new(keypair.secret), keypair.public) } - fn encapsulate(rng: &mut Rng, public_key: &[u8; KYBER_PUBLIC_KEY_SIZE]) -> Option<([u8; KYBER_CIPHERTEXT_SIZE], [u8; KYBER_PLAINTEXT_SIZE])> { + fn encapsulate( + rng: &mut Rng, + public_key: &[u8; KYBER_PUBLIC_KEY_SIZE], + ) -> Option<([u8; KYBER_CIPHERTEXT_SIZE], [u8; KYBER_PLAINTEXT_SIZE])> { pqc_kyber::encapsulate(public_key, rng).ok() } diff --git a/src/crypto_impl/p384_impl.rs b/src/crypto_impl/p384_impl.rs index ddf6e68..d20705a 100644 --- a/src/crypto_impl/p384_impl.rs +++ b/src/crypto_impl/p384_impl.rs @@ -26,6 +26,12 @@ impl KeyPairP384 for EphemeralSecret { } fn agree(&self, public_key: &Self::PublicKey) -> Option<[u8; P384_ECDH_SHARED_SECRET_SIZE]> { - Some(self.diffie_hellman(public_key).raw_secret_bytes().as_slice().try_into().unwrap()) + Some( + self.diffie_hellman(public_key) + .raw_secret_bytes() + .as_slice() + .try_into() + .unwrap(), + ) } } diff --git a/src/fragmentation.rs b/src/fragmentation.rs index a0ceda1..902f836 100644 --- a/src/fragmentation.rs +++ b/src/fragmentation.rs @@ -10,7 +10,12 @@ use crate::proto::*; use crate::result::{byzantine_fault, ReceiveError}; /// Corresponds to Figure 13 found in Section 6. -fn create_fragment_header(kid_send: u32, fragment_count: usize, fragment_no: usize, n: &[u8; PACKET_NONCE_SIZE]) -> [u8; HEADER_SIZE] { +fn create_fragment_header( + kid_send: u32, + fragment_count: usize, + fragment_no: usize, + n: &[u8; PACKET_NONCE_SIZE], +) -> [u8; HEADER_SIZE] { debug_assert!(fragment_count > 0); debug_assert!(fragment_count <= MAX_FRAGMENTS); debug_assert!(fragment_no < MAX_FRAGMENTS); @@ -47,7 +52,10 @@ pub fn send_with_fragmentation( fragment.extend(&packet[i..j]); if let Some(hk_send) = hk_send { - App::Prp::encrypt_in_place(hk_send, (&mut fragment[HEADER_AUTH_START..HEADER_AUTH_END]).try_into().unwrap()); + App::Prp::encrypt_in_place( + hk_send, + (&mut fragment[HEADER_AUTH_START..HEADER_AUTH_END]).try_into().unwrap(), + ); } if !send(fragment) { return false; @@ -87,7 +95,12 @@ impl DefragBuffer { } if let Some(hk_recv) = self.hk_recv.as_ref() { - App::Prp::decrypt_in_place(hk_recv, (&mut raw_fragment[HEADER_AUTH_START..HEADER_AUTH_END]).try_into().unwrap()) + App::Prp::decrypt_in_place( + hk_recv, + (&mut raw_fragment[HEADER_AUTH_START..HEADER_AUTH_END]) + .try_into() + .unwrap(), + ) } let fragment_no = raw_fragment[FRAGMENT_NO_IDX] as usize; diff --git a/src/proto.rs b/src/proto.rs index d0ee289..68594bb 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -1,4 +1,6 @@ -use crate::crypto::{AES_GCM_TAG_SIZE, KYBER_CIPHERTEXT_SIZE, KYBER_PUBLIC_KEY_SIZE, P384_PUBLIC_KEY_SIZE, SHA512_HASH_SIZE}; +use crate::crypto::{ + AES_GCM_TAG_SIZE, KYBER_CIPHERTEXT_SIZE, KYBER_PUBLIC_KEY_SIZE, P384_PUBLIC_KEY_SIZE, SHA512_HASH_SIZE, +}; /* Common constants */ @@ -71,7 +73,8 @@ pub(crate) const HASHLEN: usize = SHA512_HASH_SIZE; pub const RATCHET_SIZE: usize = 32; /// Initial value of 'h'. -pub(crate) const PROTOCOL_NAME_NOISE_XK: &[u8; HASHLEN] = b"Noise_XKhfs+psk2_P384+Kyber1024_AESGCM_SHA512\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; +pub(crate) const PROTOCOL_NAME_NOISE_XK: &[u8; HASHLEN] = + b"Noise_XKhfs+psk2_P384+Kyber1024_AESGCM_SHA512\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; /// Initial value of 'ck' for rekeying. pub(crate) const PROTOCOL_NAME_NOISE_KK: &[u8; HASHLEN] = b"Noise_KKpsk0_P384_AESGCM_SHA512\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; @@ -112,10 +115,12 @@ pub(crate) const PACKET_TYPE_DATA: u8 = 8; pub(crate) const PACKET_TYPE_CHALLENGE: u8 = 9; pub(crate) const PACKET_TYPE_USES_COUNTER_RANGE: std::ops::Range = 3..9; -pub(crate) const HANDSHAKE_HELLO_MIN_SIZE: usize = KID_SIZE + P384_PUBLIC_KEY_SIZE + KYBER_PUBLIC_KEY_SIZE + AES_GCM_TAG_SIZE + AES_GCM_TAG_SIZE; +pub(crate) const HANDSHAKE_HELLO_MIN_SIZE: usize = + KID_SIZE + P384_PUBLIC_KEY_SIZE + KYBER_PUBLIC_KEY_SIZE + AES_GCM_TAG_SIZE + AES_GCM_TAG_SIZE; pub(crate) const HANDSHAKE_HELLO_MAX_SIZE: usize = HANDSHAKE_HELLO_MIN_SIZE + RATCHET_SIZE; -pub(crate) const HANDSHAKE_RESPONSE_SIZE: usize = P384_PUBLIC_KEY_SIZE + KYBER_CIPHERTEXT_SIZE + AES_GCM_TAG_SIZE + KID_SIZE + AES_GCM_TAG_SIZE; +pub(crate) const HANDSHAKE_RESPONSE_SIZE: usize = + P384_PUBLIC_KEY_SIZE + KYBER_CIPHERTEXT_SIZE + AES_GCM_TAG_SIZE + KID_SIZE + AES_GCM_TAG_SIZE; pub(crate) const HANDSHAKE_COMPLETION_MIN_SIZE: usize = P384_PUBLIC_KEY_SIZE + AES_GCM_TAG_SIZE + 0 + AES_GCM_TAG_SIZE; pub(crate) const HANDSHAKE_COMPLETION_MAX_SIZE: usize = HANDSHAKE_COMPLETION_MIN_SIZE + IDENTITY_MAX_SIZE; diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index 702198a..00b77cc 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -84,7 +84,14 @@ impl SymmetricState { let mut next_ck = [0u8; HASHLEN]; let mut temp_k = [0u8; HASHLEN]; - self.kbkdf(input_key_material, LABEL_KBKDF_CHAIN, 2, &mut next_ck, Some(&mut temp_k), None); + self.kbkdf( + input_key_material, + LABEL_KBKDF_CHAIN, + 2, + &mut next_ck, + Some(&mut temp_k), + None, + ); *self.ck = next_ck; self.k.clone_from_slice(&temp_k[..AES_256_KEY_SIZE]); @@ -116,14 +123,24 @@ impl SymmetricState { self.k.clone_from_slice(&temp_k[..AES_256_KEY_SIZE]); } /// Corresponds to Noise `EncryptAndHash`. - pub fn encrypt_and_hash_in_place(&mut self, iv: [u8; AES_GCM_IV_SIZE], plaintext_start: usize, buffer: &mut Vec) { + pub fn encrypt_and_hash_in_place( + &mut self, + iv: [u8; AES_GCM_IV_SIZE], + plaintext_start: usize, + buffer: &mut Vec, + ) { let tag = App::Aead::encrypt_in_place(&self.k, iv, Some(&self.h), &mut buffer[plaintext_start..]); buffer.extend(&tag); self.mix_hash(&buffer[plaintext_start..]); } /// Corresponds to Noise `DecryptAndHash`. #[must_use] - pub fn decrypt_and_hash_in_place(&mut self, iv: [u8; AES_GCM_IV_SIZE], buffer: &mut [u8], tag: [u8; AES_GCM_TAG_SIZE]) -> bool { + pub fn decrypt_and_hash_in_place( + &mut self, + iv: [u8; AES_GCM_IV_SIZE], + buffer: &mut [u8], + tag: [u8; AES_GCM_TAG_SIZE], + ) -> bool { let mut hash = App::Hash::new(); hash.update(&self.h); hash.update(buffer); diff --git a/src/zeta.rs b/src/zeta.rs index 7113133..e51f2f1 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -276,7 +276,15 @@ pub(crate) fn trans_to_a1( let mut session_map = ctx.session_map.lock().unwrap(); let kid_recv = gen_kid(session_map.deref(), ctx.rng.lock().unwrap().deref_mut()); - let a1 = create_a1_state(&ctx.rng, &s_remote, kid_recv, &ratchet_state1, ratchet_state2.as_ref(), identity).ok_or(OpenError::InvalidPublicKey)?; + let a1 = create_a1_state( + &ctx.rng, + &s_remote, + kid_recv, + &ratchet_state1, + ratchet_state2.as_ref(), + identity, + ) + .ok_or(OpenError::InvalidPublicKey)?; let packet = a1.packet.clone(); let (hk_recv, hk_send) = a1.noise.get_ask(LABEL_HEADER_KEY); @@ -304,14 +312,21 @@ pub(crate) fn trans_to_a1( let session = Arc::new(Session(Mutex::new(zeta))); session_map.insert(kid_recv, Arc::downgrade(&session)); - ctx.sessions.lock().unwrap().insert(Arc::as_ptr(&session), Arc::downgrade(&session)); + ctx.sessions + .lock() + .unwrap() + .insert(Arc::as_ptr(&session), Arc::downgrade(&session)); send(&packet); Ok(session) } /// Corresponds to Algorithm 13 found in Section 5. -pub(crate) fn respond_to_challenge(zeta: &mut Zeta, rng: &Mutex, challenge: &[u8; CHALLENGE_SIZE]) { +pub(crate) fn respond_to_challenge( + zeta: &mut Zeta, + rng: &Mutex, + challenge: &[u8; CHALLENGE_SIZE], +) { if let ZetaAutomata::A1(StateA1 { packet: Packet(_, _, x1), .. }) = &mut zeta.beta { let response_start = x1.len() - CHALLENGE_SIZE; respond_to_challenge_in_place::( @@ -345,13 +360,16 @@ pub(crate) fn received_x1_trans( // Noise process prologue. let j = i + KID_SIZE; noise.mix_hash(&x1[i..j]); - let kid_send = NonZeroU32::new(u32::from_be_bytes(x1[i..j].try_into().unwrap())).ok_or(byzantine_fault!(InvalidPacket, true))?; + let kid_send = NonZeroU32::new(u32::from_be_bytes(x1[i..j].try_into().unwrap())) + .ok_or(byzantine_fault!(InvalidPacket, true))?; noise.mix_hash(&ctx.s_secret.public_key_bytes()); i = j; // Process message pattern 1 e token. let e_remote = noise.read_e(&mut i, &x1).ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 1 es token. - noise.mix_dh(&ctx.s_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise + .mix_dh(&ctx.s_secret, &e_remote) + .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 1 e1 token. let j = i + KYBER_PUBLIC_KEY_SIZE; let k = j + AES_GCM_TAG_SIZE; @@ -396,12 +414,17 @@ pub(crate) fn received_x1_trans( // Process message pattern 2 e token. let e_secret = noise.write_e(&ctx.rng, &mut x2); // Process message pattern 2 ee token. - noise.mix_dh(&e_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise + .mix_dh(&e_secret, &e_remote) + .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 ekem1 token. let i = x2.len(); - let (ekem1, ekem1_secret) = App::Kem::encapsulate(ctx.rng.lock().unwrap().deref_mut(), (&x1[e1_start..e1_end]).try_into().unwrap()) - .map(|(ct, secret)| (ct, Zeroizing::new(secret))) - .ok_or(byzantine_fault!(FailedAuth, true))?; + let (ekem1, ekem1_secret) = App::Kem::encapsulate( + ctx.rng.lock().unwrap().deref_mut(), + (&x1[e1_start..e1_end]).try_into().unwrap(), + ) + .map(|(ct, secret)| (ct, Zeroizing::new(secret))) + .ok_or(byzantine_fault!(FailedAuth, true))?; x2.extend(ekem1); noise.encrypt_and_hash_in_place(to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), i, &mut x2); noise.mix_key(ekem1_secret.as_ref()); @@ -436,7 +459,10 @@ pub(crate) fn received_x1_trans( }, ); - send(&Packet(kid_send.get(), to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, c), x2), &hk_send); + send( + &Packet(kid_send.get(), to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, c), x2), + &hk_send, + ); Ok(()) } /// Corresponds to Transition Algorithm 3 found in Section 4.3. @@ -470,7 +496,9 @@ pub(crate) fn received_x2_trans( // Process message pattern 2 e token. let e_remote = noise.read_e(&mut i, &x2).ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 ee token. - noise.mix_dh(e_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise + .mix_dh(e_secret, &e_remote) + .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 ekem1 token. let j = i + KYBER_CIPHERTEXT_SIZE; let k = j + AES_GCM_TAG_SIZE; @@ -536,7 +564,9 @@ pub(crate) fn received_x2_trans( x3.extend(&ctx.s_secret.public_key_bytes()); noise.encrypt_and_hash_in_place(to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 1), i, &mut x3); // Process message pattern 3 se token. - noise.mix_dh(&ctx.s_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise + .mix_dh(&ctx.s_secret, &e_remote) + .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 3 payload. let i = x3.len(); x3.extend(identity); @@ -623,10 +653,13 @@ pub(crate) fn received_x3_trans( if !noise.decrypt_and_hash_in_place(to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 1), &mut x3[i..j], tag) { return Err(byzantine_fault!(FailedAuth, true)); } - let s_remote = App::PublicKey::from_bytes((&x3[i..j]).try_into().unwrap()).ok_or(byzantine_fault!(FailedAuth, true))?; + let s_remote = + App::PublicKey::from_bytes((&x3[i..j]).try_into().unwrap()).ok_or(byzantine_fault!(FailedAuth, true))?; i = k; // Process message pattern 3 se token. - noise.mix_dh(&zeta.e_secret, &s_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise + .mix_dh(&zeta.e_secret, &s_remote) + .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 3 payload. let k = x3.len(); let j = k - AES_GCM_TAG_SIZE; @@ -726,7 +759,10 @@ pub(crate) fn received_x3_trans( defrag: zeta.defrag, }))); entry.insert(Arc::downgrade(&session)); - ctx.sessions.lock().unwrap().insert(Arc::as_ptr(&session), Arc::downgrade(&session)); + ctx.sessions + .lock() + .unwrap() + .insert(Arc::as_ptr(&session), Arc::downgrade(&session)); send(&Packet(zeta.kid_send.get(), n, c1), Some(&zeta.hk_send)); Ok(session) @@ -765,7 +801,12 @@ pub(crate) fn received_c1_trans( return Err(byzantine_fault!(OutOfSequence, false)); }; - let specified_key = zeta.key_ref(is_other).recv.kek.as_ref().ok_or(byzantine_fault!(OutOfSequence, true))?; + let specified_key = zeta + .key_ref(is_other) + .recv + .kek + .as_ref() + .ok_or(byzantine_fault!(OutOfSequence, true))?; let tag = c1[..].try_into().unwrap(); if !App::Aead::decrypt_in_place(specified_key, n, None, &mut [], tag) { return Err(byzantine_fault!(FailedAuth, true)); @@ -800,7 +841,8 @@ pub(crate) fn received_c1_trans( zeta.timeout_timer = app.time() + App::SETTINGS .rekey_after_time - .saturating_sub(rng.lock().unwrap().next_u64() % App::SETTINGS.rekey_time_max_jitter) as i64; + .saturating_sub(rng.lock().unwrap().next_u64() % App::SETTINGS.rekey_time_max_jitter) + as i64; zeta.resend_timer = i64::MAX; zeta.beta = ZetaAutomata::S2; } @@ -810,11 +852,19 @@ pub(crate) fn received_c1_trans( let c = zeta.send_counter; zeta.send_counter += 1; let n = to_nonce(PACKET_TYPE_ACK, c); - let latest_confirmed_key = zeta.key_ref(false).send.kek.as_ref().ok_or(byzantine_fault!(OutOfSequence, true))?; + let latest_confirmed_key = zeta + .key_ref(false) + .send + .kek + .as_ref() + .ok_or(byzantine_fault!(OutOfSequence, true))?; let tag = App::Aead::encrypt_in_place(latest_confirmed_key, n, None, &mut []); c2.extend(&tag); - send(&Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, c2), Some(&zeta.hk_send)); + send( + &Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, c2), + Some(&zeta.hk_send), + ); Ok(just_establised) } /// Corresponds to the trivial Transition Algorithm described for processing C_2 packets found in @@ -929,7 +979,12 @@ pub(crate) fn service( let c = zeta.send_counter; zeta.send_counter += 1; let n = to_nonce(p, c); - let tag = App::Aead::encrypt_in_place(zeta.key_ref(false).send.kek.as_ref().unwrap(), n, None, &mut control_payload); + let tag = App::Aead::encrypt_in_place( + zeta.key_ref(false).send.kek.as_ref().unwrap(), + n, + None, + &mut control_payload, + ); control_payload.extend(&tag); send( &Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, control_payload), @@ -937,7 +992,12 @@ pub(crate) fn service( ); } } -fn remap(session: &Arc>, zeta: &Zeta, rng: &Mutex, session_map: &SessionMap) -> NonZeroU32 { +fn remap( + session: &Arc>, + zeta: &Zeta, + rng: &Mutex, + session_map: &SessionMap, +) -> NonZeroU32 { let mut session_map = session_map.lock().unwrap(); let weak = if let Some(Some(weak)) = zeta.key_ref(true).recv.kid.as_ref().map(|kid| session_map.remove(kid)) { weak @@ -1034,7 +1094,10 @@ fn timeout_trans( let tag = App::Aead::encrypt_in_place(zeta.key_ref(false).send.kek.as_ref().unwrap(), n, None, &mut k1); k1.extend(&tag); - send(&Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, k1), Some(&zeta.hk_send)); + send( + &Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, k1), + Some(&zeta.hk_send), + ); } ZetaAutomata::S1 { .. } => { log!(app, TimeoutKeyConfirm(session)); @@ -1088,7 +1151,13 @@ pub(crate) fn received_k1_trans( let i = k1.len() - AES_GCM_TAG_SIZE; let tag = k1[i..].try_into().unwrap(); - if !App::Aead::decrypt_in_place(zeta.key_ref(false).recv.kek.as_ref().unwrap(), n, None, &mut k1[..i], tag) { + if !App::Aead::decrypt_in_place( + zeta.key_ref(false).recv.kek.as_ref().unwrap(), + n, + None, + &mut k1[..i], + tag, + ) { return Err(byzantine_fault!(FailedAuth, true)); } let (_, c) = from_nonce(&n); @@ -1108,9 +1177,13 @@ pub(crate) fn received_k1_trans( // Process message pattern 1 e token. let e_remote = noise.read_e(&mut i, &k1).ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 1 es token. - noise.mix_dh(s_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise + .mix_dh(s_secret, &e_remote) + .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 1 ss token. - noise.mix_dh(s_secret, &zeta.s_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise + .mix_dh(s_secret, &zeta.s_remote) + .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 1 payload. let j = i + KID_SIZE; let k = j + AES_GCM_TAG_SIZE; @@ -1118,15 +1191,20 @@ pub(crate) fn received_k1_trans( if !noise.decrypt_and_hash_in_place(to_nonce(PACKET_TYPE_REKEY_INIT, 0), &mut k1[i..j], tag) { return Err(byzantine_fault!(FailedAuth, true)); } - let kid_send = NonZeroU32::new(u32::from_be_bytes(k1[i..j].try_into().unwrap())).ok_or(byzantine_fault!(FailedAuth, true))?; + let kid_send = NonZeroU32::new(u32::from_be_bytes(k1[i..j].try_into().unwrap())) + .ok_or(byzantine_fault!(FailedAuth, true))?; let mut k2 = Vec::new(); // Process message pattern 2 e token. let e_secret = noise.write_e(rng, &mut k2); // Process message pattern 2 ee token. - noise.mix_dh(&e_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise + .mix_dh(&e_secret, &e_remote) + .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 se token. - noise.mix_dh(&s_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise + .mix_dh(&s_secret, &e_remote) + .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 payload. let i = k2.len(); let new_kid_recv = remap(session, &zeta, rng, session_map); @@ -1172,7 +1250,10 @@ pub(crate) fn received_k1_trans( let tag = App::Aead::encrypt_in_place(zeta.key_ref(false).send.kek.as_ref().unwrap(), n, None, &mut k2); k2.extend(&tag); - send(&Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, k2), Some(&zeta.hk_send)); + send( + &Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, k2), + Some(&zeta.hk_send), + ); Ok(()) })(); if matches!(result, Err(ReceiveError::ByzantineFault { .. })) { @@ -1205,7 +1286,13 @@ pub(crate) fn received_k2_trans( let i = k2.len() - AES_GCM_TAG_SIZE; let tag = k2[i..].try_into().unwrap(); - if !App::Aead::decrypt_in_place(zeta.key_ref(false).recv.kek.as_ref().unwrap(), n, None, &mut k2[..i], tag) { + if !App::Aead::decrypt_in_place( + zeta.key_ref(false).recv.kek.as_ref().unwrap(), + n, + None, + &mut k2[..i], + tag, + ) { return Err(byzantine_fault!(FailedAuth, true)); } let (_, c) = from_nonce(&n); @@ -1220,9 +1307,13 @@ pub(crate) fn received_k2_trans( // Process message pattern 2 e token. let e_remote = noise.read_e(&mut i, &k2).ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 ee token. - noise.mix_dh(e_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise + .mix_dh(e_secret, &e_remote) + .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 se token. - noise.mix_dh(e_secret, &zeta.s_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise + .mix_dh(e_secret, &zeta.s_remote) + .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 payload. let j = i + KID_SIZE; let k = j + AES_GCM_TAG_SIZE; @@ -1230,7 +1321,8 @@ pub(crate) fn received_k2_trans( if !noise.decrypt_and_hash_in_place(to_nonce(PACKET_TYPE_REKEY_COMPLETE, 0), &mut k2[i..j], tag) { return Err(byzantine_fault!(FailedAuth, true)); } - let kid_send = NonZeroU32::new(u32::from_be_bytes(k2[i..j].try_into().unwrap())).ok_or(byzantine_fault!(InvalidPacket, true))?; + let kid_send = NonZeroU32::new(u32::from_be_bytes(k2[i..j].try_into().unwrap())) + .ok_or(byzantine_fault!(InvalidPacket, true))?; let (rk, rf) = noise.get_ask(LABEL_RATCHET_STATE); let new_ratchet_state = RatchetState::new(rk, rf, zeta.ratchet_state1.chain_len + 1); @@ -1271,7 +1363,10 @@ pub(crate) fn received_k2_trans( let tag = App::Aead::encrypt_in_place(zeta.key_ref(false).send.kek.as_ref().unwrap(), n, None, &mut []); c1.extend(&tag); - send(&Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, c1), Some(&zeta.hk_send)); + send( + &Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, c1), + Some(&zeta.hk_send), + ); Ok(()) } else { unreachable!() @@ -1314,7 +1409,10 @@ pub(crate) fn send_payload( let tag = App::Aead::encrypt_in_place(zeta.key_ref(false).send.nk.as_ref().unwrap(), n, None, &mut payload); payload.extend(&tag); - send(&Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, payload), Some(&zeta.hk_send)); + send( + &Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, payload), + Some(&zeta.hk_send), + ); Ok(()) } /// Corresponds to Algorithm 10 found in Section 4.3. @@ -1340,7 +1438,12 @@ pub(crate) fn received_payload_in_place( }; let i = payload.len() - AES_GCM_TAG_SIZE; - let specified_key = zeta.key_ref(is_other).recv.nk.as_ref().ok_or(byzantine_fault!(OutOfSequence, true))?; + let specified_key = zeta + .key_ref(is_other) + .recv + .nk + .as_ref() + .ok_or(byzantine_fault!(OutOfSequence, true))?; let tag = payload[i..].try_into().unwrap(); if !App::Aead::decrypt_in_place(specified_key, n, None, &mut payload[..i], tag) { return Err(byzantine_fault!(FailedAuth, true)); From 0416cb9a0dd5311d16103fe9c76a784365630975 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 7 Aug 2023 15:33:27 -0400 Subject: [PATCH 24/37] updated docs --- src/applicationlayer.rs | 58 +++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/src/applicationlayer.rs b/src/applicationlayer.rs index 53090d5..31c4e12 100644 --- a/src/applicationlayer.rs +++ b/src/applicationlayer.rs @@ -173,22 +173,17 @@ pub trait ApplicationLayer: Sized { /// Function to accept sessions after final negotiation. /// The second argument is the identity that the remote peer sent us. The application /// must verify this identity is associated with the remote peer's static key. - /// To prevent desync, if this function returns (Some(_), _), no other open session with the - /// same remote peer must exist. Drop or call expire on any pre-existing sessions before returning. + /// To prevent desync, if this function specifies that we should connect, no other open session + /// with the same remote peer must exist. Drop or call expire on any pre-existing sessions + /// before returning. fn check_accept_session(&self, remote_static_key: &Self::PublicKey, identity: &[u8]) -> AcceptAction; /// Lookup a specific ratchet state based on its ratchet fingerprint. /// This function will be called whenever Alice attempts to connect to us with a non-empty /// ratchet fingerprint. /// - /// If the ratchet key was found, the function should return `RestoreAction::RestoreRatchet`. This will - /// cause us to connect to Alice using the returned ratchet number and ratchet key. - /// - /// If the ratchet key could not be found, the application may choose between returning - /// `RatchetAction::DowngradeRatchet` or `RatchetAction::FailAuthentication`. - /// If `RatchetAction::DowngradeRatchet` is returned we will attempt to convince Alice to downgrade - /// to the empty ratchet key, restarting the ratchet chain. - /// If `RatchetAction::FailAuthentication` is returned Alice's connection will be silently dropped. + /// If a ratchet state with a matching fingerprint could not be found, this function should + /// return `Ok(None)`. fn restore_by_fingerprint( &self, ratchet_fingerprint: &[u8; RATCHET_SIZE], @@ -212,16 +207,9 @@ pub trait ApplicationLayer: Sized { remote_static_key: &Self::PublicKey, session_data: &Self::SessionData, ) -> Result<(RatchetState, Option), Self::StorageError>; - /// Atomically save `current_state1` and `current_state2` so that them and only them can be - /// restored with `restore_by_identity` and `restore_by_fingerprint` through a system restart. - /// Theses should overwrite the previous ratchet states 1 and 2 saved to storage. - /// - /// `state_added` will be equal to the brand new ratchet state that was added in this update, - /// or `None` if there is not a new ratchet state this update. `state_deleted1` and - /// `state_deleted2` will be equal to any ratchet states that are to be deleted and overwritten - /// as a result of this update, or `None` if there is not one to be deleted. - /// `state_added` will always have a non-empty (`Some()`) ratchet fingerprint, and it will - /// always be equal to `current_state1`. + /// Atomically commit the update specified by `update_data` to storage, or return an error if + /// the update could not be made. + /// The implementor is free to choose how to apply these updates to storage. /// /// If this returns `Err(IoError)`, the packet which triggered this function to be called will be /// dropped, and no session state will be mutated, preserving synchronization. The remote peer @@ -233,8 +221,7 @@ pub trait ApplicationLayer: Sized { /// fix is to reset both ratchet keys to empty. /// /// This function may also save state to volatile storage, in which case all peers which connect - /// to us will have to allow downgrade, i.e. `initiator_disallows_downgrade` returns false - /// and/or `check_accept_session` returns `(Some(true, _), _)`. + /// to us will have to allow downgrade across the board. /// Otherwise, when we restart, we will not be allowed to reconnect. fn save_ratchet_state( &self, @@ -250,16 +237,43 @@ pub trait ApplicationLayer: Sized { fn event_log(&self, event: LogEvent<'_, Self>); } +/// A set of references to ratchet states specifying how a remote peer's persistent +/// storage should be updated. +/// +/// There should be only up to two ratchet states saved to storage at a time per peer. +/// Every time a new ratchet state is generated, a previous ratchet state will be deleted. +/// +/// These are sensitive values should they ought to be securely stored. pub struct RatchetUpdate<'a> { + /// The ratchet key and fingerprint to store in the first slot. pub state1: &'a RatchetState, + /// The ratchet key and fingerprint to store in the second slot. pub state2: Option<&'a RatchetState>, + /// Whether `state1` is a brand new ratchet state, or if it was previously saved. pub state1_was_just_added: bool, + /// A previous ratchet key and fingerprint that now must be deleted from storage. + /// This will have been a previously given value of `state1` or `state2`. pub state_deleted1: Option<&'a RatchetState>, + /// A previous ratchet key and fingerprint that now must be deleted from storage. + /// It is extremely rare that this field is occupied. pub state_deleted2: Option<&'a RatchetState>, } +/// A collection of fields specifying how to complete the key exchange with a specific remote peer, +/// used by Bob, the responder, at the very last stage of the key exchange. +/// +/// Corresponds to the *Accept* callback of Transition Algorithm 4. pub struct AcceptAction { + /// The data object to be attached to the session if we successfully connect. + /// If this field is None then we will not connect to this remote peer. pub session_data: Option, + /// Whether or not we will accept a connection with the remote peer when they do not have a + /// ratchet key that we think they should have. pub responder_disallows_downgrade: bool, + /// Whether or not to send an explicit rejection packet to the remote peer if we do not create + /// a session with them. + /// + /// This field will not be used if `session_data` is `Some` and the remote peer passes all other + /// authentication checks. pub responder_silently_rejects: bool, } From d20d71ecb029b56be990748d9c856d764c956646 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 8 Aug 2023 09:31:55 -0400 Subject: [PATCH 25/37] reorganized --- examples/basic_test.rs | 28 ++-- src/{applicationlayer.rs => application.rs} | 26 +-- src/context.rs | 2 +- src/fragmentation.rs | 2 +- src/lib.rs | 8 +- src/log_event.rs | 2 +- src/proto.rs | 4 +- src/ratchet_state.rs | 118 +++++++++++-- src/result.rs | 13 +- src/symmetric_state.rs | 2 +- src/zeta.rs | 175 ++++++++++---------- 11 files changed, 223 insertions(+), 157 deletions(-) rename src/{applicationlayer.rs => application.rs} (91%) diff --git a/examples/basic_test.rs b/examples/basic_test.rs index 8770adb..4301add 100644 --- a/examples/basic_test.rs +++ b/examples/basic_test.rs @@ -21,8 +21,8 @@ use rand_core::OsRng; use rand_core::RngCore; use sha2::Sha512; -use zssp_proto::applicationlayer::{ - AcceptAction, ApplicationLayer, RatchetState, RatchetUpdate, Settings, RATCHET_SIZE, +use zssp_proto::application::{ + AcceptAction, ApplicationLayer, RatchetState, RatchetStates, RatchetUpdate, Settings, RATCHET_SIZE, }; use zssp_proto::crypto_impl::PqcKyberSecretKey; use zssp_proto::Session; @@ -37,7 +37,7 @@ struct TestApplication { struct Ratchets { rf_map: HashMap<[u8; RATCHET_SIZE], RatchetState>, - peer_map: HashMap)>, + peer_map: HashMap, } impl Ratchets { fn new() -> Self { @@ -95,13 +95,9 @@ impl ApplicationLayer for &TestApplication { &self, remote_static_key: &Self::PublicKey, session_data: &Self::SessionData, - ) -> Result<(RatchetState, Option), Self::StorageError> { + ) -> Result, Self::StorageError> { let ratchets = self.ratchets.lock().unwrap(); - Ok(ratchets - .peer_map - .get(session_data) - .cloned() - .unwrap_or_else(|| RatchetState::new_initial_states())) + Ok(ratchets.peer_map.get(session_data).cloned()) } fn save_ratchet_state( @@ -111,20 +107,16 @@ impl ApplicationLayer for &TestApplication { update_data: RatchetUpdate<'_>, ) -> Result<(), Self::StorageError> { let mut ratchets = self.ratchets.lock().unwrap(); - ratchets - .peer_map - .insert(*session_data, (update_data.state1.clone(), update_data.state2.cloned())); + ratchets.peer_map.insert(*session_data, update_data.to_states()); - if update_data.state1_was_just_added { - ratchets - .rf_map - .insert(*update_data.state1.fingerprint().unwrap(), update_data.state1.clone()); + if let Some(rf) = update_data.added_fingerprint() { + ratchets.rf_map.insert(*rf, update_data.state1.clone()); println!("[{}] new ratchet #{}", self.name, update_data.state1.chain_len); } - if let Some(Some(rf)) = update_data.state_deleted1.map(|rs| rs.fingerprint()) { + if let Some(rf) = update_data.deleted_fingerprint1() { ratchets.rf_map.remove(rf); } - if let Some(Some(rf)) = update_data.state_deleted2.map(|rs| rs.fingerprint()) { + if let Some(rf) = update_data.deleted_fingerprint2() { ratchets.rf_map.remove(rf); } Ok(()) diff --git a/src/applicationlayer.rs b/src/application.rs similarity index 91% rename from src/applicationlayer.rs rename to src/application.rs index 31c4e12..776c7ac 100644 --- a/src/applicationlayer.rs +++ b/src/application.rs @@ -7,7 +7,7 @@ use crate::LogEvent; use crate::Session; pub use crate::proto::RATCHET_SIZE; -pub use crate::ratchet_state::RatchetState; +pub use crate::ratchet_state::*; /// A container for a vast majority of the dynamic settings within ZSSP, including all time-based settings. /// If the user wishes to measure time in units other than milliseconds for some reason, then they can @@ -206,7 +206,7 @@ pub trait ApplicationLayer: Sized { &self, remote_static_key: &Self::PublicKey, session_data: &Self::SessionData, - ) -> Result<(RatchetState, Option), Self::StorageError>; + ) -> Result, Self::StorageError>; /// Atomically commit the update specified by `update_data` to storage, or return an error if /// the update could not be made. /// The implementor is free to choose how to apply these updates to storage. @@ -237,28 +237,6 @@ pub trait ApplicationLayer: Sized { fn event_log(&self, event: LogEvent<'_, Self>); } -/// A set of references to ratchet states specifying how a remote peer's persistent -/// storage should be updated. -/// -/// There should be only up to two ratchet states saved to storage at a time per peer. -/// Every time a new ratchet state is generated, a previous ratchet state will be deleted. -/// -/// These are sensitive values should they ought to be securely stored. -pub struct RatchetUpdate<'a> { - /// The ratchet key and fingerprint to store in the first slot. - pub state1: &'a RatchetState, - /// The ratchet key and fingerprint to store in the second slot. - pub state2: Option<&'a RatchetState>, - /// Whether `state1` is a brand new ratchet state, or if it was previously saved. - pub state1_was_just_added: bool, - /// A previous ratchet key and fingerprint that now must be deleted from storage. - /// This will have been a previously given value of `state1` or `state2`. - pub state_deleted1: Option<&'a RatchetState>, - /// A previous ratchet key and fingerprint that now must be deleted from storage. - /// It is extremely rare that this field is occupied. - pub state_deleted2: Option<&'a RatchetState>, -} - /// A collection of fields specifying how to complete the key exchange with a specific remote peer, /// used by Bob, the responder, at the very last stage of the key exchange. /// diff --git a/src/context.rs b/src/context.rs index ca4b4a9..3e5e088 100644 --- a/src/context.rs +++ b/src/context.rs @@ -5,7 +5,7 @@ use std::hash::Hash; use std::num::NonZeroU32; use std::sync::{Arc, Mutex, Weak}; -use crate::applicationlayer::ApplicationLayer; +use crate::application::ApplicationLayer; use crate::crypto::{AES_256_KEY_SIZE, AES_GCM_IV_SIZE}; use crate::fragmentation::{send_with_fragmentation, DefragBuffer}; use crate::proto::*; diff --git a/src/fragmentation.rs b/src/fragmentation.rs index 902f836..6af8f45 100644 --- a/src/fragmentation.rs +++ b/src/fragmentation.rs @@ -4,7 +4,7 @@ use std::sync::Mutex; use zeroize::Zeroizing; -use crate::applicationlayer::ApplicationLayer; +use crate::application::ApplicationLayer; use crate::crypto::{PrpAes256, AES_256_KEY_SIZE}; use crate::proto::*; use crate::result::{byzantine_fault, ReceiveError}; diff --git a/src/lib.rs b/src/lib.rs index 4a67dec..f871edf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,7 @@ * (c) ZeroTier, Inc. * https://www.zerotier.com/ */ -//#![warn(missing_docs, rust_2018_idioms)] +#![warn(missing_docs, rust_2018_idioms)] //! TODO: docs mod challenge; mod context; @@ -17,7 +17,10 @@ mod ratchet_state; mod symmetric_state; mod zeta; -pub mod applicationlayer; +/// An abstraction over OS and use-case specific resources and queries. +/// This allows this library to be platform independent, but a user of this library must implement +/// the `ApplicationLayer` trait. +pub mod application; /// A collection of implementation-independent traits for the various specific cryptographic /// algorithms ZSSP depends on. /// @@ -37,6 +40,7 @@ pub mod crypto; /// /// This module contains the trait implementations as well as re-exports of those crates. pub mod crypto_impl; +/// The collection of major return types of this library. pub mod result; pub use context::Context; diff --git a/src/log_event.rs b/src/log_event.rs index b7b9428..69b4826 100644 --- a/src/log_event.rs +++ b/src/log_event.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use crate::applicationlayer::ApplicationLayer; +use crate::application::ApplicationLayer; use crate::Session; /// ZSSP events that might be interesting to log or aggregate into metrics. diff --git a/src/proto.rs b/src/proto.rs index 68594bb..cd0b520 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -85,8 +85,8 @@ pub(crate) const LABEL_RATCHET_STATE: &[u8; 4] = b"ASKR"; pub(crate) const LABEL_HEADER_KEY: &[u8; 4] = b"ASKH"; pub(crate) const LABEL_KEX_KEY: &[u8; 4] = b"ASKK"; -pub(crate) const INIT_COUNTER: u64 = 0; -pub(crate) const EXPIRE_AFTER_USES: u64 = 1 << 32 - 1; +//pub(crate) const EXPIRE_AFTER_USES: u64 = (1 << 32) - 1; +pub(crate) const HARD_EXPIRATION: u64 = u64::MAX; /// Determines the number of counters a session will remember. If a counter arrives over /// this amount out of order relative to other received counters, it is likely to be /// rejected on the basis that the session can't remember if this counter was replayed. diff --git a/src/ratchet_state.rs b/src/ratchet_state.rs index ec5babe..c4ede59 100644 --- a/src/ratchet_state.rs +++ b/src/ratchet_state.rs @@ -5,6 +5,10 @@ use crate::proto::*; /// A ratchet key and fingerprint, /// along with the length of the ratchet chain the keys were derived from. /// +/// Implements constant time equality. +/// The hash implementation only uses the ratchet fingerprint. +/// Any operation involving the ratchet key must take constant time. +/// /// Corresponds to the Ratchet Key and Ratchet Fingerprint described in Section 3. #[derive(Clone, Eq)] pub struct RatchetState { @@ -14,13 +18,19 @@ pub struct RatchetState { } impl PartialEq for RatchetState { fn eq(&self, other: &Self) -> bool { - secure_eq(&self.key, &other.key) - & (self.chain_len == other.chain_len) - & match (self.fingerprint.as_ref(), other.fingerprint.as_ref()) { - (Some(rf1), Some(rf2)) => secure_eq(rf1, rf2), - (None, None) => true, - _ => false, - } + let ret = match (self.fingerprint.as_ref(), other.fingerprint.as_ref()) { + (Some(rf1), Some(rf2)) => secure_eq(rf1, rf2), + (None, None) => true, + _ => false, + }; + ret & secure_eq(&self.key, &other.key) & (self.chain_len == other.chain_len) + } +} +impl std::hash::Hash for RatchetState { + fn hash(&self, state: &mut H) { + if let Some(rf) = &self.fingerprint { + state.write_u64(u64::from_ne_bytes(rf[..8].try_into().unwrap())) + } } } impl RatchetState { @@ -46,7 +56,7 @@ impl RatchetState { buffer.push(1); buffer.extend(LABEL_OTP_TO_RATCHET); buffer.push(0x00); - buffer.extend((2u16 * 512u16).to_be_bytes()); + buffer.extend((1024u16).to_be_bytes()); let r1 = Hmac::hmac(otp, &buffer); buffer[0] = 2; let r2 = Hmac::hmac(otp, &buffer); @@ -56,14 +66,6 @@ impl RatchetState { 1, ) } - - pub fn new_initial_states() -> (RatchetState, Option) { - (RatchetState::empty(), None) - } - pub fn new_otp_states(otp: &[u8]) -> (RatchetState, Option) { - (RatchetState::new_from_otp::(otp), None) - } - pub fn is_empty(&self) -> bool { self.fingerprint.is_none() } @@ -74,3 +76,87 @@ impl RatchetState { self.fingerprint.as_deref() } } +impl Default for RatchetState { + fn default() -> Self { + Self::empty() + } +} + +/// A pair of ratchet states. +/// It is expected that an instance of this object will be saved to a storage device per-peer, +/// and be restore-able via the `ApplicationLayer` trait. +/// +/// This corresponds to the possible values of abstract variables `rf` and `rk` found in Section 4.3. +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct RatchetStates { + pub state1: RatchetState, + pub state2: Option, +} +impl RatchetStates { + pub fn new(state1: RatchetState, state2: Option) -> Self { + Self { state1, state2 } + } + pub fn new_initial_states() -> Self { + Self { state1: RatchetState::empty(), state2: None } + } + pub fn new_otp_states(otp: &[u8]) -> Self { + Self { + state1: RatchetState::new_from_otp::(otp), + state2: None, + } + } +} +impl Default for RatchetStates { + fn default() -> Self { + Self::new_initial_states() + } +} + +/// A set of references to ratchet states specifying how a remote peer's persistent +/// storage should be updated. +/// +/// There should be only up to two ratchet states saved to storage at a time per peer. +/// Every time a new ratchet state is generated, a previous ratchet state will be deleted. +/// +/// These are sensitive values should they ought to be securely stored. +#[derive(Clone)] +pub struct RatchetUpdate<'a> { + /// The ratchet key and fingerprint to store in the first slot. + pub state1: &'a RatchetState, + /// The ratchet key and fingerprint to store in the second slot. + pub state2: Option<&'a RatchetState>, + /// Whether `state1` is a brand new ratchet state, or if it was previously saved. + pub state1_was_just_added: bool, + /// A previous ratchet key and fingerprint that now must be deleted from storage. + /// This will have been a previously given value of `state1` or `state2`. + pub deleted_state1: Option<&'a RatchetState>, + /// A previous ratchet key and fingerprint that now must be deleted from storage. + /// It is extremely rare that this field is occupied. + pub deleted_state2: Option<&'a RatchetState>, +} +impl<'a> RatchetUpdate<'a> { + pub fn to_states(&self) -> RatchetStates { + RatchetStates::new(self.state1.clone(), self.state2.cloned()) + } + pub fn added_fingerprint(&self) -> Option<&[u8; RATCHET_SIZE]> { + if self.state1_was_just_added { + self.state1.fingerprint() + } else { + None + } + } + pub fn deleted_fingerprint1(&self) -> Option<&[u8; RATCHET_SIZE]> { + if let Some(rs) = &self.deleted_state1 { + rs.fingerprint() + } else { + None + } + } + pub fn deleted_fingerprint2(&self) -> Option<&[u8; RATCHET_SIZE]> { + if let Some(rs) = &self.deleted_state2 { + rs.fingerprint() + } else { + None + } + } +} diff --git a/src/result.rs b/src/result.rs index 963017f..ec194cb 100644 --- a/src/result.rs +++ b/src/result.rs @@ -1,18 +1,19 @@ use std::sync::Arc; -use crate::applicationlayer::ApplicationLayer; +use crate::application::ApplicationLayer; use crate::Session; /// An error that can occur when attempting to open a session. /// Depending on the error type trying again may not work. #[derive(Debug, PartialEq, Eq, Clone, Hash)] -pub enum OpenError { +pub enum OpenError { /// An invalid parameter was supplied to the function. InvalidPublicKey, - + /// The given identity slice is too large to be fragmented. It must at most than 4096 bytes. IdentityTooLarge, - RatchetIoError(IoError), + /// Looking up the ratchet state of the specified peer failed. + StorageError(StorageError), } /// An error that can occur when attempting to send data over a session. @@ -62,7 +63,7 @@ pub enum FaultType { /// An error that occurred during the receipt of a given packet. #[derive(Debug, PartialEq, Eq, Clone, Hash)] -pub enum ReceiveError { +pub enum ReceiveError { /// A type of fault that can occur because a remote peer sent us a bad packet. /// Such packets will be ignored by ZSSP but a user of ZSSP might want to log /// them for debugging or tracing. @@ -104,7 +105,7 @@ pub enum ReceiveError { /// One of the ratchet saving or lookup functions returned an error, so the packet had to be /// dropped. - RatchetIoError(IoError), + StorageError(StorageError), } macro_rules! byzantine_fault { diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index 00b77cc..7c276d5 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -2,7 +2,7 @@ use std::marker::PhantomData; use zeroize::Zeroizing; -use crate::applicationlayer::ApplicationLayer; +use crate::application::ApplicationLayer; use crate::crypto::{AeadAesGcm, HashSha512, AES_256_KEY_SIZE, AES_GCM_IV_SIZE, AES_GCM_TAG_SIZE}; use crate::proto::{HASHLEN, LABEL_KBKDF_CHAIN}; diff --git a/src/zeta.rs b/src/zeta.rs index e51f2f1..c005334 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -5,14 +5,14 @@ use std::ops::{Deref, DerefMut}; use std::sync::{Arc, Mutex, Weak}; use zeroize::Zeroizing; -use crate::applicationlayer::ApplicationLayer; -use crate::applicationlayer::RatchetUpdate; +use crate::application::ApplicationLayer; +use crate::application::RatchetUpdate; use crate::challenge::{gen_null_response, respond_to_challenge_in_place}; use crate::context::{log, ContextInner, SessionMap}; use crate::crypto::*; use crate::fragmentation::DefragBuffer; use crate::proto::*; -use crate::ratchet_state::RatchetState; +use crate::ratchet_state::{RatchetState, RatchetStates}; use crate::result::{byzantine_fault, FaultType, OpenError, ReceiveError, SendError}; use crate::symmetric_state::SymmetricState; #[cfg(feature = "logging")] @@ -210,6 +210,19 @@ impl Zeta { pub(crate) fn next_timer(&self) -> i64 { self.timeout_timer.min(self.resend_timer) } + fn get_counter(&mut self) -> Option<(u64, bool)> { + let c = self.send_counter; + if c >= HARD_EXPIRATION { + return None; + } + self.send_counter += 1; + Some(( + c, + c >= self + .key_creation_counter + .saturating_add(App::SETTINGS.rekey_after_key_uses), + )) + } } fn create_a1_state( @@ -269,22 +282,16 @@ pub(crate) fn trans_to_a1( identity: Vec, send: impl FnOnce(&Packet), ) -> Result>, OpenError> { - let (ratchet_state1, ratchet_state2) = app + let ratchet_states = app .restore_by_identity(&s_remote, &session_data) - .map_err(|e| OpenError::RatchetIoError(e))?; + .map_err(|e| OpenError::StorageError(e))?; + let RatchetStates { state1, state2 } = ratchet_states.unwrap_or_default(); let mut session_map = ctx.session_map.lock().unwrap(); let kid_recv = gen_kid(session_map.deref(), ctx.rng.lock().unwrap().deref_mut()); - let a1 = create_a1_state( - &ctx.rng, - &s_remote, - kid_recv, - &ratchet_state1, - ratchet_state2.as_ref(), - identity, - ) - .ok_or(OpenError::InvalidPublicKey)?; + let a1 = create_a1_state(&ctx.rng, &s_remote, kid_recv, &state1, state2.as_ref(), identity) + .ok_or(OpenError::InvalidPublicKey)?; let packet = a1.packet.clone(); let (hk_recv, hk_send) = a1.noise.get_ask(LABEL_HEADER_KEY); @@ -295,14 +302,14 @@ pub(crate) fn trans_to_a1( session_data, was_bob: false, s_remote, - send_counter: INIT_COUNTER, + send_counter: 0, key_creation_counter: 0, counter_antireplay_window: std::array::from_fn(|_| 0), defrag: DefragBuffer::new(Some(hk_recv)), key_index: true, keys: [DuplexKey::default(), DuplexKey::default()], - ratchet_state1, - ratchet_state2, + ratchet_state1: state1, + ratchet_state2: state2, hk_send, resend_timer: current_time + App::SETTINGS.resend_time as i64, timeout_timer: current_time + App::SETTINGS.initial_offer_timeout as i64, @@ -390,13 +397,14 @@ pub(crate) fn received_x1_trans( let mut ratchet_state = None; while i + RATCHET_SIZE <= j { - match app.restore_by_fingerprint((&x1[i..i + RATCHET_SIZE]).try_into().unwrap()) { + let rf = (&x1[i..i + RATCHET_SIZE]).try_into().unwrap(); + match app.restore_by_fingerprint(rf) { Ok(None) => {} Ok(Some(rs)) => { ratchet_state = Some(rs); break; } - Err(e) => return Err(ReceiveError::RatchetIoError(e)), + Err(e) => return Err(ReceiveError::StorageError(e)), } i += RATCHET_SIZE; } @@ -587,12 +595,12 @@ pub(crate) fn received_x2_trans( state1: &new_ratchet_state, state2: ratchet_to_preserve, state1_was_just_added: true, - state_deleted1: ratchet_to_delete, - state_deleted2: None, + deleted_state1: ratchet_to_delete, + deleted_state2: None, }, ); if let Err(e) = result { - return Err(ReceiveError::RatchetIoError(e)); + return Err(ReceiveError::StorageError(e)); } let (kek_recv, kek_send) = noise.get_ask(LABEL_KEX_KEY); @@ -671,7 +679,7 @@ pub(crate) fn received_x3_trans( let identity_end = j; let (kek_send, kek_recv) = noise.get_ask(LABEL_KEX_KEY); - let c = INIT_COUNTER; + let c = 0; let action = app.check_accept_session(&s_remote, &x3[identity_start..identity_end]); let responder_disallows_downgrade = action.responder_disallows_downgrade; @@ -690,8 +698,9 @@ pub(crate) fn received_x3_trans( if let Some(session_data) = session_data { let result = app.restore_by_identity(&s_remote, &session_data); match result { - Ok((ratchet_state1, ratchet_state2)) => { - if (&zeta.ratchet_state != &ratchet_state1) & (Some(&zeta.ratchet_state) != ratchet_state2.as_ref()) { + Ok(rss) => { + let RatchetStates { state1, state2 } = rss.unwrap_or_default(); + if (&zeta.ratchet_state != &state1) & (Some(&zeta.ratchet_state) != state2.as_ref()) { if !responder_disallows_downgrade && zeta.ratchet_state.fingerprint().is_none() { // TODO: add some kind of warning callback or signal. } else { @@ -712,12 +721,12 @@ pub(crate) fn received_x3_trans( state1: &new_ratchet_state, state2: None, state1_was_just_added: true, - state_deleted1: Some(&ratchet_state1), - state_deleted2: ratchet_state2.as_ref(), + deleted_state1: Some(&state1), + deleted_state2: state2.as_ref(), }, ); if let Err(e) = result { - return Err(ReceiveError::RatchetIoError(e)); + return Err(ReceiveError::StorageError(e)); } let mut c1 = Vec::new(); @@ -745,8 +754,8 @@ pub(crate) fn received_x3_trans( session_data, was_bob: true, s_remote, - send_counter: INIT_COUNTER + 1, - key_creation_counter: INIT_COUNTER + 1, + send_counter: 1, + key_creation_counter: 1, key_index: false, keys: [keys, DuplexKey::default()], ratchet_state1: new_ratchet_state, @@ -767,7 +776,7 @@ pub(crate) fn received_x3_trans( send(&Packet(zeta.kid_send.get(), n, c1), Some(&zeta.hk_send)); Ok(session) } - Err(e) => Err(ReceiveError::RatchetIoError(e)), + Err(e) => Err(ReceiveError::StorageError(e)), } } else { if !responder_silently_rejects { @@ -827,12 +836,12 @@ pub(crate) fn received_c1_trans( state1: &zeta.ratchet_state1, state2: None, state1_was_just_added: false, - state_deleted1: zeta.ratchet_state2.as_ref(), - state_deleted2: None, + deleted_state1: zeta.ratchet_state2.as_ref(), + deleted_state2: None, }, ); if let Err(e) = result { - return Err(ReceiveError::RatchetIoError(e)); + return Err(ReceiveError::StorageError(e)); } } @@ -849,8 +858,7 @@ pub(crate) fn received_c1_trans( } let mut c2 = Vec::new(); - let c = zeta.send_counter; - zeta.send_counter += 1; + let (c, _) = zeta.get_counter().ok_or(byzantine_fault!(ExpiredCounter, true))?; let n = to_nonce(PACKET_TYPE_ACK, c); let latest_confirmed_key = zeta .key_ref(false) @@ -976,20 +984,20 @@ pub(crate) fn service( (PACKET_TYPE_REKEY_COMPLETE, k2.clone()) } }; - let c = zeta.send_counter; - zeta.send_counter += 1; - let n = to_nonce(p, c); - let tag = App::Aead::encrypt_in_place( - zeta.key_ref(false).send.kek.as_ref().unwrap(), - n, - None, - &mut control_payload, - ); - control_payload.extend(&tag); - send( - &Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, control_payload), - Some(&zeta.hk_send), - ); + if let Some((c, _)) = zeta.get_counter() { + let n = to_nonce(p, c); + let tag = App::Aead::encrypt_in_place( + zeta.key_ref(false).send.kek.as_ref().unwrap(), + n, + None, + &mut control_payload, + ); + control_payload.extend(&tag); + send( + &Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, control_payload), + Some(&zeta.hk_send), + ); + } } } fn remap( @@ -1088,16 +1096,16 @@ fn timeout_trans( zeta.resend_timer = current_time + App::SETTINGS.resend_time as i64; zeta.beta = ZetaAutomata::R1 { noise, e_secret, k1: k1.clone() }; - let c = zeta.send_counter; - zeta.send_counter += 1; - let n = to_nonce(PACKET_TYPE_REKEY_INIT, c); - let tag = App::Aead::encrypt_in_place(zeta.key_ref(false).send.kek.as_ref().unwrap(), n, None, &mut k1); - k1.extend(&tag); + if let Some((c, _)) = zeta.get_counter() { + let n = to_nonce(PACKET_TYPE_REKEY_INIT, c); + let tag = App::Aead::encrypt_in_place(zeta.key_ref(false).send.kek.as_ref().unwrap(), n, None, &mut k1); + k1.extend(&tag); - send( - &Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, k1), - Some(&zeta.hk_send), - ); + send( + &Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, k1), + Some(&zeta.hk_send), + ); + }; } ZetaAutomata::S1 { .. } => { log!(app, TimeoutKeyConfirm(session)); @@ -1220,12 +1228,12 @@ pub(crate) fn received_k1_trans( state1: &new_ratchet_state, state2: Some(&zeta.ratchet_state1), state1_was_just_added: true, - state_deleted1: zeta.ratchet_state2.as_ref(), - state_deleted2: None, + deleted_state1: zeta.ratchet_state2.as_ref(), + deleted_state2: None, }, ); if let Err(e) = result { - return Err(ReceiveError::RatchetIoError(e)); + return Err(ReceiveError::StorageError(e)); } let (kek_send, kek_recv) = noise.get_ask(LABEL_KEX_KEY); let (nk_send, nk_recv) = noise.split(); @@ -1244,8 +1252,7 @@ pub(crate) fn received_k1_trans( zeta.resend_timer = current_time + App::SETTINGS.resend_time as i64; zeta.beta = ZetaAutomata::R2 { k2: k2.clone() }; - let c = zeta.send_counter; - zeta.send_counter += 1; + let (c, _) = zeta.get_counter().ok_or(byzantine_fault!(ExpiredCounter, true))?; let n = to_nonce(PACKET_TYPE_REKEY_COMPLETE, c); let tag = App::Aead::encrypt_in_place(zeta.key_ref(false).send.kek.as_ref().unwrap(), n, None, &mut k2); k2.extend(&tag); @@ -1333,12 +1340,12 @@ pub(crate) fn received_k2_trans( state1: &new_ratchet_state, state2: None, state1_was_just_added: true, - state_deleted1: Some(&zeta.ratchet_state1), - state_deleted2: zeta.ratchet_state2.as_ref(), + deleted_state1: Some(&zeta.ratchet_state1), + deleted_state2: zeta.ratchet_state2.as_ref(), }, ); if let Err(e) = result { - return Err(ReceiveError::RatchetIoError(e)); + return Err(ReceiveError::StorageError(e)); } let (kek_recv, kek_send) = noise.get_ask(LABEL_KEX_KEY); let (nk_recv, nk_send) = noise.split(); @@ -1357,8 +1364,7 @@ pub(crate) fn received_k2_trans( zeta.beta = ZetaAutomata::S1; let mut c1 = Vec::new(); - let c = zeta.send_counter; - zeta.send_counter += 1; + let (c, _) = zeta.get_counter().ok_or(byzantine_fault!(ExpiredCounter, true))?; let n = to_nonce(PACKET_TYPE_KEY_CONFIRM, c); let tag = App::Aead::encrypt_in_place(zeta.key_ref(false).send.kek.as_ref().unwrap(), n, None, &mut []); c1.extend(&tag); @@ -1394,26 +1400,25 @@ pub(crate) fn send_payload( ) { return Err(SessionNotEstablished); } - let c = zeta.send_counter; - zeta.send_counter += 1; - if c >= zeta.key_creation_counter + App::SETTINGS.rekey_after_key_uses { - if c >= zeta.key_creation_counter + EXPIRE_AFTER_USES { - zeta.expire(); - } else { + if let Some((c, should_rekey)) = zeta.get_counter() { + if should_rekey { // Cause timeout to occur next service interval. zeta.timeout_timer = i64::MIN; } + + let n = to_nonce(PACKET_TYPE_DATA, c); + let tag = App::Aead::encrypt_in_place(zeta.key_ref(false).send.nk.as_ref().unwrap(), n, None, &mut payload); + payload.extend(&tag); + + send( + &Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, payload), + Some(&zeta.hk_send), + ); + Ok(()) + } else { + zeta.expire(); + Err(SessionExpired) } - - let n = to_nonce(PACKET_TYPE_DATA, c); - let tag = App::Aead::encrypt_in_place(zeta.key_ref(false).send.nk.as_ref().unwrap(), n, None, &mut payload); - payload.extend(&tag); - - send( - &Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, payload), - Some(&zeta.hk_send), - ); - Ok(()) } /// Corresponds to Algorithm 10 found in Section 4.3. pub(crate) fn received_payload_in_place( From a5851e93957bb34091226ffd18808a7d64a1ed2e Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 8 Aug 2023 09:32:06 -0400 Subject: [PATCH 26/37] standardized names --- src/application.rs | 14 ++-- src/challenge.rs | 14 ++-- src/context.rs | 8 +- src/crypto/aes.rs | 12 +-- src/crypto/kyber1024.rs | 2 +- src/crypto/p384.rs | 6 +- src/crypto/sha512.rs | 2 +- src/crypto_impl/aes_impl.rs | 19 +++-- src/crypto_impl/kyber1024.rs | 2 +- src/crypto_impl/p384_impl.rs | 4 +- src/crypto_impl/sha512.rs | 2 +- src/fragmentation.rs | 2 +- src/ratchet_state.rs | 6 +- src/symmetric_state.rs | 10 +-- src/zeta.rs | 140 ++++++++++++++--------------------- 15 files changed, 110 insertions(+), 133 deletions(-) diff --git a/src/application.rs b/src/application.rs index 776c7ac..5e97ba0 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,7 +1,7 @@ use rand_core::{CryptoRng, RngCore}; use std::sync::Arc; -use crate::crypto::{AeadAesGcm, HashSha512, KeyPairP384, PrivateKeyKyber1024, PrpAes256, PublicKeyP384}; +use crate::crypto::{Aes256Prp, AesGcmAead, Kyber1024PrivateKey, P384KeyPair, P384PublicKey, Sha512Hash}; #[cfg(feature = "logging")] use crate::LogEvent; use crate::Session; @@ -101,35 +101,35 @@ pub trait ApplicationLayer: Sized { /// We provide an optional implementation for this trait using the `aes` crate. /// /// FIPS compliance requires use of a FIPS certified implementation. - type Prp: PrpAes256; + type Prp: Aes256Prp; /// The implementation of AES-GCM-256 that ZSSP should use. /// The efficiency and security of ZSSP is very closely tied to the efficiency and security of /// this implementation. /// We provide an optional implementation for this trait using the `aes-gcm` crate. /// /// FIPS compliance requires a FIPS certified implementation. - type Aead: AeadAesGcm; + type Aead: AesGcmAead; /// The implementation of SHA-512 that ZSSP should use. /// We provide an optional implementation for this trait using the `sha2` crate. /// /// FIPS compliance requires use of a FIPS certified implementation. - type Hash: HashSha512; + type Hash: Sha512Hash; /// The implementation of P-384 public keys that ZSSP should use. /// We provide an optional implementation for this trait using the `p384` crate. /// /// FIPS compliance requires a FIPS certified implementation. - type PublicKey: PublicKeyP384; + type PublicKey: P384PublicKey; /// The implementation of P-384 private keys that ZSSP should use. /// We provide an optional implementation for this trait using the `p384` crate. /// /// FIPS compliance requires use of a FIPS certified implementation. - type KeyPair: KeyPairP384; + type KeyPair: P384KeyPair; /// The implementation of Kyber1024 that ZSSP should use. /// We provide an optional implementation for this trait using the `pqc_kyber` crate. /// /// No implementation of Kyber1024 can be FIPS certified, but this is not required /// for ZSSP to achieve FIPS compliance. - type Kem: PrivateKeyKyber1024; + type Kem: Kyber1024PrivateKey; /// A user-defined error returned when the `ApplicationLayer` fails to access persistent storage /// for a peer's ratchet states. diff --git a/src/challenge.rs b/src/challenge.rs index 5249a7d..fa8ccd2 100644 --- a/src/challenge.rs +++ b/src/challenge.rs @@ -2,7 +2,7 @@ use std::hash::Hasher; use rand_core::{CryptoRng, RngCore}; -use crate::crypto::{secure_eq, HashSha512}; +use crate::crypto::{secure_eq, Sha512Hash}; use crate::proto::*; pub struct ChallengeContext { @@ -19,7 +19,7 @@ pub fn gen_null_response(rng: &mut Rng) -> [u8; CHALLE response } /// Corresponds to Algorithm 13 found in Section 5. -pub fn respond_to_challenge_in_place( +pub fn respond_to_challenge_in_place( rng: &mut Rng, challenge: &[u8; CHALLENGE_SIZE], pre_response: &mut [u8; CHALLENGE_SIZE], @@ -49,7 +49,7 @@ impl ChallengeContext { } } /// Corresponds to Algorithm 12 found in Section 5. - pub fn process_hello( + pub fn process_hello( &mut self, addr: &impl std::hash::Hash, response: &[u8; CHALLENGE_SIZE], @@ -74,7 +74,7 @@ impl ChallengeContext { Err(challenge) } } - fn create_mac(&self, c: u64, addr: &impl std::hash::Hash) -> [u8; MAC_SIZE] { + fn create_mac(&self, c: u64, addr: &impl std::hash::Hash) -> [u8; MAC_SIZE] { let mut h = Hash::new(); let mut hasher = ShaHasher(&mut h); hasher.write(&c.to_be_bytes()); @@ -100,8 +100,8 @@ impl ChallengeContext { } /// Trick rust into letting us use a hasher that returns more than 64 bits. -struct ShaHasher<'a, ShaImpl: HashSha512>(&'a mut ShaImpl); -impl<'a, ShaImpl: HashSha512> Hasher for ShaHasher<'a, ShaImpl> { +struct ShaHasher<'a, ShaImpl: Sha512Hash>(&'a mut ShaImpl); +impl<'a, ShaImpl: Sha512Hash> Hasher for ShaHasher<'a, ShaImpl> { fn finish(&self) -> u64 { unimplemented!() } @@ -112,7 +112,7 @@ impl<'a, ShaImpl: HashSha512> Hasher for ShaHasher<'a, ShaImpl> { /// Check if the proof of work attached to the first message contains the correct number of leading /// zeros. -fn verify_pow(response: &[u8]) -> bool { +fn verify_pow(response: &[u8]) -> bool { if DIFFICULTY == 0 { return true; } diff --git a/src/context.rs b/src/context.rs index 3e5e088..49c3829 100644 --- a/src/context.rs +++ b/src/context.rs @@ -6,7 +6,7 @@ use std::num::NonZeroU32; use std::sync::{Arc, Mutex, Weak}; use crate::application::ApplicationLayer; -use crate::crypto::{AES_256_KEY_SIZE, AES_GCM_IV_SIZE}; +use crate::crypto::{AES_256_KEY_SIZE, AES_GCM_NONCE_SIZE}; use crate::fragmentation::{send_with_fragmentation, DefragBuffer}; use crate::proto::*; use crate::result::{byzantine_fault, ReceiveError, ReceiveOk, SendError, SessionEvent}; @@ -51,13 +51,13 @@ pub(crate) struct ContextInner { } /// Corresponds to Figure 10 found in Section 4.3. -fn to_aes_nonce(pn: &[u8; PACKET_NONCE_SIZE]) -> [u8; AES_GCM_IV_SIZE] { - let mut an = [0u8; AES_GCM_IV_SIZE]; +fn to_aes_nonce(pn: &[u8; PACKET_NONCE_SIZE]) -> [u8; AES_GCM_NONCE_SIZE] { + let mut an = [0u8; AES_GCM_NONCE_SIZE]; an[2..].copy_from_slice(pn); an } /// Corresponds to Figure 14 found near Section 6. -fn to_packet_nonce(n: &[u8; AES_GCM_IV_SIZE]) -> &[u8; PACKET_NONCE_SIZE] { +fn to_packet_nonce(n: &[u8; AES_GCM_NONCE_SIZE]) -> &[u8; PACKET_NONCE_SIZE] { (&n[n.len() - PACKET_NONCE_SIZE..]).try_into().unwrap() } diff --git a/src/crypto/aes.rs b/src/crypto/aes.rs index 9a16d87..f7881c0 100644 --- a/src/crypto/aes.rs +++ b/src/crypto/aes.rs @@ -6,12 +6,12 @@ pub const AES_256_KEY_SIZE: usize = 32; /// Some implementations of AES-GCM allow use of smaller tags, but ZSSP will only accept 16 byte tags. pub const AES_GCM_TAG_SIZE: usize = 16; /// The size of an AES-GCM IV, or nonce, which is 12 bytes, or 96 bits. -pub const AES_GCM_IV_SIZE: usize = 12; +pub const AES_GCM_NONCE_SIZE: usize = 12; /// A trait for encrypting individual blocks of plaintext using AES-256. /// It is used for header authentication, for which we have a standard model proof that our /// algorithm is secure. -pub trait PrpAes256 { +pub trait Aes256Prp { /// Encrypt the given `block` of plaintext directly using the AES block cipher /// (i.e. AES-256 in zero-padding ECB mode). /// The ciphertext should be written directly back to `block`. @@ -24,12 +24,12 @@ pub trait PrpAes256 { /// A trait accessing AES-GCM-256 encryption and decryption as a set of pure-functions. /// These should be trivial to implement for most implementations of AES-GCM. -pub trait AeadAesGcm { +pub trait AesGcmAead { /// Encrypt the given `buffer` of plaintext using AES-GCM-256, with the given `key`, `iv` and `aad`. /// The ciphertext should be written directly back to `buffer`, and the GCM tag should be returned. fn encrypt_in_place( key: &[u8; AES_256_KEY_SIZE], - iv: [u8; AES_GCM_IV_SIZE], + nonce: &[u8; AES_GCM_NONCE_SIZE], aad: Option<&[u8]>, buffer: &mut [u8], ) -> [u8; AES_GCM_TAG_SIZE]; @@ -37,9 +37,9 @@ pub trait AeadAesGcm { /// The ciphertext should be written directly back to `buffer`, and the GCM tag should be returned. fn decrypt_in_place( key: &[u8; AES_256_KEY_SIZE], - iv: [u8; AES_GCM_IV_SIZE], + nonce: &[u8; AES_GCM_NONCE_SIZE], aad: Option<&[u8]>, buffer: &mut [u8], - tag: [u8; AES_GCM_TAG_SIZE], + tag: &[u8; AES_GCM_TAG_SIZE], ) -> bool; } diff --git a/src/crypto/kyber1024.rs b/src/crypto/kyber1024.rs index 9c5b5c5..0cc898a 100644 --- a/src/crypto/kyber1024.rs +++ b/src/crypto/kyber1024.rs @@ -8,7 +8,7 @@ pub const KYBER_CIPHERTEXT_SIZE: usize = 1568; pub const KYBER_PLAINTEXT_SIZE: usize = 32; /// Instances must securely delete the private key when dropped. -pub trait PrivateKeyKyber1024: Sized + Send + Sync { +pub trait Kyber1024PrivateKey: Sized + Send + Sync { /// Generate a Kyber1024 private key and public key pair, and return the raw bytes of the public /// key. /// The private key will be temporarily held in memory but the public key will be immediately diff --git a/src/crypto/p384.rs b/src/crypto/p384.rs index 6d13387..84acf23 100644 --- a/src/crypto/p384.rs +++ b/src/crypto/p384.rs @@ -6,7 +6,7 @@ pub const P384_PUBLIC_KEY_SIZE: usize = 49; pub const P384_ECDH_SHARED_SECRET_SIZE: usize = 48; /// A NIST P-384 ECDH/ECDSA public key. -pub trait PublicKeyP384: Sized + Send + Sync { +pub trait P384PublicKey: Sized + Send + Sync { /// Create a P-384 public key from raw bytes. /// /// **CRITICAL**: This function must return `None` if the input `raw_key` is not on the P-384 @@ -22,9 +22,9 @@ pub trait PublicKeyP384: Sized + Send + Sync { /// A NIST P-384 ECDH/ECDSA public/private key pair. /// /// Instances must securely delete the private key when dropped. -pub trait KeyPairP384 { +pub trait P384KeyPair { /// The `PublicKeyP384` implementation which matches this `KeyPairP384` implementation. - type PublicKey: PublicKeyP384; + type PublicKey: P384PublicKey; /// Randomly generate a new P-384 keypair. /// /// This function may use the provided RNG or its own, so long as the output is cryptographically random. diff --git a/src/crypto/sha512.rs b/src/crypto/sha512.rs index 2eb32a2..b1468ca 100644 --- a/src/crypto/sha512.rs +++ b/src/crypto/sha512.rs @@ -2,7 +2,7 @@ pub const SHA512_HASH_SIZE: usize = 64; /// A SHA-512 and HMAC-SHA-512 implementation. -pub trait HashSha512 { +pub trait Sha512Hash { /// Create a new instance of SHA-512 for streaming data to. fn new() -> Self; /// Update the instance of SHA-512 with input `data`. diff --git a/src/crypto_impl/aes_impl.rs b/src/crypto_impl/aes_impl.rs index c19b26d..037430a 100644 --- a/src/crypto_impl/aes_impl.rs +++ b/src/crypto_impl/aes_impl.rs @@ -5,7 +5,7 @@ use aes_gcm::{Aes256Gcm, Key, Nonce, Tag}; use crate::crypto::*; -impl PrpAes256 for Aes256 { +impl Aes256Prp for Aes256 { fn encrypt_in_place(key: &[u8; AES_256_KEY_SIZE], block: &mut [u8; AES_256_BLOCK_SIZE]) { let cipher = Aes256::new(GenericArray::from_slice(key)); cipher.encrypt_block(GenericArray::from_mut_slice(block)); @@ -17,17 +17,17 @@ impl PrpAes256 for Aes256 { } } -impl AeadAesGcm for Aes256Gcm { +impl AesGcmAead for Aes256Gcm { fn encrypt_in_place( key: &[u8; AES_256_KEY_SIZE], - iv: [u8; AES_GCM_IV_SIZE], + iv: &[u8; AES_GCM_NONCE_SIZE], aad: Option<&[u8]>, buffer: &mut [u8], ) -> [u8; AES_GCM_TAG_SIZE] { let key = Key::::from_slice(key); let mut cipher = Aes256Gcm::new(&key); cipher - .encrypt_in_place_detached(&Nonce::from(iv), aad.unwrap_or(&[]), buffer) + .encrypt_in_place_detached(&Nonce::from_slice(iv), aad.unwrap_or(&[]), buffer) .unwrap() .try_into() .unwrap() @@ -35,15 +35,20 @@ impl AeadAesGcm for Aes256Gcm { fn decrypt_in_place( key: &[u8; AES_256_KEY_SIZE], - iv: [u8; AES_GCM_IV_SIZE], + iv: &[u8; AES_GCM_NONCE_SIZE], aad: Option<&[u8]>, buffer: &mut [u8], - tag: [u8; AES_GCM_TAG_SIZE], + tag: &[u8; AES_GCM_TAG_SIZE], ) -> bool { let key = Key::::from_slice(key); let mut cipher = Aes256Gcm::new(&key); cipher - .decrypt_in_place_detached(&Nonce::from(iv), aad.unwrap_or(&[]), buffer, &Tag::from(tag)) + .decrypt_in_place_detached( + &Nonce::from_slice(iv), + aad.unwrap_or(&[]), + buffer, + &Tag::from_slice(tag), + ) .is_ok() } } diff --git a/src/crypto_impl/kyber1024.rs b/src/crypto_impl/kyber1024.rs index 9f56dc3..f5a518b 100644 --- a/src/crypto_impl/kyber1024.rs +++ b/src/crypto_impl/kyber1024.rs @@ -7,7 +7,7 @@ use crate::crypto::*; /// The crate `pqc_kyber` is low level and operates directly on buffers of bytes. pub type PqcKyberSecretKey = Zeroizing<[u8; pqc_kyber::KYBER_SECRETKEYBYTES]>; -impl PrivateKeyKyber1024 for PqcKyberSecretKey { +impl Kyber1024PrivateKey for PqcKyberSecretKey { fn generate(rng: &mut Rng) -> (Self, [u8; KYBER_PUBLIC_KEY_SIZE]) { let keypair = pqc_kyber::keypair(rng); (Zeroizing::new(keypair.secret), keypair.public) diff --git a/src/crypto_impl/p384_impl.rs b/src/crypto_impl/p384_impl.rs index d20705a..2898bc6 100644 --- a/src/crypto_impl/p384_impl.rs +++ b/src/crypto_impl/p384_impl.rs @@ -3,7 +3,7 @@ use rand_core::{CryptoRng, RngCore}; use crate::crypto::*; -impl PublicKeyP384 for PublicKey { +impl P384PublicKey for PublicKey { fn from_bytes(raw_key: &[u8; P384_PUBLIC_KEY_SIZE]) -> Option { PublicKey::from_sec1_bytes(raw_key).ok() } @@ -14,7 +14,7 @@ impl PublicKeyP384 for PublicKey { } } -impl KeyPairP384 for EphemeralSecret { +impl P384KeyPair for EphemeralSecret { type PublicKey = PublicKey; fn generate(rng: &mut Rng) -> Self { diff --git a/src/crypto_impl/sha512.rs b/src/crypto_impl/sha512.rs index 11e531c..87c66d5 100644 --- a/src/crypto_impl/sha512.rs +++ b/src/crypto_impl/sha512.rs @@ -3,7 +3,7 @@ use sha2::{Digest, Sha512}; use crate::crypto::*; -impl HashSha512 for Sha512 { +impl Sha512Hash for Sha512 { fn new() -> Self { Digest::new() } diff --git a/src/fragmentation.rs b/src/fragmentation.rs index 6af8f45..43c1cb6 100644 --- a/src/fragmentation.rs +++ b/src/fragmentation.rs @@ -5,7 +5,7 @@ use std::sync::Mutex; use zeroize::Zeroizing; use crate::application::ApplicationLayer; -use crate::crypto::{PrpAes256, AES_256_KEY_SIZE}; +use crate::crypto::{Aes256Prp, AES_256_KEY_SIZE}; use crate::proto::*; use crate::result::{byzantine_fault, ReceiveError}; diff --git a/src/ratchet_state.rs b/src/ratchet_state.rs index c4ede59..75de1ee 100644 --- a/src/ratchet_state.rs +++ b/src/ratchet_state.rs @@ -1,6 +1,6 @@ use zeroize::Zeroizing; -use crate::crypto::{secure_eq, HashSha512}; +use crate::crypto::{secure_eq, Sha512Hash}; use crate::proto::*; /// A ratchet key and fingerprint, /// along with the length of the ratchet chain the keys were derived from. @@ -51,7 +51,7 @@ impl RatchetState { chain_len: 0, } } - pub fn new_from_otp(otp: &[u8]) -> RatchetState { + pub fn new_from_otp(otp: &[u8]) -> RatchetState { let mut buffer = Vec::new(); buffer.push(1); buffer.extend(LABEL_OTP_TO_RATCHET); @@ -99,7 +99,7 @@ impl RatchetStates { pub fn new_initial_states() -> Self { Self { state1: RatchetState::empty(), state2: None } } - pub fn new_otp_states(otp: &[u8]) -> Self { + pub fn new_otp_states(otp: &[u8]) -> Self { Self { state1: RatchetState::new_from_otp::(otp), state2: None, diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index 7c276d5..deaab6e 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -3,7 +3,7 @@ use std::marker::PhantomData; use zeroize::Zeroizing; use crate::application::ApplicationLayer; -use crate::crypto::{AeadAesGcm, HashSha512, AES_256_KEY_SIZE, AES_GCM_IV_SIZE, AES_GCM_TAG_SIZE}; +use crate::crypto::{AesGcmAead, Sha512Hash, AES_256_KEY_SIZE, AES_GCM_NONCE_SIZE, AES_GCM_TAG_SIZE}; use crate::proto::{HASHLEN, LABEL_KBKDF_CHAIN}; pub struct SymmetricState { @@ -125,11 +125,11 @@ impl SymmetricState { /// Corresponds to Noise `EncryptAndHash`. pub fn encrypt_and_hash_in_place( &mut self, - iv: [u8; AES_GCM_IV_SIZE], + iv: [u8; AES_GCM_NONCE_SIZE], plaintext_start: usize, buffer: &mut Vec, ) { - let tag = App::Aead::encrypt_in_place(&self.k, iv, Some(&self.h), &mut buffer[plaintext_start..]); + let tag = App::Aead::encrypt_in_place(&self.k, &iv, Some(&self.h), &mut buffer[plaintext_start..]); buffer.extend(&tag); self.mix_hash(&buffer[plaintext_start..]); } @@ -137,7 +137,7 @@ impl SymmetricState { #[must_use] pub fn decrypt_and_hash_in_place( &mut self, - iv: [u8; AES_GCM_IV_SIZE], + iv: [u8; AES_GCM_NONCE_SIZE], buffer: &mut [u8], tag: [u8; AES_GCM_TAG_SIZE], ) -> bool { @@ -145,7 +145,7 @@ impl SymmetricState { hash.update(&self.h); hash.update(buffer); hash.update(&tag); - let ret = App::Aead::decrypt_in_place(&self.k, iv, Some(&self.h), buffer, tag); + let ret = App::Aead::decrypt_in_place(&self.k, &iv, Some(&self.h), buffer, &tag); self.h = hash.finish(); ret } diff --git a/src/zeta.rs b/src/zeta.rs index c005334..1601b70 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -27,8 +27,8 @@ use crate::LogEvent::*; /// the key id. /// /// Corresponds to Figure 10 found in Section 4.3. -pub(crate) fn to_nonce(packet_type: u8, counter: u64) -> [u8; AES_GCM_IV_SIZE] { - let mut ret = [0u8; AES_GCM_IV_SIZE]; +pub(crate) fn to_nonce(packet_type: u8, counter: u64) -> [u8; AES_GCM_NONCE_SIZE] { + let mut ret = [0u8; AES_GCM_NONCE_SIZE]; ret[3] = packet_type; // Noise requires a big endian counter at the end of the Nonce ret[4..].copy_from_slice(&counter.to_be_bytes()); @@ -97,7 +97,7 @@ pub(crate) struct Keys { /// Corresponds to the tuple of values the Transition Algorithms send to the remote peer in Section 4.3. #[derive(Clone)] -pub(crate) struct Packet(pub u32, pub [u8; AES_GCM_IV_SIZE], pub Vec); +pub(crate) struct Packet(pub u32, pub [u8; AES_GCM_NONCE_SIZE], pub Vec); /// Corresponds to State A_1 of the Zeta State Machine found in Section 4.1. #[derive(Clone)] @@ -347,7 +347,7 @@ pub(crate) fn respond_to_challenge( pub(crate) fn received_x1_trans( app: &App, ctx: &ContextInner, - n: [u8; AES_GCM_IV_SIZE], + n: [u8; AES_GCM_NONCE_SIZE], mut x1: Vec, send: impl FnOnce(&Packet, &[u8; AES_256_KEY_SIZE]), ) -> Result<(), ReceiveError> { @@ -359,7 +359,7 @@ pub(crate) fn received_x1_trans( if !(HANDSHAKE_HELLO_MIN_SIZE..=HANDSHAKE_HELLO_MAX_SIZE).contains(&x1.len()) { return Err(byzantine_fault!(InvalidPacket, true)); } - if &n[AES_GCM_IV_SIZE - 8..] != &x1[x1.len() - 8..] { + if &n[AES_GCM_NONCE_SIZE - 8..] != &x1[x1.len() - 8..] { return Err(byzantine_fault!(FailedAuth, true)); } let mut noise = SymmetricState::::initialize(PROTOCOL_NAME_NOISE_XK); @@ -480,7 +480,7 @@ pub(crate) fn received_x2_trans( app: &App, ctx: &Arc>, kid: NonZeroU32, - n: [u8; AES_GCM_IV_SIZE], + n: [u8; AES_GCM_NONCE_SIZE], mut x2: Vec, send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), ) -> Result<(), ReceiveError> { @@ -494,7 +494,7 @@ pub(crate) fn received_x2_trans( return Err(byzantine_fault!(UnknownLocalKeyId, true)); } let (_, c) = from_nonce(&n); - if c >= COUNTER_WINDOW_MAX_SKIP_AHEAD || &n[AES_GCM_IV_SIZE - 3..] != &x2[x2.len() - 3..] { + if c >= COUNTER_WINDOW_MAX_SKIP_AHEAD || &n[AES_GCM_NONCE_SIZE - 3..] != &x2[x2.len() - 3..] { return Err(byzantine_fault!(FailedAuth, true)); } let result = (|| { @@ -634,6 +634,27 @@ pub(crate) fn received_x2_trans( } result.map(|_| ()) } +fn send_control( + zeta: &mut Zeta, + packet_type: u8, + mut payload: Vec, + send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), +) -> bool { + if let Some((c, _)) = zeta.get_counter() { + if let (Some(kek), Some(kid)) = (zeta.key_ref(false).send.kek.as_ref(), zeta.key_ref(false).send.kid) { + let nonce = to_nonce(packet_type, c); + let tag = App::Aead::encrypt_in_place(kek, &nonce, None, &mut payload[HEADER_SIZE..]); + payload.extend(tag); + + send(&Packet(kid.get(), nonce, payload), Some(&zeta.hk_send)); + true + } else { + false + } + } else { + false + } +} /// Corresponds to Transition Algorithm 4 found in Section 4.3. pub(crate) fn received_x3_trans( zeta: StateB2, @@ -688,7 +709,7 @@ pub(crate) fn received_x3_trans( let create_reject = || { let mut d = Vec::::new(); let n = to_nonce(PACKET_TYPE_SESSION_REJECTED, c); - let tag = App::Aead::encrypt_in_place(&kek_send, n, None, &mut []); + let tag = App::Aead::encrypt_in_place(&kek_send, &n, None, &mut []); d.extend(&tag); // We just used a counter with this key, but we are not storing // the fact we used it in memory. This is currently ok because the @@ -731,7 +752,7 @@ pub(crate) fn received_x3_trans( let mut c1 = Vec::new(); let n = to_nonce(PACKET_TYPE_KEY_CONFIRM, c); - let tag = App::Aead::encrypt_in_place(&kek_send, n, None, &mut []); + let tag = App::Aead::encrypt_in_place(&kek_send, &n, None, &mut []); c1.extend(&tag); let (nk1, nk2) = noise.split(); @@ -791,7 +812,7 @@ pub(crate) fn received_c1_trans( app: &App, rng: &Mutex, kid: NonZeroU32, - n: [u8; AES_GCM_IV_SIZE], + n: [u8; AES_GCM_NONCE_SIZE], c1: Vec, send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), ) -> Result> { @@ -817,7 +838,7 @@ pub(crate) fn received_c1_trans( .as_ref() .ok_or(byzantine_fault!(OutOfSequence, true))?; let tag = c1[..].try_into().unwrap(); - if !App::Aead::decrypt_in_place(specified_key, n, None, &mut [], tag) { + if !App::Aead::decrypt_in_place(specified_key, &n, None, &mut [], tag) { return Err(byzantine_fault!(FailedAuth, true)); } let (_, c) = from_nonce(&n); @@ -856,23 +877,11 @@ pub(crate) fn received_c1_trans( zeta.beta = ZetaAutomata::S2; } } - let mut c2 = Vec::new(); + let c2 = Vec::new(); + if !send_control(zeta, PACKET_TYPE_ACK, c2, send) { + return Err(byzantine_fault!(OutOfSequence, true)); + } - let (c, _) = zeta.get_counter().ok_or(byzantine_fault!(ExpiredCounter, true))?; - let n = to_nonce(PACKET_TYPE_ACK, c); - let latest_confirmed_key = zeta - .key_ref(false) - .send - .kek - .as_ref() - .ok_or(byzantine_fault!(OutOfSequence, true))?; - let tag = App::Aead::encrypt_in_place(latest_confirmed_key, n, None, &mut []); - c2.extend(&tag); - - send( - &Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, c2), - Some(&zeta.hk_send), - ); Ok(just_establised) } /// Corresponds to the trivial Transition Algorithm described for processing C_2 packets found in @@ -882,7 +891,7 @@ pub(crate) fn received_c2_trans( app: &App, rng: &Mutex, kid: NonZeroU32, - n: [u8; AES_GCM_IV_SIZE], + n: [u8; AES_GCM_NONCE_SIZE], c2: Vec, ) -> Result<(), ReceiveError> { use FaultType::*; @@ -900,7 +909,7 @@ pub(crate) fn received_c2_trans( } let tag = c2[..].try_into().unwrap(); - if !App::Aead::decrypt_in_place(zeta.key_ref(false).recv.kek.as_ref().unwrap(), n, None, &mut [], tag) { + if !App::Aead::decrypt_in_place(zeta.key_ref(false).recv.kek.as_ref().unwrap(), &n, None, &mut [], tag) { return Err(byzantine_fault!(FailedAuth, true)); } let (_, c) = from_nonce(&n); @@ -921,7 +930,7 @@ pub(crate) fn received_c2_trans( pub(crate) fn received_d_trans( zeta: &mut Zeta, kid: NonZeroU32, - n: [u8; AES_GCM_IV_SIZE], + n: [u8; AES_GCM_NONCE_SIZE], d: Vec, ) -> Result<(), ReceiveError> { use FaultType::*; @@ -934,7 +943,7 @@ pub(crate) fn received_d_trans( } let tag = d[..].try_into().unwrap(); - if !App::Aead::decrypt_in_place(zeta.key_ref(true).recv.kek.as_ref().unwrap(), n, None, &mut [], tag) { + if !App::Aead::decrypt_in_place(zeta.key_ref(true).recv.kek.as_ref().unwrap(), &n, None, &mut [], tag) { return Err(byzantine_fault!(FailedAuth, true)); } let (_, c) = from_nonce(&n); @@ -960,7 +969,7 @@ pub(crate) fn service( // Corresponds to the resend timer rules found in Section 4.1 - Definition 3. zeta.resend_timer = current_time + App::SETTINGS.resend_time as i64; - let (p, mut control_payload) = match &zeta.beta { + let (p, control_payload) = match &zeta.beta { ZetaAutomata::Null => return, ZetaAutomata::A1(StateA1 { packet, .. }) => { log!(app, ResentX1(session)); @@ -984,20 +993,8 @@ pub(crate) fn service( (PACKET_TYPE_REKEY_COMPLETE, k2.clone()) } }; - if let Some((c, _)) = zeta.get_counter() { - let n = to_nonce(p, c); - let tag = App::Aead::encrypt_in_place( - zeta.key_ref(false).send.kek.as_ref().unwrap(), - n, - None, - &mut control_payload, - ); - control_payload.extend(&tag); - send( - &Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, control_payload), - Some(&zeta.hk_send), - ); - } + + send_control(zeta, p, control_payload, send); } } fn remap( @@ -1096,16 +1093,7 @@ fn timeout_trans( zeta.resend_timer = current_time + App::SETTINGS.resend_time as i64; zeta.beta = ZetaAutomata::R1 { noise, e_secret, k1: k1.clone() }; - if let Some((c, _)) = zeta.get_counter() { - let n = to_nonce(PACKET_TYPE_REKEY_INIT, c); - let tag = App::Aead::encrypt_in_place(zeta.key_ref(false).send.kek.as_ref().unwrap(), n, None, &mut k1); - k1.extend(&tag); - - send( - &Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, k1), - Some(&zeta.hk_send), - ); - }; + send_control(zeta, PACKET_TYPE_REKEY_INIT, k1, send); } ZetaAutomata::S1 { .. } => { log!(app, TimeoutKeyConfirm(session)); @@ -1130,7 +1118,7 @@ pub(crate) fn received_k1_trans( session_map: &SessionMap, s_secret: &App::KeyPair, kid: NonZeroU32, - n: [u8; AES_GCM_IV_SIZE], + n: [u8; AES_GCM_NONCE_SIZE], mut k1: Vec, send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), ) -> Result<(), ReceiveError> { @@ -1161,10 +1149,10 @@ pub(crate) fn received_k1_trans( let tag = k1[i..].try_into().unwrap(); if !App::Aead::decrypt_in_place( zeta.key_ref(false).recv.kek.as_ref().unwrap(), - n, + &n, None, &mut k1[..i], - tag, + &tag, ) { return Err(byzantine_fault!(FailedAuth, true)); } @@ -1252,15 +1240,7 @@ pub(crate) fn received_k1_trans( zeta.resend_timer = current_time + App::SETTINGS.resend_time as i64; zeta.beta = ZetaAutomata::R2 { k2: k2.clone() }; - let (c, _) = zeta.get_counter().ok_or(byzantine_fault!(ExpiredCounter, true))?; - let n = to_nonce(PACKET_TYPE_REKEY_COMPLETE, c); - let tag = App::Aead::encrypt_in_place(zeta.key_ref(false).send.kek.as_ref().unwrap(), n, None, &mut k2); - k2.extend(&tag); - - send( - &Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, k2), - Some(&zeta.hk_send), - ); + send_control(zeta, PACKET_TYPE_REKEY_COMPLETE, k2, send); Ok(()) })(); if matches!(result, Err(ReceiveError::ByzantineFault { .. })) { @@ -1273,7 +1253,7 @@ pub(crate) fn received_k2_trans( zeta: &mut Zeta, app: &App, kid: NonZeroU32, - n: [u8; AES_GCM_IV_SIZE], + n: [u8; AES_GCM_NONCE_SIZE], mut k2: Vec, send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), ) -> Result<(), ReceiveError> { @@ -1295,10 +1275,10 @@ pub(crate) fn received_k2_trans( let tag = k2[i..].try_into().unwrap(); if !App::Aead::decrypt_in_place( zeta.key_ref(false).recv.kek.as_ref().unwrap(), - n, + &n, None, &mut k2[..i], - tag, + &tag, ) { return Err(byzantine_fault!(FailedAuth, true)); } @@ -1363,16 +1343,8 @@ pub(crate) fn received_k2_trans( zeta.resend_timer = current_time + App::SETTINGS.resend_time as i64; zeta.beta = ZetaAutomata::S1; - let mut c1 = Vec::new(); - let (c, _) = zeta.get_counter().ok_or(byzantine_fault!(ExpiredCounter, true))?; - let n = to_nonce(PACKET_TYPE_KEY_CONFIRM, c); - let tag = App::Aead::encrypt_in_place(zeta.key_ref(false).send.kek.as_ref().unwrap(), n, None, &mut []); - c1.extend(&tag); - - send( - &Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, c1), - Some(&zeta.hk_send), - ); + let c1 = Vec::new(); + send_control(zeta, PACKET_TYPE_KEY_CONFIRM, c1, send); Ok(()) } else { unreachable!() @@ -1407,7 +1379,7 @@ pub(crate) fn send_payload( } let n = to_nonce(PACKET_TYPE_DATA, c); - let tag = App::Aead::encrypt_in_place(zeta.key_ref(false).send.nk.as_ref().unwrap(), n, None, &mut payload); + let tag = App::Aead::encrypt_in_place(zeta.key_ref(false).send.nk.as_ref().unwrap(), &n, None, &mut payload); payload.extend(&tag); send( @@ -1424,7 +1396,7 @@ pub(crate) fn send_payload( pub(crate) fn received_payload_in_place( zeta: &mut Zeta, kid: NonZeroU32, - n: [u8; AES_GCM_IV_SIZE], + n: [u8; AES_GCM_NONCE_SIZE], payload: &mut Vec, ) -> Result<(), ReceiveError> { use FaultType::*; @@ -1450,7 +1422,7 @@ pub(crate) fn received_payload_in_place( .as_ref() .ok_or(byzantine_fault!(OutOfSequence, true))?; let tag = payload[i..].try_into().unwrap(); - if !App::Aead::decrypt_in_place(specified_key, n, None, &mut payload[..i], tag) { + if !App::Aead::decrypt_in_place(specified_key, &n, None, &mut payload[..i], &tag) { return Err(byzantine_fault!(FailedAuth, true)); } let (_, c) = from_nonce(&n); From 3176d7594824517b702ebd0f063edab3ba36e5c9 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 8 Aug 2023 09:45:40 -0400 Subject: [PATCH 27/37] replaced mutex with refcell --- examples/basic_test.rs | 6 +-- src/context.rs | 64 ++++++++++++++++---------------- src/fragmentation.rs | 10 ++--- src/zeta.rs | 84 +++++++++++++++++++----------------------- 4 files changed, 77 insertions(+), 87 deletions(-) diff --git a/examples/basic_test.rs b/examples/basic_test.rs index 4301add..40997e1 100644 --- a/examples/basic_test.rs +++ b/examples/basic_test.rs @@ -142,7 +142,7 @@ fn alice_main( bob_pubkey: PublicKey, ) { let startup_time = std::time::Instant::now(); - let context = zssp_proto::Context::<&TestApplication>::new(alice_keypair, OsRng); + let mut context = zssp_proto::Context::<&TestApplication>::new(alice_keypair, OsRng); let mut next_service = startup_time.elapsed().as_millis() as i64 + 500; let test_data = [1u8; TEST_MTU * 10]; let mut up = false; @@ -245,7 +245,7 @@ fn bob_main( bob_keypair: EphemeralSecret, ) { let startup_time = std::time::Instant::now(); - let context = zssp_proto::Context::<&TestApplication>::new(bob_keypair, OsRng); + let mut context = zssp_proto::Context::<&TestApplication>::new(bob_keypair, OsRng); let mut last_speed_metric = startup_time.elapsed().as_millis() as i64; let mut next_service = last_speed_metric + 500; let mut transferred = 0u64; @@ -372,7 +372,7 @@ fn core(time: u64, packet_success_rate: u32) { fn main() { let args = std::env::args(); let packet_success_rate = if args.len() <= 1 { - let default_success_rate = 1.0; + let default_success_rate = 0.5; ((u32::MAX as f64) * default_success_rate) as u32 } else { ((u32::MAX as f64) * f64::from_str(args.last().unwrap().as_str()).unwrap()) as u32 diff --git a/src/context.rs b/src/context.rs index 49c3829..fb9ff36 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,9 +1,10 @@ use rand_core::RngCore; +use std::cell::RefCell; use std::collections::hash_map::Entry; use std::collections::HashMap; use std::hash::Hash; use std::num::NonZeroU32; -use std::sync::{Arc, Mutex, Weak}; +use std::sync::{Arc, Weak}; use crate::application::ApplicationLayer; use crate::crypto::{AES_256_KEY_SIZE, AES_GCM_NONCE_SIZE}; @@ -37,17 +38,17 @@ impl Clone for Context { } } -pub(crate) type SessionMap = Mutex>>>; +pub(crate) type SessionMap = RefCell>>>; pub(crate) struct ContextInner { - pub(crate) rng: Mutex, + pub(crate) rng: RefCell, pub(crate) s_secret: App::KeyPair, pub(crate) session_map: SessionMap, - pub(crate) sessions: Mutex, Weak>>>, - pub(crate) b2_map: Mutex>>, + pub(crate) sessions: RefCell, Weak>>>, + pub(crate) b2_map: RefCell>>, - hello_defrag: Mutex, - challenge: Mutex, + hello_defrag: RefCell, + challenge: RefCell, } /// Corresponds to Figure 10 found in Section 4.3. @@ -66,19 +67,19 @@ impl Context { pub fn new(static_secret_key: App::KeyPair, mut rng: App::Rng) -> Self { let challenge = ChallengeContext::new(&mut rng); Self(Arc::new(ContextInner { - rng: Mutex::new(rng), + rng: RefCell::new(rng), s_secret: static_secret_key, - session_map: Mutex::new(HashMap::new()), - b2_map: Mutex::new(HashMap::new()), - hello_defrag: Mutex::new(DefragBuffer::new(None)), - challenge: Mutex::new(challenge), - sessions: Mutex::new(HashMap::new()), + session_map: RefCell::new(HashMap::new()), + b2_map: RefCell::new(HashMap::new()), + hello_defrag: RefCell::new(DefragBuffer::new(None)), + challenge: RefCell::new(challenge), + sessions: RefCell::new(HashMap::new()), })) } /// Enable the ZeroTier Challenge Protocol, to protect this machine from CPU exhaustion DDOS /// attacks. - pub fn enable_challenge(&self, enabled: bool) { - self.0.challenge.lock().unwrap().enabled = enabled; + pub fn enable_challenge(&mut self, enabled: bool) { + self.0.challenge.borrow_mut().enabled = enabled; } /// Create a new session and send initial packet(s) to other side. @@ -93,7 +94,7 @@ impl Context { /// * `identity` - Payload to be sent to Bob that contains the information necessary /// for the upper protocol to authenticate and approve of Alice's identity pub fn open( - &self, + &mut self, app: App, send: impl FnMut(Vec) -> bool, mut mtu: usize, @@ -130,7 +131,7 @@ impl Context { /// * `remote_address` - Whatever the remote address is, as long as you can Hash it /// * `raw_fragment` - Buffer containing incoming wire packet pub fn receive<'a, SendFn: FnMut(Vec) -> bool>( - &self, + &mut self, app: App, send_unassociated_reply: impl FnMut(Vec) -> bool, mut send_unassociated_mtu: usize, @@ -145,10 +146,10 @@ impl Context { // Multiplex session. let kid_recv = u32::from_be_bytes(raw_fragment[..KID_SIZE].try_into().unwrap()); if let Some(kid_recv) = NonZeroU32::new(kid_recv) { - let session = ctx.session_map.lock().unwrap().get(&kid_recv).map(|r| r.upgrade()); + let session = ctx.session_map.borrow_mut().get(&kid_recv).map(|r| r.upgrade()); if let Some(Some(session)) = session { // Process recv fragmentation layer. - let mut zeta = session.0.lock().unwrap(); + let mut zeta = session.0.borrow_mut(); let result = zeta.defrag .received_fragment::(raw_fragment, app.time(), |n, frag_no, frag_count| { @@ -293,7 +294,7 @@ impl Context { Ok(ReceiveOk::Unassociated) } } else { - let mut b2_map = ctx.b2_map.lock().unwrap(); + let mut b2_map = ctx.b2_map.borrow_mut(); if let Entry::Occupied(mut entry) = b2_map.entry(kid_recv) { let zeta = entry.get_mut(); // Process recv fragmentation layer. @@ -341,7 +342,7 @@ impl Context { } } else { // Process recv fragmentation layer. - let result = ctx.hello_defrag.lock().unwrap().received_fragment::( + let result = ctx.hello_defrag.borrow_mut().received_fragment::( raw_fragment, app.time(), |n, frag_no, frag_count| { @@ -360,7 +361,7 @@ impl Context { log!(app, ReceivedRawX1); // Process recv challenge layer. let challenge_start = assembled_packet.len() - CHALLENGE_SIZE; - let result = ctx.challenge.lock().unwrap().process_hello::( + let result = ctx.challenge.borrow_mut().process_hello::( remote_address, (&assembled_packet[challenge_start..]).try_into().unwrap(), ); @@ -369,7 +370,7 @@ impl Context { let mut challenge_packet = Vec::new(); challenge_packet.extend(&assembled_packet[..KID_SIZE]); challenge_packet.extend(&challenge); - let nonce = to_nonce(PACKET_TYPE_CHALLENGE, ctx.rng.lock().unwrap().next_u64()); + let nonce = to_nonce(PACKET_TYPE_CHALLENGE, ctx.rng.borrow_mut().next_u64()); send_with_fragmentation::( send_unassociated_reply, send_unassociated_mtu, @@ -413,9 +414,8 @@ impl Context { if let Some(kid_recv) = NonZeroU32::new(u32::from_be_bytes(assembled_packet[..KID_SIZE].try_into().unwrap())) { - if let Some(Some(session)) = ctx.session_map.lock().unwrap().get(&kid_recv).map(|r| r.upgrade()) - { - let mut zeta = session.0.lock().unwrap(); + if let Some(Some(session)) = ctx.session_map.borrow_mut().get(&kid_recv).map(|r| r.upgrade()) { + let mut zeta = session.0.borrow_mut(); respond_to_challenge( &mut zeta, &ctx.rng, @@ -443,14 +443,14 @@ impl Context { /// * `mtu` - The MTU of the link, all packets passed to `send` will be at most `mtu` in length /// * `payload` - Data to send pub fn send( - &self, + &mut self, session: &Arc>, send: impl FnMut(Vec) -> bool, mut mtu: usize, payload: Vec, ) -> Result<(), SendError> { mtu = mtu.max(MIN_TRANSPORT_MTU); - let mut zeta = session.0.lock().unwrap(); + let mut zeta = session.0.borrow_mut(); send_payload(&mut zeta, payload, |Packet(kid, nonce, payload), hk| { send_with_fragmentation::(send, mtu, *kid, to_packet_nonce(nonce), &payload, hk); }) @@ -464,17 +464,17 @@ impl Context { /// /// * `send_to` - Function to get a sender and an MTU to send something over an active session pub fn service) -> bool>( - &self, + &mut self, app: App, mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, ) -> i64 { let ctx = &self.0; - let sessions = ctx.sessions.lock().unwrap(); + let sessions = ctx.sessions.borrow_mut(); let current_time = app.time(); let mut next_timer = i64::MAX; for (_, session) in sessions.iter() { if let Some(session) = session.upgrade() { - let mut zeta = session.0.lock().unwrap(); + let mut zeta = session.0.borrow_mut(); service( &mut zeta, &session, @@ -499,7 +499,7 @@ impl Context { zeta.defrag.service::(current_time); } } - ctx.hello_defrag.lock().unwrap().service::(current_time); + ctx.hello_defrag.borrow_mut().service::(current_time); (App::SETTINGS.resend_time as i64).min(next_timer - current_time) } } diff --git a/src/fragmentation.rs b/src/fragmentation.rs index 43c1cb6..55b425f 100644 --- a/src/fragmentation.rs +++ b/src/fragmentation.rs @@ -1,6 +1,6 @@ +use std::cell::RefCell; use std::collections::hash_map::Entry; use std::collections::HashMap; -use std::sync::Mutex; use zeroize::Zeroizing; @@ -66,7 +66,7 @@ pub fn send_with_fragmentation( } pub struct DefragBuffer { - fragment_map: Mutex>, + fragment_map: RefCell>, hk_recv: Option>, } @@ -79,7 +79,7 @@ struct Buffer { impl DefragBuffer { pub fn new(hk_recv: Option>) -> Self { - Self { fragment_map: Mutex::new(HashMap::new()), hk_recv } + Self { fragment_map: RefCell::new(HashMap::new()), hk_recv } } /// Corresponds to the authentication and defragmentation algorithm described in Section 6.1. @@ -116,7 +116,7 @@ impl DefragBuffer { } let expiration_time = current_time + App::SETTINGS.fragment_assembly_timeout as i64; - let mut map = self.fragment_map.lock().unwrap(); + let mut map = self.fragment_map.borrow_mut(); match map.entry(n) { Entry::Occupied(mut entry) => { let buffer = entry.get_mut(); @@ -158,7 +158,7 @@ impl DefragBuffer { } pub fn service(&self, current_time: i64) { - let mut map = self.fragment_map.lock().unwrap(); + let mut map = self.fragment_map.borrow_mut(); map.retain(|_, buffer| buffer.expiration_time < current_time); } } diff --git a/src/zeta.rs b/src/zeta.rs index 1601b70..ca69176 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -1,8 +1,9 @@ use rand_core::RngCore; +use std::cell::RefCell; use std::collections::HashMap; use std::num::NonZeroU32; use std::ops::{Deref, DerefMut}; -use std::sync::{Arc, Mutex, Weak}; +use std::sync::{Arc, Weak}; use zeroize::Zeroizing; use crate::application::ApplicationLayer; @@ -69,7 +70,7 @@ pub(crate) struct Zeta { /// ZeroTier Secure Session Protocol (ZSSP) Session. /// /// A FIPS/NIST compliant variant of Noise_XK with hybrid Kyber1024 PQ data forward secrecy. -pub struct Session(pub(crate) Mutex>); +pub struct Session(pub(crate) RefCell>); /// Corresponds to State B_2 of the Zeta State Machine found in Section 4.1 - Definition 3. pub(crate) struct StateB2 { @@ -130,8 +131,8 @@ pub(crate) enum ZetaAutomata { } impl SymmetricState { - fn write_e(&mut self, rng: &Mutex, packet: &mut Vec) -> App::KeyPair { - let e_secret = App::KeyPair::generate(rng.lock().unwrap().deref_mut()); + fn write_e(&mut self, rng: &RefCell, packet: &mut Vec) -> App::KeyPair { + let e_secret = App::KeyPair::generate(rng.borrow_mut().deref_mut()); let pub_key = e_secret.public_key_bytes(); packet.extend(&pub_key); self.mix_hash(&pub_key); @@ -196,8 +197,8 @@ impl Zeta { self.timeout_timer = i64::MAX; self.beta = ZetaAutomata::Null; if let Some(ctx) = self.ctx.upgrade() { - let mut session_map = ctx.session_map.lock().unwrap(); - let mut sessions = ctx.sessions.lock().unwrap(); + let mut session_map = ctx.session_map.borrow_mut(); + let mut sessions = ctx.sessions.borrow_mut(); for key in &self.keys { if let Some(kid_recv) = key.recv.kid { if let Some(weak) = session_map.remove(&kid_recv) { @@ -226,7 +227,7 @@ impl Zeta { } fn create_a1_state( - rng: &Mutex, + rng: &RefCell, s_remote: &App::PublicKey, kid_recv: NonZeroU32, ratchet_state1: &RatchetState, @@ -249,7 +250,7 @@ fn create_a1_state( noise.mix_dh(&e_secret, s_remote)?; // Process message pattern 1 e1 token. let i = x1.len(); - let (e1_secret, e1_public) = App::Kem::generate(rng.lock().unwrap().deref_mut()); + let (e1_secret, e1_public) = App::Kem::generate(rng.borrow_mut().deref_mut()); x1.extend(&e1_public); noise.encrypt_and_hash_in_place(to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, 0), i, &mut x1); // Process message pattern 1 payload. @@ -264,7 +265,7 @@ fn create_a1_state( let c = u64::from_be_bytes(x1[x1.len() - 8..].try_into().unwrap()); - x1.extend(&gen_null_response(rng.lock().unwrap().deref_mut())); + x1.extend(&gen_null_response(rng.borrow_mut().deref_mut())); Some(StateA1 { noise, e_secret, @@ -287,8 +288,8 @@ pub(crate) fn trans_to_a1( .map_err(|e| OpenError::StorageError(e))?; let RatchetStates { state1, state2 } = ratchet_states.unwrap_or_default(); - let mut session_map = ctx.session_map.lock().unwrap(); - let kid_recv = gen_kid(session_map.deref(), ctx.rng.lock().unwrap().deref_mut()); + let mut session_map = ctx.session_map.borrow_mut(); + let kid_recv = gen_kid(session_map.deref(), ctx.rng.borrow_mut().deref_mut()); let a1 = create_a1_state(&ctx.rng, &s_remote, kid_recv, &state1, state2.as_ref(), identity) .ok_or(OpenError::InvalidPublicKey)?; @@ -317,11 +318,10 @@ pub(crate) fn trans_to_a1( }; zeta.key_mut(true).recv.kid = Some(kid_recv); - let session = Arc::new(Session(Mutex::new(zeta))); + let session = Arc::new(Session(RefCell::new(zeta))); session_map.insert(kid_recv, Arc::downgrade(&session)); ctx.sessions - .lock() - .unwrap() + .borrow_mut() .insert(Arc::as_ptr(&session), Arc::downgrade(&session)); send(&packet); @@ -331,13 +331,13 @@ pub(crate) fn trans_to_a1( /// Corresponds to Algorithm 13 found in Section 5. pub(crate) fn respond_to_challenge( zeta: &mut Zeta, - rng: &Mutex, + rng: &RefCell, challenge: &[u8; CHALLENGE_SIZE], ) { if let ZetaAutomata::A1(StateA1 { packet: Packet(_, _, x1), .. }) = &mut zeta.beta { let response_start = x1.len() - CHALLENGE_SIZE; respond_to_challenge_in_place::( - rng.lock().unwrap().deref_mut(), + rng.borrow_mut().deref_mut(), challenge, (&mut x1[response_start..]).try_into().unwrap(), ); @@ -428,7 +428,7 @@ pub(crate) fn received_x1_trans( // Process message pattern 2 ekem1 token. let i = x2.len(); let (ekem1, ekem1_secret) = App::Kem::encapsulate( - ctx.rng.lock().unwrap().deref_mut(), + ctx.rng.borrow_mut().deref_mut(), (&x1[e1_start..e1_end]).try_into().unwrap(), ) .map(|(ct, secret)| (ct, Zeroizing::new(secret))) @@ -440,8 +440,8 @@ pub(crate) fn received_x1_trans( // Process message pattern 2 psk2 token. noise.mix_key_and_hash(ratchet_state.key.as_ref()); // Process message pattern 2 payload. - let session_map = ctx.session_map.lock().unwrap(); - let kid_recv = gen_kid(session_map.deref(), ctx.rng.lock().unwrap().deref_mut()); + let session_map = ctx.session_map.borrow_mut(); + let kid_recv = gen_kid(session_map.deref(), ctx.rng.borrow_mut().deref_mut()); let i = x2.len(); x2.extend(kid_recv.get().to_be_bytes()); @@ -454,7 +454,7 @@ pub(crate) fn received_x1_trans( c[7] = x2[i - 1]; let c = u64::from_be_bytes(c); - ctx.b2_map.lock().unwrap().insert( + ctx.b2_map.borrow_mut().insert( kid_recv, StateB2 { ratchet_state, @@ -643,7 +643,7 @@ fn send_control( if let Some((c, _)) = zeta.get_counter() { if let (Some(kek), Some(kid)) = (zeta.key_ref(false).send.kek.as_ref(), zeta.key_ref(false).send.kid) { let nonce = to_nonce(packet_type, c); - let tag = App::Aead::encrypt_in_place(kek, &nonce, None, &mut payload[HEADER_SIZE..]); + let tag = App::Aead::encrypt_in_place(kek, &nonce, None, &mut payload); payload.extend(tag); send(&Packet(kid.get(), nonce, payload), Some(&zeta.hk_send)); @@ -762,7 +762,7 @@ pub(crate) fn received_x3_trans( }; let current_time = app.time(); - let mut session_map = ctx.session_map.lock().unwrap(); + let mut session_map = ctx.session_map.borrow_mut(); use std::collections::hash_map::Entry::*; let entry = match session_map.entry(zeta.kid_recv) { // We could have issued the kid that we initially offered Alice to someone else @@ -770,7 +770,7 @@ pub(crate) fn received_x3_trans( Occupied(_) => return Err(byzantine_fault!(OutOfSequence, false)), Vacant(entry) => entry, }; - let session = Arc::new(Session(Mutex::new(Zeta { + let session = Arc::new(Session(RefCell::new(Zeta { ctx: Arc::downgrade(ctx), session_data, was_bob: true, @@ -790,8 +790,7 @@ pub(crate) fn received_x3_trans( }))); entry.insert(Arc::downgrade(&session)); ctx.sessions - .lock() - .unwrap() + .borrow_mut() .insert(Arc::as_ptr(&session), Arc::downgrade(&session)); send(&Packet(zeta.kid_send.get(), n, c1), Some(&zeta.hk_send)); @@ -810,7 +809,7 @@ pub(crate) fn received_x3_trans( pub(crate) fn received_c1_trans( zeta: &mut Zeta, app: &App, - rng: &Mutex, + rng: &RefCell, kid: NonZeroU32, n: [u8; AES_GCM_NONCE_SIZE], c1: Vec, @@ -868,11 +867,8 @@ pub(crate) fn received_c1_trans( zeta.ratchet_state2 = None; zeta.key_index ^= true; - zeta.timeout_timer = app.time() - + App::SETTINGS - .rekey_after_time - .saturating_sub(rng.lock().unwrap().next_u64() % App::SETTINGS.rekey_time_max_jitter) - as i64; + let r = rng.borrow_mut().next_u64() % App::SETTINGS.rekey_time_max_jitter; + zeta.timeout_timer = app.time() + App::SETTINGS.rekey_after_time.saturating_sub(r) as i64; zeta.resend_timer = i64::MAX; zeta.beta = ZetaAutomata::S2; } @@ -889,7 +885,7 @@ pub(crate) fn received_c1_trans( pub(crate) fn received_c2_trans( zeta: &mut Zeta, app: &App, - rng: &Mutex, + rng: &RefCell, kid: NonZeroU32, n: [u8; AES_GCM_NONCE_SIZE], c2: Vec, @@ -917,10 +913,8 @@ pub(crate) fn received_c2_trans( return Err(byzantine_fault!(ExpiredCounter, true)); } - zeta.timeout_timer = app.time() - + App::SETTINGS - .rekey_after_time - .saturating_sub(rng.lock().unwrap().next_u64() % App::SETTINGS.rekey_time_max_jitter) as i64; + let r = rng.borrow_mut().next_u64() % App::SETTINGS.rekey_time_max_jitter; + zeta.timeout_timer = app.time() + App::SETTINGS.rekey_after_time.saturating_sub(r) as i64; zeta.resend_timer = i64::MAX; zeta.beta = ZetaAutomata::S2; Ok(()) @@ -1000,16 +994,16 @@ pub(crate) fn service( fn remap( session: &Arc>, zeta: &Zeta, - rng: &Mutex, + rng: &RefCell, session_map: &SessionMap, ) -> NonZeroU32 { - let mut session_map = session_map.lock().unwrap(); + let mut session_map = session_map.borrow_mut(); let weak = if let Some(Some(weak)) = zeta.key_ref(true).recv.kid.as_ref().map(|kid| session_map.remove(kid)) { weak } else { Arc::downgrade(&session) }; - let new_kid_recv = gen_kid(session_map.deref(), rng.lock().unwrap().deref_mut()); + let new_kid_recv = gen_kid(session_map.deref(), rng.borrow_mut().deref_mut()); session_map.insert(new_kid_recv, weak); new_kid_recv } @@ -1114,7 +1108,7 @@ pub(crate) fn received_k1_trans( zeta: &mut Zeta, session: &Arc>, app: &App, - rng: &Mutex, + rng: &RefCell, session_map: &SessionMap, s_secret: &App::KeyPair, kid: NonZeroU32, @@ -1415,12 +1409,8 @@ pub(crate) fn received_payload_in_place( }; let i = payload.len() - AES_GCM_TAG_SIZE; - let specified_key = zeta - .key_ref(is_other) - .recv - .nk - .as_ref() - .ok_or(byzantine_fault!(OutOfSequence, true))?; + let specified_key = zeta.key_ref(is_other).recv.nk.as_ref(); + let specified_key = specified_key.ok_or(byzantine_fault!(OutOfSequence, true))?; let tag = payload[i..].try_into().unwrap(); if !App::Aead::decrypt_in_place(specified_key, &n, None, &mut payload[..i], &tag) { return Err(byzantine_fault!(FailedAuth, true)); @@ -1441,7 +1431,7 @@ impl Session { /// receive or send data or control packets. It is recommended to simply `drop` the session /// instead, but this can provide some reassurance in complex shared ownership situations. pub fn expire(&mut self) { - self.0.lock().unwrap().expire(); + self.0.borrow_mut().expire(); } } From f21498a047527340d205ce1e93fc8da8ad16adf7 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 8 Aug 2023 09:49:16 -0400 Subject: [PATCH 28/37] fixed imports --- examples/basic_test.rs | 2 +- src/context.rs | 4 ++-- src/crypto/mod.rs | 1 + src/proto.rs | 4 +--- src/ratchet_state.rs | 2 +- src/symmetric_state.rs | 4 ++-- src/zeta.rs | 7 +++---- 7 files changed, 11 insertions(+), 13 deletions(-) diff --git a/examples/basic_test.rs b/examples/basic_test.rs index 40997e1..3a52a65 100644 --- a/examples/basic_test.rs +++ b/examples/basic_test.rs @@ -372,7 +372,7 @@ fn core(time: u64, packet_success_rate: u32) { fn main() { let args = std::env::args(); let packet_success_rate = if args.len() <= 1 { - let default_success_rate = 0.5; + let default_success_rate = 1.0; ((u32::MAX as f64) * default_success_rate) as u32 } else { ((u32::MAX as f64) * f64::from_str(args.last().unwrap().as_str()).unwrap()) as u32 diff --git a/src/context.rs b/src/context.rs index fb9ff36..b9b3953 100644 --- a/src/context.rs +++ b/src/context.rs @@ -7,14 +7,14 @@ use std::num::NonZeroU32; use std::sync::{Arc, Weak}; use crate::application::ApplicationLayer; +use crate::challenge::ChallengeContext; use crate::crypto::{AES_256_KEY_SIZE, AES_GCM_NONCE_SIZE}; use crate::fragmentation::{send_with_fragmentation, DefragBuffer}; use crate::proto::*; -use crate::result::{byzantine_fault, ReceiveError, ReceiveOk, SendError, SessionEvent}; +use crate::result::{byzantine_fault, OpenError, ReceiveError, ReceiveOk, SendError, SessionEvent}; use crate::zeta::*; #[cfg(feature = "logging")] use crate::LogEvent::*; -use crate::{challenge::ChallengeContext, result::OpenError}; /// Macro to turn off logging at compile time. macro_rules! log { diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 00ca2aa..16279f0 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -13,6 +13,7 @@ pub use kyber1024::*; // We re-export our dependencies so it is less of a headache for the implementor to use the same // exact version of them. pub use rand_core; +pub use zeroize; /// Constant time byte slice equality. pub fn secure_eq + ?Sized, B: AsRef<[u8]> + ?Sized>(a: &A, b: &B) -> bool { diff --git a/src/proto.rs b/src/proto.rs index cd0b520..a31a402 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -1,6 +1,4 @@ -use crate::crypto::{ - AES_GCM_TAG_SIZE, KYBER_CIPHERTEXT_SIZE, KYBER_PUBLIC_KEY_SIZE, P384_PUBLIC_KEY_SIZE, SHA512_HASH_SIZE, -}; +use crate::crypto::*; /* Common constants */ diff --git a/src/ratchet_state.rs b/src/ratchet_state.rs index 75de1ee..7538aa7 100644 --- a/src/ratchet_state.rs +++ b/src/ratchet_state.rs @@ -1,6 +1,6 @@ use zeroize::Zeroizing; -use crate::crypto::{secure_eq, Sha512Hash}; +use crate::crypto::*; use crate::proto::*; /// A ratchet key and fingerprint, /// along with the length of the ratchet chain the keys were derived from. diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index deaab6e..5388e8a 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -3,8 +3,8 @@ use std::marker::PhantomData; use zeroize::Zeroizing; use crate::application::ApplicationLayer; -use crate::crypto::{AesGcmAead, Sha512Hash, AES_256_KEY_SIZE, AES_GCM_NONCE_SIZE, AES_GCM_TAG_SIZE}; -use crate::proto::{HASHLEN, LABEL_KBKDF_CHAIN}; +use crate::crypto::*; +use crate::proto::*; pub struct SymmetricState { k: Zeroizing<[u8; AES_256_KEY_SIZE]>, diff --git a/src/zeta.rs b/src/zeta.rs index ca69176..2c71257 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -1,19 +1,18 @@ -use rand_core::RngCore; use std::cell::RefCell; use std::collections::HashMap; use std::num::NonZeroU32; use std::ops::{Deref, DerefMut}; use std::sync::{Arc, Weak}; + +use rand_core::RngCore; use zeroize::Zeroizing; -use crate::application::ApplicationLayer; -use crate::application::RatchetUpdate; +use crate::application::{ApplicationLayer, RatchetState, RatchetStates, RatchetUpdate}; use crate::challenge::{gen_null_response, respond_to_challenge_in_place}; use crate::context::{log, ContextInner, SessionMap}; use crate::crypto::*; use crate::fragmentation::DefragBuffer; use crate::proto::*; -use crate::ratchet_state::{RatchetState, RatchetStates}; use crate::result::{byzantine_fault, FaultType, OpenError, ReceiveError, SendError}; use crate::symmetric_state::SymmetricState; #[cfg(feature = "logging")] From 3e6a341491597eb8d277f7a23bb143ac3772710a Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 8 Aug 2023 11:03:00 -0400 Subject: [PATCH 29/37] improved dependencies --- examples/basic_test.rs | 31 +++++++++++++++---------------- src/application.rs | 2 +- src/crypto_impl/aes_impl.rs | 8 ++++++-- src/crypto_impl/kyber1024.rs | 5 ++--- src/crypto_impl/mod.rs | 10 ++++++++-- src/crypto_impl/p384_impl.rs | 8 ++++++-- src/crypto_impl/sha512.rs | 4 +++- 7 files changed, 41 insertions(+), 27 deletions(-) diff --git a/examples/basic_test.rs b/examples/basic_test.rs index 3a52a65..623ee3c 100644 --- a/examples/basic_test.rs +++ b/examples/basic_test.rs @@ -14,17 +14,16 @@ use std::sync::{mpsc, Arc, Mutex}; use std::thread; use std::time::{Duration, Instant}; -use aes::Aes256; -use aes_gcm::Aes256Gcm; -use p384::{ecdh::EphemeralSecret, PublicKey}; use rand_core::OsRng; use rand_core::RngCore; -use sha2::Sha512; use zssp_proto::application::{ AcceptAction, ApplicationLayer, RatchetState, RatchetStates, RatchetUpdate, Settings, RATCHET_SIZE, }; -use zssp_proto::crypto_impl::PqcKyberSecretKey; +use zssp_proto::crypto::P384KeyPair; +use zssp_proto::crypto_impl::{ + Aes256Crate, AesGcmCrate, RustKyber1024PrivateKey, P384CrateKeyPair, P384CratePublicKey, Sha512Crate, +}; use zssp_proto::Session; const TEST_MTU: usize = 1500; @@ -57,12 +56,12 @@ impl ApplicationLayer for &TestApplication { fragment_assembly_timeout: Settings::FRAGMENT_ASSEMBLY_TIMEOUT_MS, }; type Rng = OsRng; - type Prp = Aes256; - type Aead = Aes256Gcm; - type Hash = Sha512; - type PublicKey = PublicKey; - type KeyPair = EphemeralSecret; - type Kem = PqcKyberSecretKey; + type Prp = Aes256Crate; + type Aead = AesGcmCrate; + type Hash = Sha512Crate; + type PublicKey = P384CratePublicKey; + type KeyPair = P384CrateKeyPair; + type Kem = RustKyber1024PrivateKey; type StorageError = std::convert::Infallible; type SessionData = u128; @@ -138,8 +137,8 @@ fn alice_main( alice_out: mpsc::SyncSender>, alice_in: mpsc::Receiver>, recursive_out: mpsc::SyncSender>, - alice_keypair: EphemeralSecret, - bob_pubkey: PublicKey, + alice_keypair: P384CrateKeyPair, + bob_pubkey: P384CratePublicKey, ) { let startup_time = std::time::Instant::now(); let mut context = zssp_proto::Context::<&TestApplication>::new(alice_keypair, OsRng); @@ -242,7 +241,7 @@ fn bob_main( bob_out: mpsc::SyncSender>, bob_in: mpsc::Receiver>, recursive_out: mpsc::SyncSender>, - bob_keypair: EphemeralSecret, + bob_keypair: P384CrateKeyPair, ) { let startup_time = std::time::Instant::now(); let mut context = zssp_proto::Context::<&TestApplication>::new(bob_keypair, OsRng); @@ -316,13 +315,13 @@ fn bob_main( fn core(time: u64, packet_success_rate: u32) { let run = &AtomicBool::new(true); - let alice_keypair = EphemeralSecret::random(&mut OsRng); + let alice_keypair = P384CrateKeyPair::generate(&mut OsRng); let alice_app = TestApplication { time: Instant::now(), name: "alice", ratchets: Mutex::new(Ratchets::new()), }; - let bob_keypair = EphemeralSecret::random(&mut OsRng); + let bob_keypair = P384CrateKeyPair::generate(&mut OsRng); let bob_pubkey = bob_keypair.public_key(); let bob_app = TestApplication { time: Instant::now(), diff --git a/src/application.rs b/src/application.rs index 5e97ba0..7a50314 100644 --- a/src/application.rs +++ b/src/application.rs @@ -109,7 +109,7 @@ pub trait ApplicationLayer: Sized { /// /// FIPS compliance requires a FIPS certified implementation. type Aead: AesGcmAead; - /// The implementation of SHA-512 that ZSSP should use. + /// The implementation of SHA-512 and HMAC-SHA-512 that ZSSP should use. /// We provide an optional implementation for this trait using the `sha2` crate. /// /// FIPS compliance requires use of a FIPS certified implementation. diff --git a/src/crypto_impl/aes_impl.rs b/src/crypto_impl/aes_impl.rs index 037430a..5160ef4 100644 --- a/src/crypto_impl/aes_impl.rs +++ b/src/crypto_impl/aes_impl.rs @@ -5,7 +5,9 @@ use aes_gcm::{Aes256Gcm, Key, Nonce, Tag}; use crate::crypto::*; -impl Aes256Prp for Aes256 { +/// The version and type of the aes crate that the `Aes256Prp` trait is implemented for. +pub type Aes256Crate = Aes256; +impl Aes256Prp for Aes256Crate { fn encrypt_in_place(key: &[u8; AES_256_KEY_SIZE], block: &mut [u8; AES_256_BLOCK_SIZE]) { let cipher = Aes256::new(GenericArray::from_slice(key)); cipher.encrypt_block(GenericArray::from_mut_slice(block)); @@ -17,7 +19,9 @@ impl Aes256Prp for Aes256 { } } -impl AesGcmAead for Aes256Gcm { +/// The version and type of the aes-gcm crate that the `Aes256Gcm` trait is implemented for. +pub type AesGcmCrate = Aes256Gcm; +impl AesGcmAead for AesGcmCrate { fn encrypt_in_place( key: &[u8; AES_256_KEY_SIZE], iv: &[u8; AES_GCM_NONCE_SIZE], diff --git a/src/crypto_impl/kyber1024.rs b/src/crypto_impl/kyber1024.rs index f5a518b..57cd5e7 100644 --- a/src/crypto_impl/kyber1024.rs +++ b/src/crypto_impl/kyber1024.rs @@ -5,9 +5,8 @@ use crate::crypto::*; /// A wrapper for a buffer the size of a pqc_kyber secret key. /// The crate `pqc_kyber` is low level and operates directly on buffers of bytes. -pub type PqcKyberSecretKey = Zeroizing<[u8; pqc_kyber::KYBER_SECRETKEYBYTES]>; - -impl Kyber1024PrivateKey for PqcKyberSecretKey { +pub type RustKyber1024PrivateKey = Zeroizing<[u8; pqc_kyber::KYBER_SECRETKEYBYTES]>; +impl Kyber1024PrivateKey for RustKyber1024PrivateKey { fn generate(rng: &mut Rng) -> (Self, [u8; KYBER_PUBLIC_KEY_SIZE]) { let keypair = pqc_kyber::keypair(rng); (Zeroizing::new(keypair.secret), keypair.public) diff --git a/src/crypto_impl/mod.rs b/src/crypto_impl/mod.rs index a08b8ef..ccbe469 100644 --- a/src/crypto_impl/mod.rs +++ b/src/crypto_impl/mod.rs @@ -1,11 +1,19 @@ #[cfg(feature = "aes-gcm")] mod aes_impl; +#[cfg(feature = "aes-gcm")] +pub use aes_impl::*; #[cfg(feature = "pqc_kyber")] mod kyber1024; +#[cfg(feature = "pqc_kyber")] +pub use kyber1024::*; #[cfg(feature = "p384")] mod p384_impl; +#[cfg(feature = "p384")] +pub use p384_impl::*; #[cfg(feature = "sha2")] mod sha512; +#[cfg(feature = "sha2")] +pub use sha512::*; #[cfg(feature = "aes")] pub use aes; @@ -13,8 +21,6 @@ pub use aes; pub use aes_gcm; #[cfg(feature = "hmac")] pub use hmac; -#[cfg(feature = "pqc_kyber")] -pub use kyber1024::PqcKyberSecretKey; #[cfg(feature = "p384")] pub use p384; #[cfg(feature = "pqc_kyber")] diff --git a/src/crypto_impl/p384_impl.rs b/src/crypto_impl/p384_impl.rs index 2898bc6..66cd470 100644 --- a/src/crypto_impl/p384_impl.rs +++ b/src/crypto_impl/p384_impl.rs @@ -3,7 +3,9 @@ use rand_core::{CryptoRng, RngCore}; use crate::crypto::*; -impl P384PublicKey for PublicKey { +/// The version and type of the p384 crate that the `P384PublicKey` trait is implemented for. +pub type P384CratePublicKey = PublicKey; +impl P384PublicKey for P384CratePublicKey { fn from_bytes(raw_key: &[u8; P384_PUBLIC_KEY_SIZE]) -> Option { PublicKey::from_sec1_bytes(raw_key).ok() } @@ -14,7 +16,9 @@ impl P384PublicKey for PublicKey { } } -impl P384KeyPair for EphemeralSecret { +/// The version and type of the p384 crate that the `P384KeyPair` trait is implemented for. +pub type P384CrateKeyPair = EphemeralSecret; +impl P384KeyPair for P384CrateKeyPair { type PublicKey = PublicKey; fn generate(rng: &mut Rng) -> Self { diff --git a/src/crypto_impl/sha512.rs b/src/crypto_impl/sha512.rs index 87c66d5..1d2996c 100644 --- a/src/crypto_impl/sha512.rs +++ b/src/crypto_impl/sha512.rs @@ -3,7 +3,9 @@ use sha2::{Digest, Sha512}; use crate::crypto::*; -impl Sha512Hash for Sha512 { +/// The version and type of the sha2 crate that the `Sha512Hash` trait is implemented for. +pub type Sha512Crate = Sha512; +impl Sha512Hash for Sha512Crate { fn new() -> Self { Digest::new() } From 7b45033cd5287ec235456e6afaf4bed3ff470cb4 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 8 Aug 2023 13:00:04 -0400 Subject: [PATCH 30/37] fixed size bug --- src/proto.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/proto.rs b/src/proto.rs index a31a402..6a08419 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -115,7 +115,7 @@ pub(crate) const PACKET_TYPE_USES_COUNTER_RANGE: std::ops::Range = 3..9; pub(crate) const HANDSHAKE_HELLO_MIN_SIZE: usize = KID_SIZE + P384_PUBLIC_KEY_SIZE + KYBER_PUBLIC_KEY_SIZE + AES_GCM_TAG_SIZE + AES_GCM_TAG_SIZE; -pub(crate) const HANDSHAKE_HELLO_MAX_SIZE: usize = HANDSHAKE_HELLO_MIN_SIZE + RATCHET_SIZE; +pub(crate) const HANDSHAKE_HELLO_MAX_SIZE: usize = HANDSHAKE_HELLO_MIN_SIZE + RATCHET_SIZE + RATCHET_SIZE; pub(crate) const HANDSHAKE_RESPONSE_SIZE: usize = P384_PUBLIC_KEY_SIZE + KYBER_CIPHERTEXT_SIZE + AES_GCM_TAG_SIZE + KID_SIZE + AES_GCM_TAG_SIZE; From c500fee8d5a82197ac26e3ead63ef733f03ad6be Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 8 Aug 2023 16:34:37 -0400 Subject: [PATCH 31/37] added warning enum --- examples/basic_test.rs | 8 +++----- src/context.rs | 16 ++++++++++++---- src/result.rs | 16 ++++++++++++++++ src/zeta.rs | 14 +++++++++----- 4 files changed, 40 insertions(+), 14 deletions(-) diff --git a/examples/basic_test.rs b/examples/basic_test.rs index 623ee3c..ad81be8 100644 --- a/examples/basic_test.rs +++ b/examples/basic_test.rs @@ -191,9 +191,8 @@ fn alice_main( assert!(!data.is_empty()); //println!("[alice] received {}", data.len()); } - NewSession => panic!(), - Rejected => panic!(), Control => (), + _ => panic!(), }, Err(e) => { println!("[alice] ERROR {:?}", e); @@ -270,7 +269,7 @@ fn bob_main( ) { Ok(Unassociated) => {} Ok(Session(s, event)) => match event { - NewSession => { + NewSession | NewDowngradedSession => { println!("[bob] new session, took {}s", current_time as f32 / 1000.0); let _ = bob_session.replace(s); } @@ -280,9 +279,8 @@ fn bob_main( transferred += data.len() as u64 * 2; // *2 because we are also sending this many bytes back context.send(&s, |b| bob_out.send(b).is_ok(), TEST_MTU, data).unwrap(); } - Established => panic!(), - Rejected => panic!(), Control => (), + _ => panic!(), }, Err(e) => { println!("[bob] ERROR {:?}", e); diff --git a/src/context.rs b/src/context.rs index b9b3953..5e01784 100644 --- a/src/context.rs +++ b/src/context.rs @@ -206,7 +206,7 @@ impl Context { } PACKET_TYPE_HANDSHAKE_RESPONSE => { log!(app, ReceivedRawX2); - received_x2_trans( + let should_warn_missing_ratchet = received_x2_trans( &mut zeta, &session, &app, @@ -217,7 +217,11 @@ impl Context { send_associated, )?; log!(app, X2IsAuthSentX3(&session)); - SessionEvent::Control + if should_warn_missing_ratchet { + SessionEvent::DowngradedRatchetKey + } else { + SessionEvent::Control + } } PACKET_TYPE_KEY_CONFIRM => { log!(app, ReceivedRawKeyConfirm); @@ -312,7 +316,7 @@ impl Context { if let Some((_, assembled_packet)) = result { log!(app, ReceivedRawX3); let zeta = entry.remove(); - let session = received_x3_trans( + let (session, should_warn_missing_ratchet) = received_x3_trans( zeta, &app, ctx, @@ -330,7 +334,11 @@ impl Context { }, )?; log!(app, X3IsAuthSentKeyConfirm(&session)); - Ok(ReceiveOk::Session(session, SessionEvent::NewSession)) + Ok(ReceiveOk::Session(session, if should_warn_missing_ratchet { + SessionEvent::NewDowngradedSession + } else { + SessionEvent::NewSession + })) } else { Ok(ReceiveOk::Unassociated) } diff --git a/src/result.rs b/src/result.rs index ec194cb..68f7884 100644 --- a/src/result.rs +++ b/src/result.rs @@ -142,6 +142,16 @@ pub enum SessionEvent { /// If the session Arc returned is dropped, the session with this peer will be immediately /// terminated. Save the session Arc to some long lived datastructure to keep it alive. NewSession, + /// The received packet was valid, and it contained the necessary keys to fully establish a new + /// session with Alice, the handshake initiator. + /// + /// However we had to downgrade our ratchet chain with this peer to a known state, + /// meaning either Alice corrupted their ratchet key storage, or someone is impersonating Alice. + /// The application should attempt to warn the user of this fact. + /// + /// If the session Arc returned is dropped, the session with this peer will be immediately + /// terminated. Save the session Arc to some long lived datastructure to keep it alive. + NewDowngradedSession, /// When Alice calls `Context::open`, a session will be created, but Bob will not yet have /// received this session. They will have to successfully complete a handshake first. /// @@ -162,4 +172,10 @@ pub enum SessionEvent { Data(Vec), /// The received packet was some authentic protocol control packet. No action needs to be taken. Control, + /// In the process of establishing a session with Bob, the responder, + /// we had to downgrade our ratchet chain with this peer to a known state. + /// This means either Bob corrupted their ratchet key storage, or someone is impersonating Alice. + /// The application should attempt to warn the user of this fact. + DowngradedRatchetKey, + } diff --git a/src/zeta.rs b/src/zeta.rs index 2c71257..19d3d96 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -482,7 +482,7 @@ pub(crate) fn received_x2_trans( n: [u8; AES_GCM_NONCE_SIZE], mut x2: Vec, send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), -) -> Result<(), ReceiveError> { +) -> Result> { use FaultType::*; // <- e, ee, ekem1, psk // -> s, se @@ -496,6 +496,7 @@ pub(crate) fn received_x2_trans( if c >= COUNTER_WINDOW_MAX_SKIP_AHEAD || &n[AES_GCM_NONCE_SIZE - 3..] != &x2[x2.len() - 3..] { return Err(byzantine_fault!(FailedAuth, true)); } + let mut should_warn_missing_ratchet = false; let result = (|| { if let ZetaAutomata::A1(StateA1 { noise, e_secret, e1_secret, identity, .. }) = &zeta.beta { let mut noise = noise.clone(); @@ -560,6 +561,7 @@ pub(crate) fn received_x2_trans( result = test_ratchet_key(&[0u8; RATCHET_SIZE]); if result.is_some() { // TODO: add some kind of warning callback or signal. + should_warn_missing_ratchet = true; } } @@ -631,7 +633,7 @@ pub(crate) fn received_x2_trans( Ok(packet) => send(packet, Some(&zeta.hk_send)), _ => {} } - result.map(|_| ()) + result.map(|_| should_warn_missing_ratchet) } fn send_control( zeta: &mut Zeta, @@ -662,7 +664,7 @@ pub(crate) fn received_x3_trans( kid: NonZeroU32, mut x3: Vec, send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), -) -> Result>, ReceiveError> { +) -> Result<(Arc>, bool), ReceiveError> { use FaultType::*; // -> s, se if !(HANDSHAKE_COMPLETION_MIN_SIZE..=HANDSHAKE_COMPLETION_MAX_SIZE).contains(&x3.len()) { @@ -720,9 +722,11 @@ pub(crate) fn received_x3_trans( match result { Ok(rss) => { let RatchetStates { state1, state2 } = rss.unwrap_or_default(); + let mut should_warn_missing_ratchet = false; + if (&zeta.ratchet_state != &state1) & (Some(&zeta.ratchet_state) != state2.as_ref()) { if !responder_disallows_downgrade && zeta.ratchet_state.fingerprint().is_none() { - // TODO: add some kind of warning callback or signal. + should_warn_missing_ratchet = true; } else { if !responder_silently_rejects { send(&create_reject(), Some(&zeta.hk_send)) @@ -793,7 +797,7 @@ pub(crate) fn received_x3_trans( .insert(Arc::as_ptr(&session), Arc::downgrade(&session)); send(&Packet(zeta.kid_send.get(), n, c1), Some(&zeta.hk_send)); - Ok(session) + Ok((session, should_warn_missing_ratchet)) } Err(e) => Err(ReceiveError::StorageError(e)), } From c964b0e1f59a4d9e53a04b9e678294c3fcc9a32d Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 8 Aug 2023 16:44:05 -0400 Subject: [PATCH 32/37] removed old comment --- src/zeta.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/zeta.rs b/src/zeta.rs index 19d3d96..cc4f10b 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -560,7 +560,6 @@ pub(crate) fn received_x2_trans( chain_len = 0; result = test_ratchet_key(&[0u8; RATCHET_SIZE]); if result.is_some() { - // TODO: add some kind of warning callback or signal. should_warn_missing_ratchet = true; } } From 8aad3746ed2fad37ca82c6a50c6d4c2859eb1466 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 8 Aug 2023 17:40:59 -0400 Subject: [PATCH 33/37] added docs --- examples/basic_test.rs | 2 +- src/ratchet_state.rs | 76 +++++++++++++++++++++++++++++++++++++++--- src/zeta.rs | 20 +++++------ 3 files changed, 83 insertions(+), 15 deletions(-) diff --git a/examples/basic_test.rs b/examples/basic_test.rs index ad81be8..7513eef 100644 --- a/examples/basic_test.rs +++ b/examples/basic_test.rs @@ -110,7 +110,7 @@ impl ApplicationLayer for &TestApplication { if let Some(rf) = update_data.added_fingerprint() { ratchets.rf_map.insert(*rf, update_data.state1.clone()); - println!("[{}] new ratchet #{}", self.name, update_data.state1.chain_len); + println!("[{}] new ratchet #{}", self.name, update_data.state1.chain_len()); } if let Some(rf) = update_data.deleted_fingerprint1() { ratchets.rf_map.remove(rf); diff --git a/src/ratchet_state.rs b/src/ratchet_state.rs index 7538aa7..b6bf51b 100644 --- a/src/ratchet_state.rs +++ b/src/ratchet_state.rs @@ -12,9 +12,9 @@ use crate::proto::*; /// Corresponds to the Ratchet Key and Ratchet Fingerprint described in Section 3. #[derive(Clone, Eq)] pub struct RatchetState { - pub key: Zeroizing<[u8; RATCHET_SIZE]>, - pub fingerprint: Option>, - pub chain_len: u64, + key: Zeroizing<[u8; RATCHET_SIZE]>, + fingerprint: Option>, + chain_len: u64, } impl PartialEq for RatchetState { fn eq(&self, other: &Self) -> bool { @@ -34,9 +34,14 @@ impl std::hash::Hash for RatchetState { } } impl RatchetState { + /// Creates a new ratchet state from the given ratchet key, ratchet fingerprint and chain length. pub fn new(key: Zeroizing<[u8; RATCHET_SIZE]>, fingerprint: Zeroizing<[u8; RATCHET_SIZE]>, chain_len: u64) -> Self { RatchetState { key, fingerprint: Some(fingerprint), chain_len } } + /// Creates a new ratchet state from the given ratchet key, ratchet fingerprint and chain length. + /// + /// The caller should make sure any copies of these values are deleted from memory once they are + /// no longer needed. pub fn new_raw(key: [u8; RATCHET_SIZE], fingerprint: [u8; RATCHET_SIZE], chain_len: u64) -> Self { RatchetState { key: Zeroizing::new(key), @@ -44,6 +49,28 @@ impl RatchetState { chain_len, } } + /// The ratchet key for this ratchet state. This is directly mixed into the master secret of a + /// session and so is very sensitive. All operations upon a ratchet key must be implemented + /// in constant time. The user should prefer to do nothing with the ratchet key besides copying + /// it to or from a storage device. + /// + /// If `fingerprint` returns `None` then this is the "empty" ratchet state and the key will be + /// all zeros. + pub fn key(&self) -> &[u8; RATCHET_SIZE] { + &self.key + } + /// Ratchet keys and fingerprints are "chained together", where each set is derived from the + /// previous set. + /// + /// This function outputs the total length of that chain, as in the total number of previous + /// ratchet states that this ratchet state was derived from. + pub fn chain_len(&self) -> u64 { + self.chain_len + } + /// Creates a new "empty" ratchet state, where the ratchet fingerprint is the + /// empty string, the ratchet key is all zeros, and the chain length is 0. + /// + /// This value is the default value of `RatchetState`. pub fn empty() -> Self { RatchetState { key: Zeroizing::new([0u8; RATCHET_SIZE]), @@ -51,6 +78,9 @@ impl RatchetState { chain_len: 0, } } + /// Creates a new ratchet state derived from a one-time-password. If both sides of a session use + /// the same one-time-password then they can use this ratchet state to connect with each other + /// for the first time. pub fn new_from_otp(otp: &[u8]) -> RatchetState { let mut buffer = Vec::new(); buffer.push(1); @@ -66,12 +96,25 @@ impl RatchetState { 1, ) } + /// Returns true if this is the "empty" ratchet state, where the ratchet fingerprint is the + /// empty string, the ratchet key is all zeros, and the chain length is 0. pub fn is_empty(&self) -> bool { self.fingerprint.is_none() } + /// Checks if the fingerprint of this ratchet state equals the fingerprint contained in argument + /// `rf`. Uses constant time equality. pub fn fingerprint_eq(&self, rf: &[u8; RATCHET_SIZE]) -> bool { self.fingerprint.as_ref().map_or(false, |rf0| secure_eq(rf0, rf)) } + /// The ratchet fingerprint for this ratchet state. + /// + /// If this returns `None` then the ratchet fingerprint is the empty string. + /// This is the "empty" ratchet state and the key will be all zeros. + /// + /// The ratchet fingerprint value is sensitive and should be hidden, + /// but the security of ZSSP can survive having this value leaked. + /// Operations on a ratchet fingerprint should be implemented in constant time, + /// but it is ok if they are not. pub fn fingerprint(&self) -> Option<&[u8; RATCHET_SIZE]> { self.fingerprint.as_deref() } @@ -82,23 +125,38 @@ impl Default for RatchetState { } } -/// A pair of ratchet states. +/// An ordered pair of two ratchet states. /// It is expected that an instance of this object will be saved to a storage device per-peer, /// and be restore-able via the `ApplicationLayer` trait. /// /// This corresponds to the possible values of abstract variables `rf` and `rk` found in Section 4.3. #[derive(Clone, PartialEq, Eq, Hash)] pub struct RatchetStates { + /// The first ratchet state from the pair. pub state1: RatchetState, + /// The second ratchet state from the pair. It can, and usually will be `None`. pub state2: Option, } impl RatchetStates { + /// Creates a new pair of ratchet states. The order of the arguments matters, and it should be + /// the same order that was originally given by an instance of the `RatchetUpdate` struct. pub fn new(state1: RatchetState, state2: Option) -> Self { Self { state1, state2 } } + /// Creates a new initial pair of ratchet states, where the first ratchet state is the empty + /// ratchet state and the second is `None`. + /// + /// This value is the default value of `RatchetStates`. pub fn new_initial_states() -> Self { Self { state1: RatchetState::empty(), state2: None } } + /// Creates a new initial pair of ratchet states from a one-time password. + /// The first ratchet state will be derived from this password, while the second will be `None`. + /// + /// If both sides of a session use the same one-time-password then they can use this pair to + /// connect with each other for the first time. This pair can be generated with this function, + /// saved to persistent storage, and eventually restored by the `ApplicationLayer` when we + /// attempt to form a session with the correct peer. pub fn new_otp_states(otp: &[u8]) -> Self { Self { state1: RatchetState::new_from_otp::(otp), @@ -135,9 +193,12 @@ pub struct RatchetUpdate<'a> { pub deleted_state2: Option<&'a RatchetState>, } impl<'a> RatchetUpdate<'a> { + /// Clones the `state1` and `state2` pair of ratchet states out of this struct and into a + /// `RatchetStates` instance. pub fn to_states(&self) -> RatchetStates { RatchetStates::new(self.state1.clone(), self.state2.cloned()) } + /// Returns the new ratchet fingerprint that was added during this update, if any. pub fn added_fingerprint(&self) -> Option<&[u8; RATCHET_SIZE]> { if self.state1_was_just_added { self.state1.fingerprint() @@ -145,6 +206,7 @@ impl<'a> RatchetUpdate<'a> { None } } + /// Returns the first ratchet fingerprint that was deleted during this update, if any. pub fn deleted_fingerprint1(&self) -> Option<&[u8; RATCHET_SIZE]> { if let Some(rs) = &self.deleted_state1 { rs.fingerprint() @@ -152,6 +214,12 @@ impl<'a> RatchetUpdate<'a> { None } } + /// Returns the second fingerprint that was deleted during this update, if there was more than + /// one ratchet state deleted during this update. + /// + /// It is extremely rare that this function ever returns `Some`, but it is possible. + /// Users should explicitly test that their implementation is able to correctly delete more than + /// 1 ratchet state at a time, because it is unlikely to occur under normal conditions. pub fn deleted_fingerprint2(&self) -> Option<&[u8; RATCHET_SIZE]> { if let Some(rs) = &self.deleted_state2 { rs.fingerprint() diff --git a/src/zeta.rs b/src/zeta.rs index cc4f10b..f986b6e 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -437,7 +437,7 @@ pub(crate) fn received_x1_trans( noise.mix_key(ekem1_secret.as_ref()); drop(ekem1_secret); // Process message pattern 2 psk2 token. - noise.mix_key_and_hash(ratchet_state.key.as_ref()); + noise.mix_key_and_hash(ratchet_state.key()); // Process message pattern 2 payload. let session_map = ctx.session_map.borrow_mut(); let kid_recv = gen_kid(session_map.deref(), ctx.rng.borrow_mut().deref_mut()); @@ -545,14 +545,14 @@ pub(crate) fn received_x2_trans( }; // Check first key. let mut ratchet_i = 1; - let mut chain_len = zeta.ratchet_state1.chain_len; - let mut result = test_ratchet_key(zeta.ratchet_state1.key.as_ref()); + let mut chain_len = zeta.ratchet_state1.chain_len(); + let mut result = test_ratchet_key(zeta.ratchet_state1.key()); // Check second key. if result.is_none() { ratchet_i = 2; if let Some(rs) = zeta.ratchet_state2.as_ref() { - chain_len = rs.chain_len; - result = test_ratchet_key(rs.key.as_ref()); + chain_len = rs.chain_len(); + result = test_ratchet_key(rs.key()); } } // Check zero key. @@ -736,7 +736,7 @@ pub(crate) fn received_x3_trans( let (rk, rf) = noise.get_ask(LABEL_RATCHET_STATE); // We must make sure the ratchet key is saved before we transition. - let new_ratchet_state = RatchetState::new(rk, rf, zeta.ratchet_state.chain_len + 1); + let new_ratchet_state = RatchetState::new(rk, rf, zeta.ratchet_state.chain_len() + 1); let result = app.save_ratchet_state( &s_remote, &session_data, @@ -1066,7 +1066,7 @@ fn timeout_trans( noise.mix_hash(&ctx.s_secret.public_key_bytes()); noise.mix_hash(&zeta.s_remote.to_bytes()); // Process message pattern 1 psk0 token. - noise.mix_key_and_hash(zeta.ratchet_state1.key.as_ref()); + noise.mix_key_and_hash(zeta.ratchet_state1.key()); // Process message pattern 1 e token. let e_secret = noise.write_e(&ctx.rng, &mut k1); // Process message pattern 1 es token. @@ -1165,7 +1165,7 @@ pub(crate) fn received_k1_trans( noise.mix_hash(&zeta.s_remote.to_bytes()); noise.mix_hash(&s_secret.public_key_bytes()); // Process message pattern 1 psk0 token. - noise.mix_key_and_hash(zeta.ratchet_state1.key.as_ref()); + noise.mix_key_and_hash(zeta.ratchet_state1.key()); // Process message pattern 1 e token. let e_remote = noise.read_e(&mut i, &k1).ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 1 es token. @@ -1204,7 +1204,7 @@ pub(crate) fn received_k1_trans( noise.encrypt_and_hash_in_place(to_nonce(PACKET_TYPE_REKEY_COMPLETE, 0), i, &mut k2); let (rk, rf) = noise.get_ask(LABEL_RATCHET_STATE); - let new_ratchet_state = RatchetState::new(rk, rf, zeta.ratchet_state1.chain_len + 1); + let new_ratchet_state = RatchetState::new(rk, rf, zeta.ratchet_state1.chain_len() + 1); let result = app.save_ratchet_state( &zeta.s_remote, &zeta.session_data, @@ -1308,7 +1308,7 @@ pub(crate) fn received_k2_trans( .ok_or(byzantine_fault!(InvalidPacket, true))?; let (rk, rf) = noise.get_ask(LABEL_RATCHET_STATE); - let new_ratchet_state = RatchetState::new(rk, rf, zeta.ratchet_state1.chain_len + 1); + let new_ratchet_state = RatchetState::new(rk, rf, zeta.ratchet_state1.chain_len() + 1); let result = app.save_ratchet_state( &zeta.s_remote, &zeta.session_data, From 755860ff995cb01fee044592ce597178911294d0 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 8 Aug 2023 17:53:59 -0400 Subject: [PATCH 34/37] removed unused error --- src/result.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/result.rs b/src/result.rs index 68f7884..88db809 100644 --- a/src/result.rs +++ b/src/result.rs @@ -20,9 +20,6 @@ pub enum OpenError { /// Depending on the error type trying again may not work. #[derive(Debug, PartialEq, Eq, Clone, Hash)] pub enum SendError { - /// An invalid parameter was supplied to the function. - InvalidParameter, - /// The session has been marked as expired and refuses to send data. /// Several components of ZSSP can cause this to occur, but the most likely situation to be seen /// in practice is where rekeying repeatedly fails due to exceedingly bad network conditions. From fa309c0e97eb4434e4c50e7f583f55c97b8d9357 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 9 Aug 2023 09:04:18 -0400 Subject: [PATCH 35/37] added docs --- src/context.rs | 7 +++-- src/lib.rs | 27 +++++++++++++++- src/log_event.rs | 1 + src/result.rs | 1 - src/zeta.rs | 81 ++++++++++++++++++++++++------------------------ 5 files changed, 72 insertions(+), 45 deletions(-) diff --git a/src/context.rs b/src/context.rs index 5e01784..ea98d20 100644 --- a/src/context.rs +++ b/src/context.rs @@ -443,7 +443,9 @@ impl Context { } } - /// Send data over the session. + /// Send data as fragments over the session to the remote peer. + /// + /// The session used here must have been created from this instance of a ZSSP context. /// /// * `session` - The session to send to /// * `send` - Function to call to send physical packet(s); the buffer passed to `send` is a @@ -457,6 +459,7 @@ impl Context { mut mtu: usize, payload: Vec, ) -> Result<(), SendError> { + debug_assert_eq!(session.0.borrow().ctx.as_ptr(), Arc::as_ptr(&self.0)); mtu = mtu.max(MIN_TRANSPORT_MTU); let mut zeta = session.0.borrow_mut(); send_payload(&mut zeta, payload, |Packet(kid, nonce, payload), hk| { @@ -468,7 +471,7 @@ impl Context { /// /// This returns the number of milliseconds until it should be called again. The caller should /// try to satisfy this but small variations in timing of up to a few seconds are not - /// a problem. + /// a problem. It is completely fine to call this function more often than the returned interval. /// /// * `send_to` - Function to get a sender and an MTU to send something over an active session pub fn service) -> bool>( diff --git a/src/lib.rs b/src/lib.rs index f871edf..9519d31 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,8 +5,33 @@ * (c) ZeroTier, Inc. * https://www.zerotier.com/ */ +//! ZeroTier Secure Sessions Protocol +//! ====== +//! +//! ## Introduction +//! +//! An in-depth guide to the full protocol specification can be found in the [protocol whitepaper](./whitepaper.pdf) provided in this repo. This implementation references it heavily. +//! +//! ZeroTier Secure Socket Protocol (ZSSP) is a [Noise](http://noiseprotocol.org) protocol implementation using NIST/FIPS/CfSC compliant cryptographic primitives plus post-quantum forward secrecy via [Kyber1024](https://pq-crystals.org/kyber/). It also includes built-in support for fragmentation and defragmentation of large messages with strong resistance against denial of service attacks targeted against the fragmentation protocol. +//! +//! Specifically ZSSP implements the [Noise XK](http://noiseprotocol.org/noise.html#interactive-handshake-patterns-fundamental) interactive handshake pattern which provides strong forward secrecy not only for data but for the identities of the two participants in the session. The XK pattern was chosen instead of the more popular IK pattern used in popular Noise implementations like Wireguard due to ZeroTier identities being long lived and potentially tied to the real world identity of the user. As a result a Noise pattern providing identity forward secrecy was considered preferable as it offers some level of deniability for recorded traffic even after secret key compromise. +//! +//! Hybrid post-quantum forward secrecy using Kyber1024 is performed alongside Noise with the result being mixed in alongside an optional pre-shared key at the end of session negotiation. +//! +//! ZSSP is designed for use in ZeroTier but is payload-agnostic and could easily be adapted for use in other projects. +//! +//! Further information can be found in the ZSSP whitepaper [protocol whitepaper](./whitepaper.pdf). +//! +//! ## Cryptographic Primitives Used +//! +//! - **NIST P-384 ECDH**: Elliptic curve key exchange during initial handshake and for periodic re-keying during the session +//! - **Kyber1024**: Quantum attack resistant lattice-based key exchange during initial handshake +//! - **SHA-512**: Used to construct KBKDF, also used in a proof of work and IP ownership DOS mitigation scheme +//! - **KBKDF**: Key mixing, sub-key derivation +//! - **AES-256**: 128-bit PRP for AES-256-GCM and for authenticated encryption of header to harden fragmentation against DOS (see section on header protection) +//! - **AES-256-GCM**: Authenticated encryption #![warn(missing_docs, rust_2018_idioms)] -//! TODO: docs + mod challenge; mod context; mod fragmentation; diff --git a/src/log_event.rs b/src/log_event.rs index 69b4826..3cb3948 100644 --- a/src/log_event.rs +++ b/src/log_event.rs @@ -4,6 +4,7 @@ use crate::application::ApplicationLayer; use crate::Session; /// ZSSP events that might be interesting to log or aggregate into metrics. +#[allow(missing_docs)] pub enum LogEvent<'a, App: ApplicationLayer> { ResentX1(&'a Arc>), TimeoutX1(&'a Arc>), diff --git a/src/result.rs b/src/result.rs index 88db809..a3abb60 100644 --- a/src/result.rs +++ b/src/result.rs @@ -174,5 +174,4 @@ pub enum SessionEvent { /// This means either Bob corrupted their ratchet key storage, or someone is impersonating Alice. /// The application should attempt to warn the user of this fact. DowngradedRatchetKey, - } diff --git a/src/zeta.rs b/src/zeta.rs index f986b6e..47836fc 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -18,32 +18,9 @@ use crate::symmetric_state::SymmetricState; #[cfg(feature = "logging")] use crate::LogEvent::*; -/// Create a 96-bit AES-GCM nonce. -/// -/// The primary information that we want to be contained here is the counter and the -/// packet type. The former makes this unique and the latter's inclusion authenticates -/// it as effectively AAD. Other elements of the header are either not authenticated, -/// like fragmentation info, or their authentication is implied via key exchange like -/// the key id. -/// -/// Corresponds to Figure 10 found in Section 4.3. -pub(crate) fn to_nonce(packet_type: u8, counter: u64) -> [u8; AES_GCM_NONCE_SIZE] { - let mut ret = [0u8; AES_GCM_NONCE_SIZE]; - ret[3] = packet_type; - // Noise requires a big endian counter at the end of the Nonce - ret[4..].copy_from_slice(&counter.to_be_bytes()); - ret -} -/// Corresponds to Figure 10 and Figure 14 found in Section 4.3. -pub(crate) fn from_nonce(n: &[u8]) -> (u8, u64) { - assert!(n.len() >= PACKET_NONCE_SIZE); - let c_start = n.len() - 8; - (n[c_start - 1], u64::from_be_bytes(n[c_start..].try_into().unwrap())) -} - /// Corresponds to the Zeta State Machine found in Section 4.1. pub(crate) struct Zeta { - ctx: Weak>, + pub ctx: Weak>, /// An arbitrary application defined object associated with each session. pub session_data: App::SessionData, /// Is true if the local peer acted as Bob, the responder in the initial key exchange. @@ -156,6 +133,28 @@ impl SymmetricState { } } +/// Create a 96-bit AES-GCM nonce. +/// +/// The primary information that we want to be contained here is the counter and the +/// packet type. The former makes this unique and the latter's inclusion authenticates +/// it as effectively AAD. Other elements of the header are either not authenticated, +/// like fragmentation info, or their authentication is implied via key exchange like +/// the key id. +/// +/// Corresponds to Figure 10 found in Section 4.3. +pub(crate) fn to_nonce(packet_type: u8, counter: u64) -> [u8; AES_GCM_NONCE_SIZE] { + let mut ret = [0u8; AES_GCM_NONCE_SIZE]; + ret[3] = packet_type; + // Noise requires a big endian counter at the end of the Nonce + ret[4..].copy_from_slice(&counter.to_be_bytes()); + ret +} +/// Corresponds to Figure 10 and Figure 14 found in Section 4.3. +pub(crate) fn from_nonce(n: &[u8]) -> (u8, u64) { + assert!(n.len() >= PACKET_NONCE_SIZE); + let c_start = n.len() - 8; + (n[c_start - 1], u64::from_be_bytes(n[c_start..].try_into().unwrap())) +} /// Generate a random local key id that is currently unused. fn gen_kid(session_map: &HashMap, rng: &mut impl RngCore) -> NonZeroU32 { loop { @@ -166,6 +165,22 @@ fn gen_kid(session_map: &HashMap, rng: &mut impl RngCore) -> N } } } +fn remap( + session: &Arc>, + zeta: &Zeta, + rng: &RefCell, + session_map: &SessionMap, +) -> NonZeroU32 { + let mut session_map = session_map.borrow_mut(); + let weak = if let Some(Some(weak)) = zeta.key_ref(true).recv.kid.as_ref().map(|kid| session_map.remove(kid)) { + weak + } else { + Arc::downgrade(&session) + }; + let new_kid_recv = gen_kid(session_map.deref(), rng.borrow_mut().deref_mut()); + session_map.insert(new_kid_recv, weak); + new_kid_recv +} impl Zeta { pub(crate) fn check_counter_window(&self, c: u64) -> bool { @@ -447,7 +462,7 @@ pub(crate) fn received_x1_trans( noise.encrypt_and_hash_in_place(to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), i, &mut x2); let i = x2.len(); - let mut c = 0u64.to_be_bytes(); + let mut c = [0u8; 8]; c[5] = x2[i - 3]; c[6] = x2[i - 2]; c[7] = x2[i - 1]; @@ -993,22 +1008,6 @@ pub(crate) fn service( send_control(zeta, p, control_payload, send); } } -fn remap( - session: &Arc>, - zeta: &Zeta, - rng: &RefCell, - session_map: &SessionMap, -) -> NonZeroU32 { - let mut session_map = session_map.borrow_mut(); - let weak = if let Some(Some(weak)) = zeta.key_ref(true).recv.kid.as_ref().map(|kid| session_map.remove(kid)) { - weak - } else { - Arc::downgrade(&session) - }; - let new_kid_recv = gen_kid(session_map.deref(), rng.borrow_mut().deref_mut()); - session_map.insert(new_kid_recv, weak); - new_kid_recv -} /// Corresponds to the timeout timer Transition Algorithm described in Section 4.1 - Definition 3. fn timeout_trans( zeta: &mut Zeta, From 1e87d1ba756561a28010fad757d54749f987194d Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 9 Aug 2023 09:51:52 -0400 Subject: [PATCH 36/37] stable version --- src/application.rs | 16 ++++++++-------- src/context.rs | 22 +++++++++++----------- src/zeta.rs | 25 ++++++++++++++----------- 3 files changed, 33 insertions(+), 30 deletions(-) diff --git a/src/application.rs b/src/application.rs index 7a50314..03f814c 100644 --- a/src/application.rs +++ b/src/application.rs @@ -142,7 +142,7 @@ pub trait ApplicationLayer: Sized { /// with remote peers (although both of these properties would help reliability slightly). /// Used to determine if any current handshakes should be resent or timed-out, or if a session /// should rekey. - fn time(&self) -> i64; + fn time(&mut self) -> i64; /// This function will be called whenever Alice's initial Hello packet contains the empty ratchet /// fingerprint. Brand new peers will always connect to Bob with the empty ratchet, but from @@ -153,7 +153,7 @@ pub trait ApplicationLayer: Sized { /// If this function is configured to always return true, it means peers will not be able to /// connect to us unless they had a prior-established ratchet key with us. This is the best way /// for the paranoid to enforce a manual allow-list. - fn hello_requires_recognized_ratchet(&self) -> bool; + fn hello_requires_recognized_ratchet(&mut self) -> bool; /// This function is called if we, as Alice, attempted to open a session with Bob using a /// non-empty ratchet key, but Bob does not have this ratchet key and wants to downgrade /// to the zero ratchet key. @@ -169,14 +169,14 @@ pub trait ApplicationLayer: Sized { /// least one party is misconfigured and got their ratchet keys corrupted or lost, or Bob has /// been compromised and is being impersonated. An attacker must at least have Bob's private /// static key to be able to ask Alice to downgrade. - fn initiator_disallows_downgrade(&self, session: &Arc>) -> bool; + fn initiator_disallows_downgrade(&mut self, session: &Arc>) -> bool; /// Function to accept sessions after final negotiation. /// The second argument is the identity that the remote peer sent us. The application /// must verify this identity is associated with the remote peer's static key. /// To prevent desync, if this function specifies that we should connect, no other open session /// with the same remote peer must exist. Drop or call expire on any pre-existing sessions /// before returning. - fn check_accept_session(&self, remote_static_key: &Self::PublicKey, identity: &[u8]) -> AcceptAction; + fn check_accept_session(&mut self, remote_static_key: &Self::PublicKey, identity: &[u8]) -> AcceptAction; /// Lookup a specific ratchet state based on its ratchet fingerprint. /// This function will be called whenever Alice attempts to connect to us with a non-empty @@ -185,7 +185,7 @@ pub trait ApplicationLayer: Sized { /// If a ratchet state with a matching fingerprint could not be found, this function should /// return `Ok(None)`. fn restore_by_fingerprint( - &self, + &mut self, ratchet_fingerprint: &[u8; RATCHET_SIZE], ) -> Result, Self::StorageError>; /// Lookup the specific ratchet states based on the identity of the peer being communicated with. @@ -203,7 +203,7 @@ pub trait ApplicationLayer: Sized { /// Filtering peers should be done by the caller to `Context::open` as well as by the /// function `ApplicationLayer::check_accept_session`. fn restore_by_identity( - &self, + &mut self, remote_static_key: &Self::PublicKey, session_data: &Self::SessionData, ) -> Result, Self::StorageError>; @@ -224,7 +224,7 @@ pub trait ApplicationLayer: Sized { /// to us will have to allow downgrade across the board. /// Otherwise, when we restart, we will not be allowed to reconnect. fn save_ratchet_state( - &self, + &mut self, remote_static_key: &Self::PublicKey, session_data: &Self::SessionData, update_data: RatchetUpdate<'_>, @@ -234,7 +234,7 @@ pub trait ApplicationLayer: Sized { /// These are provided for debugging, logging or metrics purposes, and must be used for /// nothing else. Do not base protocol-level decisions upon the events passed to this function. #[cfg(feature = "logging")] - fn event_log(&self, event: LogEvent<'_, Self>); + fn event_log(&mut self, event: LogEvent<'_, Self>); } /// A collection of fields specifying how to complete the key exchange with a specific remote peer, diff --git a/src/context.rs b/src/context.rs index ea98d20..837b856 100644 --- a/src/context.rs +++ b/src/context.rs @@ -130,9 +130,9 @@ impl Context { /// * `send_to` - Function to get senders for existing sessions, permitting MTU and path lookup /// * `remote_address` - Whatever the remote address is, as long as you can Hash it /// * `raw_fragment` - Buffer containing incoming wire packet - pub fn receive<'a, SendFn: FnMut(Vec) -> bool>( + pub fn receive) -> bool>( &mut self, - app: App, + mut app: App, send_unassociated_reply: impl FnMut(Vec) -> bool, mut send_unassociated_mtu: usize, send_to: impl FnOnce(&Arc>) -> Option<(SendFn, usize)>, @@ -209,7 +209,7 @@ impl Context { let should_warn_missing_ratchet = received_x2_trans( &mut zeta, &session, - &app, + &mut app, &ctx, kid_recv, to_aes_nonce(&pn), @@ -227,7 +227,7 @@ impl Context { log!(app, ReceivedRawKeyConfirm); let result = received_c1_trans( &mut zeta, - &app, + &mut app, &ctx.rng, kid_recv, to_aes_nonce(&pn), @@ -245,7 +245,7 @@ impl Context { log!(app, ReceivedRawAck); received_c2_trans( &mut zeta, - &app, + &mut app, &ctx.rng, kid_recv, to_aes_nonce(&pn), @@ -259,7 +259,7 @@ impl Context { received_k1_trans( &mut zeta, &session, - &app, + &mut app, &ctx.rng, &ctx.session_map, &ctx.s_secret, @@ -275,7 +275,7 @@ impl Context { log!(app, ReceivedRawK2); received_k2_trans( &mut zeta, - &app, + &mut app, kid_recv, to_aes_nonce(&pn), assembled_packet, @@ -318,7 +318,7 @@ impl Context { let zeta = entry.remove(); let (session, should_warn_missing_ratchet) = received_x3_trans( zeta, - &app, + &mut app, ctx, kid_recv, assembled_packet, @@ -396,7 +396,7 @@ impl Context { // Process recv zeta layer. received_x1_trans( - &app, + &mut app, &ctx, to_aes_nonce(&n), assembled_packet, @@ -476,7 +476,7 @@ impl Context { /// * `send_to` - Function to get a sender and an MTU to send something over an active session pub fn service) -> bool>( &mut self, - app: App, + mut app: App, mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, ) -> i64 { let ctx = &self.0; @@ -490,7 +490,7 @@ impl Context { &mut zeta, &session, ctx, - &app, + &mut app, current_time, |Packet(kid, nonce, payload): &Packet, hk| { if let Some((send_fragment, mut mtu)) = send_to(&session) { diff --git a/src/zeta.rs b/src/zeta.rs index 47836fc..abad602 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -290,7 +290,7 @@ fn create_a1_state( } /// Corresponds to Transition Algorithm 1 found in Section 4.3. pub(crate) fn trans_to_a1( - app: App, + mut app: App, ctx: &Arc>, s_remote: App::PublicKey, session_data: App::SessionData, @@ -359,7 +359,7 @@ pub(crate) fn respond_to_challenge( } /// Corresponds to Transition Algorithm 2 found in Section 4.3. pub(crate) fn received_x1_trans( - app: &App, + app: &mut App, ctx: &ContextInner, n: [u8; AES_GCM_NONCE_SIZE], mut x1: Vec, @@ -491,7 +491,7 @@ pub(crate) fn received_x1_trans( pub(crate) fn received_x2_trans( zeta: &mut Zeta, session: &Arc>, - app: &App, + app: &mut App, ctx: &Arc>, kid: NonZeroU32, n: [u8; AES_GCM_NONCE_SIZE], @@ -643,7 +643,10 @@ pub(crate) fn received_x2_trans( } })(); match &result { - Err(ReceiveError::ByzantineFault { .. }) => timeout_trans(zeta, session, app, ctx, app.time(), send), + Err(ReceiveError::ByzantineFault { .. }) => { + let current_time = app.time(); + timeout_trans(zeta, session, app, ctx, current_time, send); + } Ok(packet) => send(packet, Some(&zeta.hk_send)), _ => {} } @@ -673,7 +676,7 @@ fn send_control( /// Corresponds to Transition Algorithm 4 found in Section 4.3. pub(crate) fn received_x3_trans( zeta: StateB2, - app: &App, + app: &mut App, ctx: &Arc>, kid: NonZeroU32, mut x3: Vec, @@ -825,7 +828,7 @@ pub(crate) fn received_x3_trans( /// Corresponds to Transition Algorithm 5 found in Section 4.3. pub(crate) fn received_c1_trans( zeta: &mut Zeta, - app: &App, + app: &mut App, rng: &RefCell, kid: NonZeroU32, n: [u8; AES_GCM_NONCE_SIZE], @@ -901,7 +904,7 @@ pub(crate) fn received_c1_trans( /// Section 4.3. pub(crate) fn received_c2_trans( zeta: &mut Zeta, - app: &App, + app: &mut App, rng: &RefCell, kid: NonZeroU32, n: [u8; AES_GCM_NONCE_SIZE], @@ -970,7 +973,7 @@ pub(crate) fn service( zeta: &mut Zeta, session: &Arc>, ctx: &Arc>, - app: &App, + app: &mut App, current_time: i64, send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), ) { @@ -1012,7 +1015,7 @@ pub(crate) fn service( fn timeout_trans( zeta: &mut Zeta, session: &Arc>, - app: &App, + app: &mut App, ctx: &Arc>, current_time: i64, send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), @@ -1108,7 +1111,7 @@ fn timeout_trans( pub(crate) fn received_k1_trans( zeta: &mut Zeta, session: &Arc>, - app: &App, + app: &mut App, rng: &RefCell, session_map: &SessionMap, s_secret: &App::KeyPair, @@ -1246,7 +1249,7 @@ pub(crate) fn received_k1_trans( /// Corresponds to Transition Algorithm 8 found in Section 4.3. pub(crate) fn received_k2_trans( zeta: &mut Zeta, - app: &App, + app: &mut App, kid: NonZeroU32, n: [u8; AES_GCM_NONCE_SIZE], mut k2: Vec, From 6b1fa53e1c22fa6501ee20d5f3db2d10a826e359 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 9 Aug 2023 10:45:56 -0400 Subject: [PATCH 37/37] added the ability to mutate app --- examples/basic_test.rs | 88 ++++++++-------- src/application.rs | 39 ++++--- src/context.rs | 78 +++++++------- src/crypto_impl/mod.rs | 35 +++++++ src/fragmentation.rs | 12 +-- src/log_event.rs | 44 ++++---- src/result.rs | 6 +- src/symmetric_state.rs | 28 ++--- src/zeta.rs | 228 ++++++++++++++++++++--------------------- 9 files changed, 300 insertions(+), 258 deletions(-) diff --git a/examples/basic_test.rs b/examples/basic_test.rs index 7513eef..87335a1 100644 --- a/examples/basic_test.rs +++ b/examples/basic_test.rs @@ -7,10 +7,11 @@ */ use std::collections::HashMap; +use std::convert::Infallible; use std::iter::ExactSizeIterator; use std::str::FromStr; use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{mpsc, Arc, Mutex}; +use std::sync::{mpsc, Arc}; use std::thread; use std::time::{Duration, Instant}; @@ -18,7 +19,7 @@ use rand_core::OsRng; use rand_core::RngCore; use zssp_proto::application::{ - AcceptAction, ApplicationLayer, RatchetState, RatchetStates, RatchetUpdate, Settings, RATCHET_SIZE, + AcceptAction, ApplicationLayer, RatchetState, RatchetStates, RatchetUpdate, Settings, RATCHET_SIZE, CryptoLayer, }; use zssp_proto::crypto::P384KeyPair; use zssp_proto::crypto_impl::{ @@ -31,7 +32,7 @@ const TEST_MTU: usize = 1500; struct TestApplication { time: Instant, name: &'static str, - ratchets: Mutex, + ratchets: Ratchets, } struct Ratchets { @@ -44,8 +45,7 @@ impl Ratchets { } } -#[allow(unused)] -impl ApplicationLayer for &TestApplication { +impl CryptoLayer for TestApplication { const SETTINGS: Settings = Settings { initial_offer_timeout: Settings::INITIAL_OFFER_TIMEOUT_MS, rekey_timeout: 60 * 1000, @@ -63,18 +63,21 @@ impl ApplicationLayer for &TestApplication { type KeyPair = P384CrateKeyPair; type Kem = RustKyber1024PrivateKey; - type StorageError = std::convert::Infallible; type SessionData = u128; - - fn hello_requires_recognized_ratchet(&self) -> bool { +} +#[allow(unused)] +impl ApplicationLayer for &mut TestApplication { + type Crypto = TestApplication; + type StorageError = Infallible; + fn hello_requires_recognized_ratchet(&mut self) -> bool { false } - fn initiator_disallows_downgrade(&self, session: &Arc>) -> bool { + fn initiator_disallows_downgrade(&mut self, session: &Arc>) -> bool { true } - fn check_accept_session(&self, remote_static_key: &Self::PublicKey, identity: &[u8]) -> AcceptAction { + fn check_accept_session(&mut self, remote_static_key: &P384CratePublicKey, identity: &[u8]) -> AcceptAction { AcceptAction { session_data: Some(1), responder_disallows_downgrade: true, @@ -83,49 +86,46 @@ impl ApplicationLayer for &TestApplication { } fn restore_by_fingerprint( - &self, + &mut self, ratchet_fingerprint: &[u8; RATCHET_SIZE], - ) -> Result, Self::StorageError> { - let ratchets = self.ratchets.lock().unwrap(); - Ok(ratchets.rf_map.get(ratchet_fingerprint).cloned()) + ) -> Result, Infallible> { + Ok(self.ratchets.rf_map.get(ratchet_fingerprint).cloned()) } fn restore_by_identity( - &self, - remote_static_key: &Self::PublicKey, - session_data: &Self::SessionData, - ) -> Result, Self::StorageError> { - let ratchets = self.ratchets.lock().unwrap(); - Ok(ratchets.peer_map.get(session_data).cloned()) + &mut self, + remote_static_key: &P384CratePublicKey, + session_data: &u128, + ) -> Result, Infallible> { + Ok(self.ratchets.peer_map.get(session_data).cloned()) } fn save_ratchet_state( - &self, - remote_static_key: &Self::PublicKey, - session_data: &Self::SessionData, + &mut self, + remote_static_key: &P384CratePublicKey, + session_data: &u128, update_data: RatchetUpdate<'_>, - ) -> Result<(), Self::StorageError> { - let mut ratchets = self.ratchets.lock().unwrap(); - ratchets.peer_map.insert(*session_data, update_data.to_states()); + ) -> Result<(), Infallible> { + self.ratchets.peer_map.insert(*session_data, update_data.to_states()); if let Some(rf) = update_data.added_fingerprint() { - ratchets.rf_map.insert(*rf, update_data.state1.clone()); + self.ratchets.rf_map.insert(*rf, update_data.state1.clone()); println!("[{}] new ratchet #{}", self.name, update_data.state1.chain_len()); } if let Some(rf) = update_data.deleted_fingerprint1() { - ratchets.rf_map.remove(rf); + self.ratchets.rf_map.remove(rf); } if let Some(rf) = update_data.deleted_fingerprint2() { - ratchets.rf_map.remove(rf); + self.ratchets.rf_map.remove(rf); } Ok(()) } - fn time(&self) -> i64 { + fn time(&mut self) -> i64 { self.time.elapsed().as_millis() as i64 } - fn event_log(&self, event: zssp_proto::LogEvent) { + fn event_log(&mut self, event: zssp_proto::LogEvent) { println!(">[{}] {:?}", self.name, event); } } @@ -133,7 +133,7 @@ impl ApplicationLayer for &TestApplication { fn alice_main( run: &AtomicBool, packet_success_rate: u32, - alice_app: &TestApplication, + mut alice_app: TestApplication, alice_out: mpsc::SyncSender>, alice_in: mpsc::Receiver>, recursive_out: mpsc::SyncSender>, @@ -141,7 +141,7 @@ fn alice_main( bob_pubkey: P384CratePublicKey, ) { let startup_time = std::time::Instant::now(); - let mut context = zssp_proto::Context::<&TestApplication>::new(alice_keypair, OsRng); + let mut context = zssp_proto::Context::::new(alice_keypair, OsRng); let mut next_service = startup_time.elapsed().as_millis() as i64 + 500; let test_data = [1u8; TEST_MTU * 10]; let mut up = false; @@ -153,7 +153,7 @@ fn alice_main( alice_session = Some( context .open( - alice_app, + &mut alice_app, |b| alice_out.send(b).is_ok(), TEST_MTU, bob_pubkey.clone(), @@ -173,7 +173,7 @@ fn alice_main( use zssp_proto::result::ReceiveOk::*; use zssp_proto::result::SessionEvent::*; match context.receive( - alice_app, + &mut alice_app, |b| alice_out.send(b).is_ok(), TEST_MTU, |_| Some((|b| alice_out.send(b).is_ok(), TEST_MTU)), @@ -228,7 +228,7 @@ fn alice_main( if current_time >= next_service { next_service = - current_time + context.service(alice_app, |_| Some((|b| alice_out.send(b).is_ok(), TEST_MTU))); + current_time + context.service(&mut alice_app, |_| Some((|b| alice_out.send(b).is_ok(), TEST_MTU))); } } } @@ -236,14 +236,14 @@ fn alice_main( fn bob_main( run: &AtomicBool, packet_success_rate: u32, - bob_app: &TestApplication, + mut bob_app: TestApplication, bob_out: mpsc::SyncSender>, bob_in: mpsc::Receiver>, recursive_out: mpsc::SyncSender>, bob_keypair: P384CrateKeyPair, ) { let startup_time = std::time::Instant::now(); - let mut context = zssp_proto::Context::<&TestApplication>::new(bob_keypair, OsRng); + let mut context = zssp_proto::Context::::new(bob_keypair, OsRng); let mut last_speed_metric = startup_time.elapsed().as_millis() as i64; let mut next_service = last_speed_metric + 500; let mut transferred = 0u64; @@ -260,7 +260,7 @@ fn bob_main( use zssp_proto::result::ReceiveOk::*; use zssp_proto::result::SessionEvent::*; match context.receive( - bob_app, + &mut bob_app, |b| bob_out.send(b).is_ok(), TEST_MTU, |_| Some((|b| bob_out.send(b).is_ok(), TEST_MTU)), @@ -305,7 +305,7 @@ fn bob_main( } if current_time >= next_service { - next_service = current_time + context.service(bob_app, |_| Some((|b| bob_out.send(b).is_ok(), TEST_MTU))); + next_service = current_time + context.service(&mut bob_app, |_| Some((|b| bob_out.send(b).is_ok(), TEST_MTU))); } } } @@ -317,14 +317,14 @@ fn core(time: u64, packet_success_rate: u32) { let alice_app = TestApplication { time: Instant::now(), name: "alice", - ratchets: Mutex::new(Ratchets::new()), + ratchets: Ratchets::new(), }; let bob_keypair = P384CrateKeyPair::generate(&mut OsRng); let bob_pubkey = bob_keypair.public_key(); let bob_app = TestApplication { time: Instant::now(), name: "bob", - ratchets: Mutex::new(Ratchets::new()), + ratchets: Ratchets::new(), }; let (alice_out, bob_in) = mpsc::sync_channel::>(256); @@ -338,7 +338,7 @@ fn core(time: u64, packet_success_rate: u32) { alice_main( run, packet_success_rate, - &alice_app, + alice_app, alice_out, alice_in, bob_out, @@ -351,7 +351,7 @@ fn core(time: u64, packet_success_rate: u32) { bob_main( run, packet_success_rate, - &bob_app, + bob_app, bob_out, bob_in, alice_out, diff --git a/src/application.rs b/src/application.rs index 03f814c..ba1f018 100644 --- a/src/application.rs +++ b/src/application.rs @@ -83,11 +83,11 @@ impl Default for Settings { } } -/// Trait to implement to integrate the session into an application. +/// Trait to implement to integrate ZSSP into an application. /// -/// Templating the session on this trait lets the code here be almost entirely transport, OS, -/// and use case independent. -pub trait ApplicationLayer: Sized { +/// This is a container trait for all of the cryptographic algorithms ZSSP will use, and all of the +/// generic types that ZSSP will attach to sessions. +pub trait CryptoLayer: Sized { /// These are constants that can be redefined from their defaults to change rekey /// and negotiation timeout behavior. If two sides of a ZSSP session have different constants, /// the protocol will tend to default to the smaller constants. @@ -131,13 +131,20 @@ pub trait ApplicationLayer: Sized { /// for ZSSP to achieve FIPS compliance. type Kem: Kyber1024PrivateKey; + /// An arbitrary opaque object for use by the application that is attached to each session. + type SessionData; +} +/// Trait to implement to integrate ZSSP into an application. +/// +/// Templating ZSSP on this trait lets the code here be almost entirely transport, OS, +/// and use case independent. +pub trait ApplicationLayer: Sized { + /// Specifies which concrete set of cryptography types will be used by this application. + type Crypto: CryptoLayer; /// A user-defined error returned when the `ApplicationLayer` fails to access persistent storage /// for a peer's ratchet states. type StorageError: std::error::Error; - /// An arbitrary opaque object for use by the application that is attached to each session. - type SessionData; - /// Should return the current time in milliseconds. Does not have to be monotonic, nor synced /// with remote peers (although both of these properties would help reliability slightly). /// Used to determine if any current handshakes should be resent or timed-out, or if a session @@ -169,14 +176,14 @@ pub trait ApplicationLayer: Sized { /// least one party is misconfigured and got their ratchet keys corrupted or lost, or Bob has /// been compromised and is being impersonated. An attacker must at least have Bob's private /// static key to be able to ask Alice to downgrade. - fn initiator_disallows_downgrade(&mut self, session: &Arc>) -> bool; + fn initiator_disallows_downgrade(&mut self, session: &Arc>) -> bool; /// Function to accept sessions after final negotiation. /// The second argument is the identity that the remote peer sent us. The application /// must verify this identity is associated with the remote peer's static key. /// To prevent desync, if this function specifies that we should connect, no other open session /// with the same remote peer must exist. Drop or call expire on any pre-existing sessions /// before returning. - fn check_accept_session(&mut self, remote_static_key: &Self::PublicKey, identity: &[u8]) -> AcceptAction; + fn check_accept_session(&mut self, remote_static_key: &::PublicKey, identity: &[u8]) -> AcceptAction; /// Lookup a specific ratchet state based on its ratchet fingerprint. /// This function will be called whenever Alice attempts to connect to us with a non-empty @@ -204,8 +211,8 @@ pub trait ApplicationLayer: Sized { /// function `ApplicationLayer::check_accept_session`. fn restore_by_identity( &mut self, - remote_static_key: &Self::PublicKey, - session_data: &Self::SessionData, + remote_static_key: &::PublicKey, + session_data: &::SessionData, ) -> Result, Self::StorageError>; /// Atomically commit the update specified by `update_data` to storage, or return an error if /// the update could not be made. @@ -225,8 +232,8 @@ pub trait ApplicationLayer: Sized { /// Otherwise, when we restart, we will not be allowed to reconnect. fn save_ratchet_state( &mut self, - remote_static_key: &Self::PublicKey, - session_data: &Self::SessionData, + remote_static_key: &::PublicKey, + session_data: &::SessionData, update_data: RatchetUpdate<'_>, ) -> Result<(), Self::StorageError>; @@ -234,17 +241,17 @@ pub trait ApplicationLayer: Sized { /// These are provided for debugging, logging or metrics purposes, and must be used for /// nothing else. Do not base protocol-level decisions upon the events passed to this function. #[cfg(feature = "logging")] - fn event_log(&mut self, event: LogEvent<'_, Self>); + fn event_log(&mut self, event: LogEvent<'_, Self::Crypto>); } /// A collection of fields specifying how to complete the key exchange with a specific remote peer, /// used by Bob, the responder, at the very last stage of the key exchange. /// /// Corresponds to the *Accept* callback of Transition Algorithm 4. -pub struct AcceptAction { +pub struct AcceptAction { /// The data object to be attached to the session if we successfully connect. /// If this field is None then we will not connect to this remote peer. - pub session_data: Option, + pub session_data: Option, /// Whether or not we will accept a connection with the remote peer when they do not have a /// ratchet key that we think they should have. pub responder_disallows_downgrade: bool, diff --git a/src/context.rs b/src/context.rs index 837b856..b5855d6 100644 --- a/src/context.rs +++ b/src/context.rs @@ -6,7 +6,7 @@ use std::hash::Hash; use std::num::NonZeroU32; use std::sync::{Arc, Weak}; -use crate::application::ApplicationLayer; +use crate::application::{ApplicationLayer, CryptoLayer}; use crate::challenge::ChallengeContext; use crate::crypto::{AES_256_KEY_SIZE, AES_GCM_NONCE_SIZE}; use crate::fragmentation::{send_with_fragmentation, DefragBuffer}; @@ -31,21 +31,21 @@ pub(crate) use log; /// defragment incoming packets that are not yet associated with a session. /// /// Internally this is just a clonable Arc, so it can be safely shared with multiple threads. -pub struct Context(Arc>); -impl Clone for Context { +pub struct Context(Arc>); +impl Clone for Context { fn clone(&self) -> Self { Self(self.0.clone()) } } -pub(crate) type SessionMap = RefCell>>>; +pub(crate) type SessionMap = RefCell>>>; -pub(crate) struct ContextInner { - pub(crate) rng: RefCell, - pub(crate) s_secret: App::KeyPair, - pub(crate) session_map: SessionMap, - pub(crate) sessions: RefCell, Weak>>>, - pub(crate) b2_map: RefCell>>, +pub(crate) struct ContextInner { + pub(crate) rng: RefCell, + pub(crate) s_secret: Crypto::KeyPair, + pub(crate) session_map: SessionMap, + pub(crate) sessions: RefCell, Weak>>>, + pub(crate) b2_map: RefCell>>, hello_defrag: RefCell, challenge: RefCell, @@ -62,9 +62,9 @@ fn to_packet_nonce(n: &[u8; AES_GCM_NONCE_SIZE]) -> &[u8; PACKET_NONCE_SIZE] { (&n[n.len() - PACKET_NONCE_SIZE..]).try_into().unwrap() } -impl Context { +impl Context { /// Create a new session context. - pub fn new(static_secret_key: App::KeyPair, mut rng: App::Rng) -> Self { + pub fn new(static_secret_key: Crypto::KeyPair, mut rng: Crypto::Rng) -> Self { let challenge = ChallengeContext::new(&mut rng); Self(Arc::new(ContextInner { rng: RefCell::new(rng), @@ -93,15 +93,15 @@ impl Context { /// object /// * `identity` - Payload to be sent to Bob that contains the information necessary /// for the upper protocol to authenticate and approve of Alice's identity - pub fn open( + pub fn open( &mut self, app: App, send: impl FnMut(Vec) -> bool, mut mtu: usize, - static_remote_key: App::PublicKey, - session_data: App::SessionData, + static_remote_key: Crypto::PublicKey, + session_data: Crypto::SessionData, identity: Vec, - ) -> Result>, OpenError> { + ) -> Result>, OpenError> where App: ApplicationLayer{ mtu = mtu.max(MIN_TRANSPORT_MTU); if identity.len() > IDENTITY_MAX_SIZE { return Err(OpenError::IdentityTooLarge); @@ -111,13 +111,13 @@ impl Context { // Process zeta layer. trans_to_a1( app, - &ctx, + ctx, static_remote_key, session_data, identity, |Packet(kid, nonce, payload): &Packet| { // Process fragmentation layer. - send_with_fragmentation::(send, mtu, *kid, to_packet_nonce(&nonce), payload, None); + send_with_fragmentation::(send, mtu, *kid, to_packet_nonce(&nonce), payload, None); }, ) } @@ -130,15 +130,15 @@ impl Context { /// * `send_to` - Function to get senders for existing sessions, permitting MTU and path lookup /// * `remote_address` - Whatever the remote address is, as long as you can Hash it /// * `raw_fragment` - Buffer containing incoming wire packet - pub fn receive) -> bool>( + pub fn receive) -> bool>( &mut self, mut app: App, send_unassociated_reply: impl FnMut(Vec) -> bool, mut send_unassociated_mtu: usize, - send_to: impl FnOnce(&Arc>) -> Option<(SendFn, usize)>, + send_to: impl FnOnce(&Arc>) -> Option<(SendFn, usize)>, remote_address: &impl Hash, raw_fragment: Vec, - ) -> Result, ReceiveError> { + ) -> Result, ReceiveError> where App: ApplicationLayer { use crate::result::FaultType::*; send_unassociated_mtu = send_unassociated_mtu.max(MIN_TRANSPORT_MTU); let ctx = &self.0; @@ -187,7 +187,7 @@ impl Context { |Packet(kid, nonce, payload): &Packet, hk: Option<&[u8; AES_256_KEY_SIZE]>| { if let Some((send_fragment, mut mtu)) = send_to(&session) { mtu = mtu.max(MIN_TRANSPORT_MTU); - send_with_fragmentation::( + send_with_fragmentation::( send_fragment, mtu, *kid, @@ -201,7 +201,7 @@ impl Context { let (p, _) = from_nonce(&pn); let ret = match p { PACKET_TYPE_DATA => { - received_payload_in_place(&mut zeta, kid_recv, to_aes_nonce(&pn), &mut assembled_packet)?; + received_payload_in_place::(&mut zeta, kid_recv, to_aes_nonce(&pn), &mut assembled_packet)?; SessionEvent::Data(assembled_packet) } PACKET_TYPE_HANDSHAKE_RESPONSE => { @@ -286,7 +286,7 @@ impl Context { } PACKET_TYPE_SESSION_REJECTED => { log!(app, ReceivedRawD); - received_d_trans(&mut zeta, kid_recv, to_aes_nonce(&pn), assembled_packet)?; + received_d_trans::(&mut zeta, kid_recv, to_aes_nonce(&pn), assembled_packet)?; log!(app, DIsAuthClosedSession(&session)); SessionEvent::Rejected } @@ -323,7 +323,7 @@ impl Context { kid_recv, assembled_packet, |Packet(kid, nonce, payload), hk| { - send_with_fragmentation::( + send_with_fragmentation::( send_unassociated_reply, send_unassociated_mtu, *kid, @@ -369,7 +369,7 @@ impl Context { log!(app, ReceivedRawX1); // Process recv challenge layer. let challenge_start = assembled_packet.len() - CHALLENGE_SIZE; - let result = ctx.challenge.borrow_mut().process_hello::( + let result = ctx.challenge.borrow_mut().process_hello::( remote_address, (&assembled_packet[challenge_start..]).try_into().unwrap(), ); @@ -379,7 +379,7 @@ impl Context { challenge_packet.extend(&assembled_packet[..KID_SIZE]); challenge_packet.extend(&challenge); let nonce = to_nonce(PACKET_TYPE_CHALLENGE, ctx.rng.borrow_mut().next_u64()); - send_with_fragmentation::( + send_with_fragmentation::( send_unassociated_reply, send_unassociated_mtu, 0, @@ -401,7 +401,7 @@ impl Context { to_aes_nonce(&n), assembled_packet, |Packet(kid, nonce, payload), hk| { - send_with_fragmentation::( + send_with_fragmentation::( send_unassociated_reply, send_unassociated_mtu, *kid, @@ -424,7 +424,7 @@ impl Context { { if let Some(Some(session)) = ctx.session_map.borrow_mut().get(&kid_recv).map(|r| r.upgrade()) { let mut zeta = session.0.borrow_mut(); - respond_to_challenge( + respond_to_challenge::( &mut zeta, &ctx.rng, &assembled_packet[KID_SIZE..].try_into().unwrap(), @@ -454,7 +454,7 @@ impl Context { /// * `payload` - Data to send pub fn send( &mut self, - session: &Arc>, + session: &Arc>, send: impl FnMut(Vec) -> bool, mut mtu: usize, payload: Vec, @@ -462,8 +462,8 @@ impl Context { debug_assert_eq!(session.0.borrow().ctx.as_ptr(), Arc::as_ptr(&self.0)); mtu = mtu.max(MIN_TRANSPORT_MTU); let mut zeta = session.0.borrow_mut(); - send_payload(&mut zeta, payload, |Packet(kid, nonce, payload), hk| { - send_with_fragmentation::(send, mtu, *kid, to_packet_nonce(nonce), &payload, hk); + send_payload::(&mut zeta, payload, |Packet(kid, nonce, payload), hk| { + send_with_fragmentation::(send, mtu, *kid, to_packet_nonce(nonce), &payload, hk); }) } @@ -474,11 +474,11 @@ impl Context { /// a problem. It is completely fine to call this function more often than the returned interval. /// /// * `send_to` - Function to get a sender and an MTU to send something over an active session - pub fn service) -> bool>( + pub fn service) -> bool>( &mut self, mut app: App, - mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, - ) -> i64 { + mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, + ) -> i64 where App: ApplicationLayer { let ctx = &self.0; let sessions = ctx.sessions.borrow_mut(); let current_time = app.time(); @@ -495,7 +495,7 @@ impl Context { |Packet(kid, nonce, payload): &Packet, hk| { if let Some((send_fragment, mut mtu)) = send_to(&session) { mtu = mtu.max(MIN_TRANSPORT_MTU); - send_with_fragmentation::( + send_with_fragmentation::( send_fragment, mtu, *kid, @@ -507,10 +507,10 @@ impl Context { }, ); next_timer = next_timer.min(zeta.next_timer()); - zeta.defrag.service::(current_time); + zeta.defrag.service(current_time); } } - ctx.hello_defrag.borrow_mut().service::(current_time); - (App::SETTINGS.resend_time as i64).min(next_timer - current_time) + ctx.hello_defrag.borrow_mut().service(current_time); + (Crypto::SETTINGS.resend_time as i64).min(next_timer - current_time) } } diff --git a/src/crypto_impl/mod.rs b/src/crypto_impl/mod.rs index ccbe469..921af90 100644 --- a/src/crypto_impl/mod.rs +++ b/src/crypto_impl/mod.rs @@ -27,3 +27,38 @@ pub use p384; pub use pqc_kyber; #[cfg(feature = "sha2")] pub use sha2; + +/* +TODO: wrangle the feature flags so we can provide the default set of crypto implementations below. +use crate::application::{Settings, CryptoLayer}; +#[cfg(feature = "default")] +pub trait CrateCryptoLayer { + /// These are constants that can be redefined from their defaults to change rekey + /// and negotiation timeout behavior. If two sides of a ZSSP session have different constants, + /// the protocol will tend to default to the smaller constants. + const SETTINGS: Settings = Settings::new_ms(); + + /// A user-defined error returned when the `ApplicationLayer` fails to access persistent storage + /// for a peer's ratchet states. + type StorageError: std::error::Error; + + /// An arbitrary opaque object for use by the application that is attached to each session. + type SessionData; +} + +use rand_core::OsRng; +#[cfg(feature = "default")] +impl CryptoLayer for Crypto { + type Rng = OsRng; + type Prp = Aes256Crate; + type Aead = AesGcmCrate; + type Hash = Sha512Crate; + type PublicKey = P384CratePublicKey; + type KeyPair = P384CrateKeyPair; + type Kem = RustKyber1024PrivateKey; + + type StorageError = Crypto::StorageError; + + type SessionData = Crypto::SessionData; +} + */ diff --git a/src/fragmentation.rs b/src/fragmentation.rs index 55b425f..c028295 100644 --- a/src/fragmentation.rs +++ b/src/fragmentation.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use zeroize::Zeroizing; -use crate::application::ApplicationLayer; +use crate::application::{CryptoLayer, ApplicationLayer}; use crate::crypto::{Aes256Prp, AES_256_KEY_SIZE}; use crate::proto::*; use crate::result::{byzantine_fault, ReceiveError}; @@ -28,7 +28,7 @@ fn create_fragment_header( } /// Corresponds to the fragmentation algorithm described in Section 6. -pub fn send_with_fragmentation( +pub fn send_with_fragmentation( mut send: impl FnMut(Vec) -> bool, mtu: usize, identifier: u32, @@ -52,7 +52,7 @@ pub fn send_with_fragmentation( fragment.extend(&packet[i..j]); if let Some(hk_send) = hk_send { - App::Prp::encrypt_in_place( + Crypto::Prp::encrypt_in_place( hk_send, (&mut fragment[HEADER_AUTH_START..HEADER_AUTH_END]).try_into().unwrap(), ); @@ -95,7 +95,7 @@ impl DefragBuffer { } if let Some(hk_recv) = self.hk_recv.as_ref() { - App::Prp::decrypt_in_place( + ::Prp::decrypt_in_place( hk_recv, (&mut raw_fragment[HEADER_AUTH_START..HEADER_AUTH_END]) .try_into() @@ -115,7 +115,7 @@ impl DefragBuffer { return Err(e); } - let expiration_time = current_time + App::SETTINGS.fragment_assembly_timeout as i64; + let expiration_time = current_time + App::Crypto::SETTINGS.fragment_assembly_timeout as i64; let mut map = self.fragment_map.borrow_mut(); match map.entry(n) { Entry::Occupied(mut entry) => { @@ -157,7 +157,7 @@ impl DefragBuffer { } } - pub fn service(&self, current_time: i64) { + pub fn service(&self, current_time: i64) { let mut map = self.fragment_map.borrow_mut(); map.retain(|_, buffer| buffer.expiration_time < current_time); } diff --git a/src/log_event.rs b/src/log_event.rs index 3cb3948..ae01138 100644 --- a/src/log_event.rs +++ b/src/log_event.rs @@ -1,23 +1,23 @@ use std::sync::Arc; -use crate::application::ApplicationLayer; +use crate::application::CryptoLayer; use crate::Session; /// ZSSP events that might be interesting to log or aggregate into metrics. #[allow(missing_docs)] -pub enum LogEvent<'a, App: ApplicationLayer> { - ResentX1(&'a Arc>), - TimeoutX1(&'a Arc>), +pub enum LogEvent<'a, Crypto: CryptoLayer> { + ResentX1(&'a Arc>), + TimeoutX1(&'a Arc>), TimeoutX2, - ResentX3(&'a Arc>), - TimeoutX3(&'a Arc>), - ResentKeyConfirm(&'a Arc>), - TimeoutKeyConfirm(&'a Arc>), - StartedRekeyingSentK1(&'a Arc>), - ResentK1(&'a Arc>), - TimeoutK1(&'a Arc>), - ResentK2(&'a Arc>), - TimeoutK2(&'a Arc>), + ResentX3(&'a Arc>), + TimeoutX3(&'a Arc>), + ResentKeyConfirm(&'a Arc>), + TimeoutKeyConfirm(&'a Arc>), + StartedRekeyingSentK1(&'a Arc>), + ResentK1(&'a Arc>), + TimeoutK1(&'a Arc>), + ResentK2(&'a Arc>), + TimeoutK2(&'a Arc>), /// `(packet_type, packet_counter, fragment_no, fragment_count)` ReceivedRawFragment(u8, u64, usize, usize), ReceivedRawX1, @@ -25,24 +25,24 @@ pub enum LogEvent<'a, App: ApplicationLayer> { X1SucceededChallenge, X1IsAuthSentX2, ReceivedRawChallenge, - ChallengeIsAuth(&'a Arc>), + ChallengeIsAuth(&'a Arc>), ReceivedRawX2, - X2IsAuthSentX3(&'a Arc>), + X2IsAuthSentX3(&'a Arc>), ReceivedRawX3, - X3IsAuthSentKeyConfirm(&'a Arc>), + X3IsAuthSentKeyConfirm(&'a Arc>), ReceivedRawKeyConfirm, - KeyConfirmIsAuthSentAck(&'a Arc>), + KeyConfirmIsAuthSentAck(&'a Arc>), ReceivedRawAck, - AckIsAuth(&'a Arc>), + AckIsAuth(&'a Arc>), ReceivedRawK1, - K1IsAuthSentK2(&'a Arc>), + K1IsAuthSentK2(&'a Arc>), ReceivedRawK2, - K2IsAuthSentKeyConfirm(&'a Arc>), + K2IsAuthSentKeyConfirm(&'a Arc>), ReceivedRawD, - DIsAuthClosedSession(&'a Arc>), + DIsAuthClosedSession(&'a Arc>), } -impl<'a, App: ApplicationLayer> std::fmt::Debug for LogEvent<'a, App> { +impl<'a, Crypto: CryptoLayer> std::fmt::Debug for LogEvent<'a, Crypto> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::ResentX1(_) => f.debug_tuple("ResentX1").finish(), diff --git a/src/result.rs b/src/result.rs index a3abb60..47da452 100644 --- a/src/result.rs +++ b/src/result.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use crate::application::ApplicationLayer; +use crate::application::CryptoLayer; use crate::Session; /// An error that can occur when attempting to open a session. @@ -121,13 +121,13 @@ pub(crate) use byzantine_fault; /// Result generated by the context packet receive function, with possible payloads. #[derive(Clone)] -pub enum ReceiveOk { +pub enum ReceiveOk { /// Packet superficially appeared valid but is not associated with a session yet. /// This can occur because the packet was only a fragment of a larger packet, /// or if it was a control packet that does not go through full Noise authentication. Unassociated, /// Packet was authentic and belongs to this specific session. - Session(Arc>, SessionEvent), + Session(Arc>, SessionEvent), } /// Something that can occur to an associated session when a packet is received successfully, /// including receiving a payload of decrypted, authenticated data. diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index 5388e8a..e061efb 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -2,19 +2,19 @@ use std::marker::PhantomData; use zeroize::Zeroizing; -use crate::application::ApplicationLayer; +use crate::application::CryptoLayer; use crate::crypto::*; use crate::proto::*; -pub struct SymmetricState { +pub struct SymmetricState { k: Zeroizing<[u8; AES_256_KEY_SIZE]>, ck: Zeroizing<[u8; HASHLEN]>, h: [u8; HASHLEN], - /// If anyone knows a better way to get rid of the "parameter `App` is never used" error please - /// let me know. - _app: PhantomData App::SessionData>, + /// If anyone knows a better way to get rid of the "parameter `Crypto` is never used" error + /// please let me know. + _app: PhantomData Crypto::SessionData>, } -impl Clone for SymmetricState { +impl Clone for SymmetricState { fn clone(&self) -> Self { Self { k: self.k.clone(), @@ -25,7 +25,7 @@ impl Clone for SymmetricState { } } -impl SymmetricState { +impl SymmetricState { /// HMAC-SHA512 key derivation based on KBKDF Counter Mode: /// https://csrc.nist.gov/publications/detail/sp/800-108/rev-1/final. /// Cryptographically this isn't meaningfully different from @@ -55,18 +55,18 @@ impl SymmetricState { buffer.extend(&(num_outputs * 8 * HASHLEN as u16).to_be_bytes()); debug_assert!(num_outputs >= 1); - *output1 = App::Hash::hmac(input_key_material, &buffer); + *output1 = Crypto::Hash::hmac(input_key_material, &buffer); if let Some(output2) = output2 { debug_assert!(num_outputs >= 2); buffer[0] = 2; - *output2 = App::Hash::hmac(input_key_material, &buffer); + *output2 = Crypto::Hash::hmac(input_key_material, &buffer); } if let Some(output3) = output3 { debug_assert!(num_outputs >= 3); buffer[0] = 3; - *output3 = App::Hash::hmac(input_key_material, &buffer); + *output3 = Crypto::Hash::hmac(input_key_material, &buffer); } } @@ -98,7 +98,7 @@ impl SymmetricState { } /// Corresponds to Noise `MixHash`. pub fn mix_hash(&mut self, data: &[u8]) { - let mut hash = App::Hash::new(); + let mut hash = Crypto::Hash::new(); hash.update(&self.h); hash.update(data); self.h = hash.finish(); @@ -129,7 +129,7 @@ impl SymmetricState { plaintext_start: usize, buffer: &mut Vec, ) { - let tag = App::Aead::encrypt_in_place(&self.k, &iv, Some(&self.h), &mut buffer[plaintext_start..]); + let tag = Crypto::Aead::encrypt_in_place(&self.k, &iv, Some(&self.h), &mut buffer[plaintext_start..]); buffer.extend(&tag); self.mix_hash(&buffer[plaintext_start..]); } @@ -141,11 +141,11 @@ impl SymmetricState { buffer: &mut [u8], tag: [u8; AES_GCM_TAG_SIZE], ) -> bool { - let mut hash = App::Hash::new(); + let mut hash = Crypto::Hash::new(); hash.update(&self.h); hash.update(buffer); hash.update(&tag); - let ret = App::Aead::decrypt_in_place(&self.k, &iv, Some(&self.h), buffer, &tag); + let ret = Crypto::Aead::decrypt_in_place(&self.k, &iv, Some(&self.h), buffer, &tag); self.h = hash.finish(); ret } diff --git a/src/zeta.rs b/src/zeta.rs index abad602..7e8a25c 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -7,7 +7,7 @@ use std::sync::{Arc, Weak}; use rand_core::RngCore; use zeroize::Zeroizing; -use crate::application::{ApplicationLayer, RatchetState, RatchetStates, RatchetUpdate}; +use crate::application::{ApplicationLayer, RatchetState, RatchetStates, RatchetUpdate, CryptoLayer}; use crate::challenge::{gen_null_response, respond_to_challenge_in_place}; use crate::context::{log, ContextInner, SessionMap}; use crate::crypto::*; @@ -19,14 +19,14 @@ use crate::symmetric_state::SymmetricState; use crate::LogEvent::*; /// Corresponds to the Zeta State Machine found in Section 4.1. -pub(crate) struct Zeta { - pub ctx: Weak>, +pub(crate) struct Zeta { + pub ctx: Weak>, /// An arbitrary application defined object associated with each session. - pub session_data: App::SessionData, + pub session_data: Crypto::SessionData, /// Is true if the local peer acted as Bob, the responder in the initial key exchange. pub was_bob: bool, - s_remote: App::PublicKey, + s_remote: Crypto::PublicKey, send_counter: u64, key_creation_counter: u64, @@ -38,7 +38,7 @@ pub(crate) struct Zeta { resend_timer: i64, timeout_timer: i64, - pub beta: ZetaAutomata, + pub beta: ZetaAutomata, pub counter_antireplay_window: [u64; COUNTER_WINDOW_MAX_OOO], pub defrag: DefragBuffer, @@ -46,16 +46,16 @@ pub(crate) struct Zeta { /// ZeroTier Secure Session Protocol (ZSSP) Session. /// /// A FIPS/NIST compliant variant of Noise_XK with hybrid Kyber1024 PQ data forward secrecy. -pub struct Session(pub(crate) RefCell>); +pub struct Session(pub(crate) RefCell>); /// Corresponds to State B_2 of the Zeta State Machine found in Section 4.1 - Definition 3. -pub(crate) struct StateB2 { +pub(crate) struct StateB2 { ratchet_state: RatchetState, kid_send: NonZeroU32, pub kid_recv: NonZeroU32, pub hk_send: Zeroizing<[u8; AES_256_KEY_SIZE]>, - e_secret: App::KeyPair, - noise: SymmetricState, + e_secret: Crypto::KeyPair, + noise: SymmetricState, pub defrag: DefragBuffer, } @@ -78,18 +78,18 @@ pub(crate) struct Packet(pub u32, pub [u8; AES_GCM_NONCE_SIZE], pub Vec); /// Corresponds to State A_1 of the Zeta State Machine found in Section 4.1. #[derive(Clone)] -pub(crate) struct StateA1 { - noise: SymmetricState, - e_secret: App::KeyPair, - e1_secret: App::Kem, +pub(crate) struct StateA1 { + noise: SymmetricState, + e_secret: Crypto::KeyPair, + e1_secret: Crypto::Kem, identity: Vec, packet: Packet, } /// Corresponds to the ZKE Automata found in Section 4.1 - Definition 2. -pub(crate) enum ZetaAutomata { +pub(crate) enum ZetaAutomata { Null, - A1(StateA1), + A1(StateA1), A3 { identity: Vec, packet: Packet, @@ -97,8 +97,8 @@ pub(crate) enum ZetaAutomata { S1, S2, R1 { - noise: SymmetricState, - e_secret: App::KeyPair, + noise: SymmetricState, + e_secret: Crypto::KeyPair, k1: Vec, }, R2 { @@ -106,24 +106,24 @@ pub(crate) enum ZetaAutomata { }, } -impl SymmetricState { - fn write_e(&mut self, rng: &RefCell, packet: &mut Vec) -> App::KeyPair { - let e_secret = App::KeyPair::generate(rng.borrow_mut().deref_mut()); +impl SymmetricState { + fn write_e(&mut self, rng: &RefCell, packet: &mut Vec) -> Crypto::KeyPair { + let e_secret = Crypto::KeyPair::generate(rng.borrow_mut().deref_mut()); let pub_key = e_secret.public_key_bytes(); packet.extend(&pub_key); self.mix_hash(&pub_key); self.mix_key(&pub_key); e_secret } - fn read_e(&mut self, i: &mut usize, packet: &Vec) -> Option { + fn read_e(&mut self, i: &mut usize, packet: &Vec) -> Option { let j = *i + P384_PUBLIC_KEY_SIZE; let pub_key = &packet[*i..j]; self.mix_hash(pub_key); self.mix_key(pub_key); *i = j; - App::PublicKey::from_bytes((pub_key).try_into().unwrap()) + Crypto::PublicKey::from_bytes((pub_key).try_into().unwrap()) } - fn mix_dh(&mut self, secret: &App::KeyPair, remote: &App::PublicKey) -> Option<()> { + fn mix_dh(&mut self, secret: &Crypto::KeyPair, remote: &Crypto::PublicKey) -> Option<()> { if let Some(ecdh) = secret.agree(&remote).map(Zeroizing::new) { self.mix_key(ecdh.as_ref()); Some(()) @@ -165,11 +165,11 @@ fn gen_kid(session_map: &HashMap, rng: &mut impl RngCore) -> N } } } -fn remap( - session: &Arc>, - zeta: &Zeta, - rng: &RefCell, - session_map: &SessionMap, +fn remap( + session: &Arc>, + zeta: &Zeta, + rng: &RefCell, + session_map: &SessionMap, ) -> NonZeroU32 { let mut session_map = session_map.borrow_mut(); let weak = if let Some(Some(weak)) = zeta.key_ref(true).recv.kid.as_ref().map(|kid| session_map.remove(kid)) { @@ -182,7 +182,7 @@ fn remap( new_kid_recv } -impl Zeta { +impl Zeta { pub(crate) fn check_counter_window(&self, c: u64) -> bool { let slot = &self.counter_antireplay_window[c as usize % self.counter_antireplay_window.len()]; let adj_counter = c.saturating_add(1); @@ -235,23 +235,23 @@ impl Zeta { c, c >= self .key_creation_counter - .saturating_add(App::SETTINGS.rekey_after_key_uses), + .saturating_add(Crypto::SETTINGS.rekey_after_key_uses), )) } } fn create_a1_state( - rng: &RefCell, - s_remote: &App::PublicKey, + rng: &RefCell<::Rng>, + s_remote: &::PublicKey, kid_recv: NonZeroU32, ratchet_state1: &RatchetState, ratchet_state2: Option<&RatchetState>, identity: Vec, -) -> Option> { +) -> Option> { // <- s // ... // -> e, es, e1 - let mut noise = SymmetricState::::initialize(PROTOCOL_NAME_NOISE_XK); + let mut noise = SymmetricState::::initialize(PROTOCOL_NAME_NOISE_XK); let mut x1 = Vec::new(); // Noise process prologue. let kid = kid_recv.get().to_be_bytes(); @@ -264,7 +264,7 @@ fn create_a1_state( noise.mix_dh(&e_secret, s_remote)?; // Process message pattern 1 e1 token. let i = x1.len(); - let (e1_secret, e1_public) = App::Kem::generate(rng.borrow_mut().deref_mut()); + let (e1_secret, e1_public) = ::Kem::generate(rng.borrow_mut().deref_mut()); x1.extend(&e1_public); noise.encrypt_and_hash_in_place(to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, 0), i, &mut x1); // Process message pattern 1 payload. @@ -291,12 +291,12 @@ fn create_a1_state( /// Corresponds to Transition Algorithm 1 found in Section 4.3. pub(crate) fn trans_to_a1( mut app: App, - ctx: &Arc>, - s_remote: App::PublicKey, - session_data: App::SessionData, + ctx: &Arc>, + s_remote: ::PublicKey, + session_data: ::SessionData, identity: Vec, send: impl FnOnce(&Packet), -) -> Result>, OpenError> { +) -> Result>, OpenError> { let ratchet_states = app .restore_by_identity(&s_remote, &session_data) .map_err(|e| OpenError::StorageError(e))?; @@ -305,7 +305,7 @@ pub(crate) fn trans_to_a1( let mut session_map = ctx.session_map.borrow_mut(); let kid_recv = gen_kid(session_map.deref(), ctx.rng.borrow_mut().deref_mut()); - let a1 = create_a1_state(&ctx.rng, &s_remote, kid_recv, &state1, state2.as_ref(), identity) + let a1 = create_a1_state::(&ctx.rng, &s_remote, kid_recv, &state1, state2.as_ref(), identity) .ok_or(OpenError::InvalidPublicKey)?; let packet = a1.packet.clone(); @@ -326,8 +326,8 @@ pub(crate) fn trans_to_a1( ratchet_state1: state1, ratchet_state2: state2, hk_send, - resend_timer: current_time + App::SETTINGS.resend_time as i64, - timeout_timer: current_time + App::SETTINGS.initial_offer_timeout as i64, + resend_timer: current_time + ::SETTINGS.resend_time as i64, + timeout_timer: current_time + ::SETTINGS.initial_offer_timeout as i64, beta: ZetaAutomata::A1(a1), }; zeta.key_mut(true).recv.kid = Some(kid_recv); @@ -344,13 +344,13 @@ pub(crate) fn trans_to_a1( } /// Corresponds to Algorithm 13 found in Section 5. pub(crate) fn respond_to_challenge( - zeta: &mut Zeta, - rng: &RefCell, + zeta: &mut Zeta, + rng: &RefCell<::Rng>, challenge: &[u8; CHALLENGE_SIZE], ) { if let ZetaAutomata::A1(StateA1 { packet: Packet(_, _, x1), .. }) = &mut zeta.beta { let response_start = x1.len() - CHALLENGE_SIZE; - respond_to_challenge_in_place::( + respond_to_challenge_in_place::<::Rng, ::Hash>( rng.borrow_mut().deref_mut(), challenge, (&mut x1[response_start..]).try_into().unwrap(), @@ -360,7 +360,7 @@ pub(crate) fn respond_to_challenge( /// Corresponds to Transition Algorithm 2 found in Section 4.3. pub(crate) fn received_x1_trans( app: &mut App, - ctx: &ContextInner, + ctx: &ContextInner, n: [u8; AES_GCM_NONCE_SIZE], mut x1: Vec, send: impl FnOnce(&Packet, &[u8; AES_256_KEY_SIZE]), @@ -376,7 +376,7 @@ pub(crate) fn received_x1_trans( if &n[AES_GCM_NONCE_SIZE - 8..] != &x1[x1.len() - 8..] { return Err(byzantine_fault!(FailedAuth, true)); } - let mut noise = SymmetricState::::initialize(PROTOCOL_NAME_NOISE_XK); + let mut noise = SymmetricState::::initialize(PROTOCOL_NAME_NOISE_XK); let mut i = 0; // Noise process prologue. let j = i + KID_SIZE; @@ -441,7 +441,7 @@ pub(crate) fn received_x1_trans( .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 ekem1 token. let i = x2.len(); - let (ekem1, ekem1_secret) = App::Kem::encapsulate( + let (ekem1, ekem1_secret) = ::Kem::encapsulate( ctx.rng.borrow_mut().deref_mut(), (&x1[e1_start..e1_end]).try_into().unwrap(), ) @@ -489,10 +489,10 @@ pub(crate) fn received_x1_trans( } /// Corresponds to Transition Algorithm 3 found in Section 4.3. pub(crate) fn received_x2_trans( - zeta: &mut Zeta, - session: &Arc>, + zeta: &mut Zeta, + session: &Arc>, app: &mut App, - ctx: &Arc>, + ctx: &Arc>, kid: NonZeroU32, n: [u8; AES_GCM_NONCE_SIZE], mut x2: Vec, @@ -547,7 +547,7 @@ pub(crate) fn received_x2_trans( let payload: [u8; KID_SIZE] = x2[i..j].try_into().unwrap(); let tag = x2[j..k].try_into().unwrap(); // Check for which ratchet key Bob wants to use. - let test_ratchet_key = |ratchet_key| -> Option<(NonZeroU32, SymmetricState)> { + let test_ratchet_key = |ratchet_key| -> Option<(NonZeroU32, SymmetricState)> { let mut noise = noise.clone(); let mut payload = payload.clone(); // Process message pattern 2 psk token. @@ -632,8 +632,8 @@ pub(crate) fn received_x2_trans( zeta.ratchet_state1 = new_ratchet_state; let current_time = app.time(); zeta.key_creation_counter = zeta.send_counter; - zeta.resend_timer = current_time + App::SETTINGS.resend_time as i64; - zeta.timeout_timer = current_time + App::SETTINGS.initial_offer_timeout as i64; + zeta.resend_timer = current_time + ::SETTINGS.resend_time as i64; + zeta.timeout_timer = current_time + ::SETTINGS.initial_offer_timeout as i64; let packet = Packet(kid_send.get(), n, x3); zeta.beta = ZetaAutomata::A3 { identity, packet: packet.clone() }; @@ -653,7 +653,7 @@ pub(crate) fn received_x2_trans( result.map(|_| should_warn_missing_ratchet) } fn send_control( - zeta: &mut Zeta, + zeta: &mut Zeta, packet_type: u8, mut payload: Vec, send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), @@ -661,7 +661,7 @@ fn send_control( if let Some((c, _)) = zeta.get_counter() { if let (Some(kek), Some(kid)) = (zeta.key_ref(false).send.kek.as_ref(), zeta.key_ref(false).send.kid) { let nonce = to_nonce(packet_type, c); - let tag = App::Aead::encrypt_in_place(kek, &nonce, None, &mut payload); + let tag = ::Aead::encrypt_in_place(kek, &nonce, None, &mut payload); payload.extend(tag); send(&Packet(kid.get(), nonce, payload), Some(&zeta.hk_send)); @@ -675,13 +675,13 @@ fn send_control( } /// Corresponds to Transition Algorithm 4 found in Section 4.3. pub(crate) fn received_x3_trans( - zeta: StateB2, + zeta: StateB2, app: &mut App, - ctx: &Arc>, + ctx: &Arc>, kid: NonZeroU32, mut x3: Vec, send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), -) -> Result<(Arc>, bool), ReceiveError> { +) -> Result<(Arc>, bool), ReceiveError> { use FaultType::*; // -> s, se if !(HANDSHAKE_COMPLETION_MIN_SIZE..=HANDSHAKE_COMPLETION_MAX_SIZE).contains(&x3.len()) { @@ -701,7 +701,7 @@ pub(crate) fn received_x3_trans( return Err(byzantine_fault!(FailedAuth, true)); } let s_remote = - App::PublicKey::from_bytes((&x3[i..j]).try_into().unwrap()).ok_or(byzantine_fault!(FailedAuth, true))?; + ::PublicKey::from_bytes((&x3[i..j]).try_into().unwrap()).ok_or(byzantine_fault!(FailedAuth, true))?; i = k; // Process message pattern 3 se token. noise @@ -727,7 +727,7 @@ pub(crate) fn received_x3_trans( let create_reject = || { let mut d = Vec::::new(); let n = to_nonce(PACKET_TYPE_SESSION_REJECTED, c); - let tag = App::Aead::encrypt_in_place(&kek_send, &n, None, &mut []); + let tag = ::Aead::encrypt_in_place(&kek_send, &n, None, &mut []); d.extend(&tag); // We just used a counter with this key, but we are not storing // the fact we used it in memory. This is currently ok because the @@ -772,7 +772,7 @@ pub(crate) fn received_x3_trans( let mut c1 = Vec::new(); let n = to_nonce(PACKET_TYPE_KEY_CONFIRM, c); - let tag = App::Aead::encrypt_in_place(&kek_send, &n, None, &mut []); + let tag = ::Aead::encrypt_in_place(&kek_send, &n, None, &mut []); c1.extend(&tag); let (nk1, nk2) = noise.split(); @@ -802,8 +802,8 @@ pub(crate) fn received_x3_trans( ratchet_state1: new_ratchet_state, ratchet_state2: None, hk_send: zeta.hk_send.clone(), - resend_timer: current_time + App::SETTINGS.resend_time as i64, - timeout_timer: current_time + App::SETTINGS.rekey_timeout as i64, + resend_timer: current_time + ::SETTINGS.resend_time as i64, + timeout_timer: current_time + ::SETTINGS.rekey_timeout as i64, beta: ZetaAutomata::S1, counter_antireplay_window: std::array::from_fn(|_| 0), defrag: zeta.defrag, @@ -827,9 +827,9 @@ pub(crate) fn received_x3_trans( } /// Corresponds to Transition Algorithm 5 found in Section 4.3. pub(crate) fn received_c1_trans( - zeta: &mut Zeta, + zeta: &mut Zeta, app: &mut App, - rng: &RefCell, + rng: &RefCell<::Rng>, kid: NonZeroU32, n: [u8; AES_GCM_NONCE_SIZE], c1: Vec, @@ -857,7 +857,7 @@ pub(crate) fn received_c1_trans( .as_ref() .ok_or(byzantine_fault!(OutOfSequence, true))?; let tag = c1[..].try_into().unwrap(); - if !App::Aead::decrypt_in_place(specified_key, &n, None, &mut [], tag) { + if !::Aead::decrypt_in_place(specified_key, &n, None, &mut [], tag) { return Err(byzantine_fault!(FailedAuth, true)); } let (_, c) = from_nonce(&n); @@ -887,14 +887,14 @@ pub(crate) fn received_c1_trans( zeta.ratchet_state2 = None; zeta.key_index ^= true; - let r = rng.borrow_mut().next_u64() % App::SETTINGS.rekey_time_max_jitter; - zeta.timeout_timer = app.time() + App::SETTINGS.rekey_after_time.saturating_sub(r) as i64; + let r = rng.borrow_mut().next_u64() % ::SETTINGS.rekey_time_max_jitter; + zeta.timeout_timer = app.time() + ::SETTINGS.rekey_after_time.saturating_sub(r) as i64; zeta.resend_timer = i64::MAX; zeta.beta = ZetaAutomata::S2; } } let c2 = Vec::new(); - if !send_control(zeta, PACKET_TYPE_ACK, c2, send) { + if !send_control::(zeta, PACKET_TYPE_ACK, c2, send) { return Err(byzantine_fault!(OutOfSequence, true)); } @@ -903,9 +903,9 @@ pub(crate) fn received_c1_trans( /// Corresponds to the trivial Transition Algorithm described for processing C_2 packets found in /// Section 4.3. pub(crate) fn received_c2_trans( - zeta: &mut Zeta, + zeta: &mut Zeta, app: &mut App, - rng: &RefCell, + rng: &RefCell<::Rng>, kid: NonZeroU32, n: [u8; AES_GCM_NONCE_SIZE], c2: Vec, @@ -925,7 +925,7 @@ pub(crate) fn received_c2_trans( } let tag = c2[..].try_into().unwrap(); - if !App::Aead::decrypt_in_place(zeta.key_ref(false).recv.kek.as_ref().unwrap(), &n, None, &mut [], tag) { + if !::Aead::decrypt_in_place(zeta.key_ref(false).recv.kek.as_ref().unwrap(), &n, None, &mut [], tag) { return Err(byzantine_fault!(FailedAuth, true)); } let (_, c) = from_nonce(&n); @@ -933,8 +933,8 @@ pub(crate) fn received_c2_trans( return Err(byzantine_fault!(ExpiredCounter, true)); } - let r = rng.borrow_mut().next_u64() % App::SETTINGS.rekey_time_max_jitter; - zeta.timeout_timer = app.time() + App::SETTINGS.rekey_after_time.saturating_sub(r) as i64; + let r = rng.borrow_mut().next_u64() % ::SETTINGS.rekey_time_max_jitter; + zeta.timeout_timer = app.time() + ::SETTINGS.rekey_after_time.saturating_sub(r) as i64; zeta.resend_timer = i64::MAX; zeta.beta = ZetaAutomata::S2; Ok(()) @@ -942,7 +942,7 @@ pub(crate) fn received_c2_trans( /// Corresponds to the trivial Transition Algorithm described for processing D packets found in /// Section 4.3. pub(crate) fn received_d_trans( - zeta: &mut Zeta, + zeta: &mut Zeta, kid: NonZeroU32, n: [u8; AES_GCM_NONCE_SIZE], d: Vec, @@ -957,7 +957,7 @@ pub(crate) fn received_d_trans( } let tag = d[..].try_into().unwrap(); - if !App::Aead::decrypt_in_place(zeta.key_ref(true).recv.kek.as_ref().unwrap(), &n, None, &mut [], tag) { + if !::Aead::decrypt_in_place(zeta.key_ref(true).recv.kek.as_ref().unwrap(), &n, None, &mut [], tag) { return Err(byzantine_fault!(FailedAuth, true)); } let (_, c) = from_nonce(&n); @@ -970,9 +970,9 @@ pub(crate) fn received_d_trans( } /// Corresponds to the timer rules of the Zeta State Machine found in Section 4.1 - Definition 3. pub(crate) fn service( - zeta: &mut Zeta, - session: &Arc>, - ctx: &Arc>, + zeta: &mut Zeta, + session: &Arc>, + ctx: &Arc>, app: &mut App, current_time: i64, send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), @@ -981,7 +981,7 @@ pub(crate) fn service( timeout_trans(zeta, session, app, ctx, current_time, send); } else if zeta.resend_timer <= current_time { // Corresponds to the resend timer rules found in Section 4.1 - Definition 3. - zeta.resend_timer = current_time + App::SETTINGS.resend_time as i64; + zeta.resend_timer = current_time + ::SETTINGS.resend_time as i64; let (p, control_payload) = match &zeta.beta { ZetaAutomata::Null => return, @@ -1008,15 +1008,15 @@ pub(crate) fn service( } }; - send_control(zeta, p, control_payload, send); + send_control::(zeta, p, control_payload, send); } } /// Corresponds to the timeout timer Transition Algorithm described in Section 4.1 - Definition 3. fn timeout_trans( - zeta: &mut Zeta, - session: &Arc>, + zeta: &mut Zeta, + session: &Arc>, app: &mut App, - ctx: &Arc>, + ctx: &Arc>, current_time: i64, send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), ) { @@ -1030,7 +1030,7 @@ fn timeout_trans( } let new_kid_recv = remap(session, &zeta, &ctx.rng, &ctx.session_map); - if let Some(a1) = create_a1_state( + if let Some(a1) = create_a1_state::( &ctx.rng, &zeta.s_remote, new_kid_recv, @@ -1044,8 +1044,8 @@ fn timeout_trans( zeta.hk_send = hk_send; *zeta.key_mut(true) = DuplexKey::default(); zeta.key_mut(true).recv.kid = Some(new_kid_recv); - zeta.resend_timer = current_time + App::SETTINGS.resend_time as i64; - zeta.timeout_timer = current_time + App::SETTINGS.initial_offer_timeout as i64; + zeta.resend_timer = current_time + ::SETTINGS.resend_time as i64; + zeta.timeout_timer = current_time + ::SETTINGS.initial_offer_timeout as i64; zeta.beta = ZetaAutomata::A1(a1); zeta.defrag = DefragBuffer::new(Some(hk_recv)); @@ -1087,11 +1087,11 @@ fn timeout_trans( noise.encrypt_and_hash_in_place(to_nonce(PACKET_TYPE_REKEY_INIT, 0), i, &mut k1); zeta.key_mut(true).recv.kid = Some(new_kid_recv); - zeta.timeout_timer = current_time + App::SETTINGS.rekey_timeout as i64; - zeta.resend_timer = current_time + App::SETTINGS.resend_time as i64; + zeta.timeout_timer = current_time + ::SETTINGS.rekey_timeout as i64; + zeta.resend_timer = current_time + ::SETTINGS.resend_time as i64; zeta.beta = ZetaAutomata::R1 { noise, e_secret, k1: k1.clone() }; - send_control(zeta, PACKET_TYPE_REKEY_INIT, k1, send); + send_control::(zeta, PACKET_TYPE_REKEY_INIT, k1, send); } ZetaAutomata::S1 { .. } => { log!(app, TimeoutKeyConfirm(session)); @@ -1109,12 +1109,12 @@ fn timeout_trans( } /// Corresponds to Transition Algorithm 7 found in Section 4.3. pub(crate) fn received_k1_trans( - zeta: &mut Zeta, - session: &Arc>, + zeta: &mut Zeta, + session: &Arc>, app: &mut App, - rng: &RefCell, - session_map: &SessionMap, - s_secret: &App::KeyPair, + rng: &RefCell<::Rng>, + session_map: &SessionMap, + s_secret: &::KeyPair, kid: NonZeroU32, n: [u8; AES_GCM_NONCE_SIZE], mut k1: Vec, @@ -1145,7 +1145,7 @@ pub(crate) fn received_k1_trans( let i = k1.len() - AES_GCM_TAG_SIZE; let tag = k1[i..].try_into().unwrap(); - if !App::Aead::decrypt_in_place( + if !::Aead::decrypt_in_place( zeta.key_ref(false).recv.kek.as_ref().unwrap(), &n, None, @@ -1162,7 +1162,7 @@ pub(crate) fn received_k1_trans( let result = (|| { let mut i = 0; - let mut noise = SymmetricState::::initialize(PROTOCOL_NAME_NOISE_KK); + let mut noise = SymmetricState::::initialize(PROTOCOL_NAME_NOISE_KK); // Noise process prologue. noise.mix_hash(&zeta.s_remote.to_bytes()); noise.mix_hash(&s_secret.public_key_bytes()); @@ -1234,11 +1234,11 @@ pub(crate) fn received_k1_trans( zeta.ratchet_state1 = new_ratchet_state; let current_time = app.time(); zeta.key_creation_counter = zeta.send_counter; - zeta.timeout_timer = current_time + App::SETTINGS.rekey_timeout as i64; - zeta.resend_timer = current_time + App::SETTINGS.resend_time as i64; + zeta.timeout_timer = current_time + ::SETTINGS.rekey_timeout as i64; + zeta.resend_timer = current_time + ::SETTINGS.resend_time as i64; zeta.beta = ZetaAutomata::R2 { k2: k2.clone() }; - send_control(zeta, PACKET_TYPE_REKEY_COMPLETE, k2, send); + send_control::(zeta, PACKET_TYPE_REKEY_COMPLETE, k2, send); Ok(()) })(); if matches!(result, Err(ReceiveError::ByzantineFault { .. })) { @@ -1248,7 +1248,7 @@ pub(crate) fn received_k1_trans( } /// Corresponds to Transition Algorithm 8 found in Section 4.3. pub(crate) fn received_k2_trans( - zeta: &mut Zeta, + zeta: &mut Zeta, app: &mut App, kid: NonZeroU32, n: [u8; AES_GCM_NONCE_SIZE], @@ -1271,7 +1271,7 @@ pub(crate) fn received_k2_trans( let i = k2.len() - AES_GCM_TAG_SIZE; let tag = k2[i..].try_into().unwrap(); - if !App::Aead::decrypt_in_place( + if !::Aead::decrypt_in_place( zeta.key_ref(false).recv.kek.as_ref().unwrap(), &n, None, @@ -1337,12 +1337,12 @@ pub(crate) fn received_k2_trans( zeta.key_index ^= true; let current_time = app.time(); zeta.key_creation_counter = zeta.send_counter; - zeta.timeout_timer = current_time + App::SETTINGS.rekey_timeout as i64; - zeta.resend_timer = current_time + App::SETTINGS.resend_time as i64; + zeta.timeout_timer = current_time + ::SETTINGS.rekey_timeout as i64; + zeta.resend_timer = current_time + ::SETTINGS.resend_time as i64; zeta.beta = ZetaAutomata::S1; let c1 = Vec::new(); - send_control(zeta, PACKET_TYPE_KEY_CONFIRM, c1, send); + send_control::(zeta, PACKET_TYPE_KEY_CONFIRM, c1, send); Ok(()) } else { unreachable!() @@ -1354,8 +1354,8 @@ pub(crate) fn received_k2_trans( result } /// Corresponds to Algorithm 9 found in Section 4.3. -pub(crate) fn send_payload( - zeta: &mut Zeta, +pub(crate) fn send_payload( + zeta: &mut Zeta, mut payload: Vec, send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), ) -> Result<(), SendError> { @@ -1377,7 +1377,7 @@ pub(crate) fn send_payload( } let n = to_nonce(PACKET_TYPE_DATA, c); - let tag = App::Aead::encrypt_in_place(zeta.key_ref(false).send.nk.as_ref().unwrap(), &n, None, &mut payload); + let tag = Crypto::Aead::encrypt_in_place(zeta.key_ref(false).send.nk.as_ref().unwrap(), &n, None, &mut payload); payload.extend(&tag); send( @@ -1392,7 +1392,7 @@ pub(crate) fn send_payload( } /// Corresponds to Algorithm 10 found in Section 4.3. pub(crate) fn received_payload_in_place( - zeta: &mut Zeta, + zeta: &mut Zeta, kid: NonZeroU32, n: [u8; AES_GCM_NONCE_SIZE], payload: &mut Vec, @@ -1416,7 +1416,7 @@ pub(crate) fn received_payload_in_place( let specified_key = zeta.key_ref(is_other).recv.nk.as_ref(); let specified_key = specified_key.ok_or(byzantine_fault!(OutOfSequence, true))?; let tag = payload[i..].try_into().unwrap(); - if !App::Aead::decrypt_in_place(specified_key, &n, None, &mut payload[..i], &tag) { + if !::Aead::decrypt_in_place(specified_key, &n, None, &mut payload[..i], &tag) { return Err(byzantine_fault!(FailedAuth, true)); } let (_, c) = from_nonce(&n); @@ -1430,7 +1430,7 @@ pub(crate) fn received_payload_in_place( Ok(()) } -impl Session { +impl Session { /// Mark a session as expired. This will make it impossible for this session to successfully /// receive or send data or control packets. It is recommended to simply `drop` the session /// instead, but this can provide some reassurance in complex shared ownership situations. @@ -1439,7 +1439,7 @@ impl Session { } } -impl Drop for Session { +impl Drop for Session { fn drop(&mut self) { self.expire(); }