From bec4fb9976337d23b51055486a968ccd95a297ac Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Fri, 14 Jul 2023 15:49:23 -0400 Subject: [PATCH 01/50] updated readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 39d3988..95f2984 100644 --- a/README.md +++ b/README.md @@ -49,11 +49,11 @@ Further information can be found in the ZSSP whitepaper (pending official releas * **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. -* **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. * **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. From 7c8ff59408168ff603f527f358a9581a140a48bd Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 19 Jul 2023 14:04:33 -0400 Subject: [PATCH 02/50] fixed typo --- src/log_event.rs | 4 ++-- src/zssp.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/log_event.rs b/src/log_event.rs index 3c033f7..bdc94b5 100644 --- a/src/log_event.rs +++ b/src/log_event.rs @@ -36,7 +36,7 @@ pub enum LogEvent<'a, Application: ApplicationLayer> { ReceiveUncheckedKK2, ReceiveValidKK2(&'a Arc>), ReceiveValidKeyConfirm(&'a Arc>), - ReceiveValidKeyDelete(&'a Arc>), + ReceiveValidAck(&'a Arc>), } impl<'a, Application: ApplicationLayer> std::fmt::Debug for LogEvent<'a, Application> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -68,7 +68,7 @@ impl<'a, Application: ApplicationLayer> std::fmt::Debug for LogEvent<'a, Applica ReceiveUncheckedKK2 => write!(f, "ReceiveUncheckedKK2"), ReceiveValidKK2(_) => write!(f, "ReceiveValidKK2"), ReceiveValidKeyConfirm(_) => write!(f, "ReceiveValidKeyConfirm"), - ReceiveValidKeyDelete(_) => write!(f, "ReceiveValidKeyDelete"), + ReceiveValidAck(_) => write!(f, "ReceiveValidAck"), } } } diff --git a/src/zssp.rs b/src/zssp.rs index 897391b..40a58b1 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -1901,7 +1901,7 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu } PACKET_TYPE_ACK => { drop(state); - app.event_log(LogEvent::ReceiveValidKeyDelete(&session), current_time); + app.event_log(LogEvent::ReceiveValidAck(&session), current_time); let kex_lock = session.state_machine_lock.lock().unwrap(); let mut state = session.state.write().unwrap(); // Check if we should end any current offers and transition back to Normal state From ce9e64448d57bd7a7d95e68fe4b1afb28affca31 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 19 Jul 2023 17:43:05 -0400 Subject: [PATCH 03/50] updated docs --- src/zssp.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/zssp.rs b/src/zssp.rs index 40a58b1..e1bed47 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -581,7 +581,9 @@ impl Context { /// * `check_accept_session` - Function to accept sessions after final negotiation. /// The second argument is the identity blob that the remote peer sent us. The application /// must verify this identity is associated with the remote peer's static key. - /// The third argument is true if the remote peer connected to us with a recognized ratchet fingerprint. + /// The third argument is the ratchet chain length, or ratchet count. + /// To prevent desync, if this function returns (Some(_), _), no other open session with the + /// same remote peer must exist. /// * `send_unassociated_reply` - Function to send reply packets directly when no session exists /// * `send_unassociated_mtu` - MTU for unassociated replies /// * `send_to` - Function to get senders for existing sessions, permitting MTU and path lookup @@ -2228,8 +2230,8 @@ impl Session { self.state.read().unwrap().ratchet_states[0].chain_len() } /// Mark a session as expired. This will make it impossible for this session to successfully - /// receive or send data. It is recommended to simply `drop` the session instead, but this can - /// provide some reassurance in complex shared ownership situations. + /// 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(&self) { if let Some(context) = self.context.upgrade() { self.expire_inner(&context, &mut context.session_queue.lock().unwrap()); From 8468213814014b191e0c35d94af480517b379111 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 19 Jul 2023 18:12:29 -0400 Subject: [PATCH 04/50] fixed minor race condition --- src/zssp.rs | 252 +++++++++++++++++++++++++++------------------------- 1 file changed, 131 insertions(+), 121 deletions(-) diff --git a/src/zssp.rs b/src/zssp.rs index e1bed47..8896b6f 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -234,6 +234,7 @@ enum NoiseXKAliceHandshakeState { noise_message: [u8; NoiseXKPattern3::MAX_SIZE], noise_message_len: usize, }, + Rejected, } struct SessionKey { @@ -390,6 +391,7 @@ impl Context { Some(&session.header_send_cipher), ); } + NoiseXKAliceHandshakeState::Rejected => {} } } retry_next @@ -501,8 +503,12 @@ impl Context { let mut session_map = self.0.session_map.write().unwrap(); let local_key_id = generate_key_id(&session_map, &mut self.0.rng.lock().unwrap()); // Begin Noise XKhfs+psk2. - let (offer, a2b_header_key, b2a_header_key) = - NoiseXKAliceHandshake::::initialize(local_key_id, &remote_static_key, &ratchet_states, &mut self.0.rng.lock().unwrap())?; + let (offer, a2b_header_key, b2a_header_key) = NoiseXKAliceHandshake::::initialize( + local_key_id, + &remote_static_key, + &ratchet_states, + &mut self.0.rng.lock().unwrap(), + )?; let handshake_state = Box::new(NoiseXKAliceHandshake { next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), timeout: current_time.saturating_add(Application::INITIAL_OFFER_TIMEOUT_MS), @@ -558,9 +564,7 @@ impl Context { Ok(session) } - Err(e) => { - Err(OpenError::RatchetIoError(e)) - } + Err(e) => Err(OpenError::RatchetIoError(e)), } } @@ -681,7 +685,7 @@ impl Context { } // This error can occur naturally if Bob's initial reply to Alice had a // resend that was delayed massively and arrived out of order. - NoiseXKAliceHandshakeState::NoiseXKPattern3 { .. } => return Err(byzantine_fault!(FaultType::OutOfSequence, true)), + _ => return Err(byzantine_fault!(FaultType::OutOfSequence, true)), }, _ => return Err(byzantine_fault!(FaultType::OutOfSequence, false)), }; @@ -1519,114 +1523,114 @@ impl Context { let result = app.restore_by_identity(&remote_s_public_key, &application_data, current_time); match result { Ok(true_ratchet_states) => { - let mut has_match = false; - for rs in &true_ratchet_states { - if !rs.is_null() { - has_match |= &handshake_state.ratchet_state == rs; - } - } - if !has_match { - if !responder_disallows_downgrade && handshake_state.ratchet_state.is_empty() { - // TODO: add some kind of warning callback or signal. - } else { - if !responder_silently_rejects { - send_reject(); + let mut has_match = false; + for rs in &true_ratchet_states { + if !rs.is_null() { + has_match |= &handshake_state.ratchet_state == rs; } + } + if !has_match { + if !responder_disallows_downgrade && handshake_state.ratchet_state.is_empty() { + // TODO: add some kind of warning callback or signal. + } else { + if !responder_silently_rejects { + send_reject(); + } + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } + } + + let mut noise_kk_ss = Secret::new(); + if !self.0.static_keypair.agree(&remote_s_public_key, noise_kk_ss.as_mut()) { return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); } - } + let noise_kk_local_init_h = mix_hash(sha512, &INITIAL_H_REKEY, self.0.static_keypair.public_key_bytes()); + let noise_kk_local_init_h = mix_hash(sha512, &noise_kk_local_init_h, remote_s_public_key.as_bytes()); + let noise_kk_remote_init_h = mix_hash(sha512, &INITIAL_H_REKEY, remote_s_public_key.as_bytes()); + let noise_kk_remote_init_h = mix_hash(sha512, &noise_kk_remote_init_h, self.0.static_keypair.public_key_bytes()); - let mut noise_kk_ss = Secret::new(); - if !self.0.static_keypair.agree(&remote_s_public_key, noise_kk_ss.as_mut()) { - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - let noise_kk_local_init_h = mix_hash(sha512, &INITIAL_H_REKEY, self.0.static_keypair.public_key_bytes()); - let noise_kk_local_init_h = mix_hash(sha512, &noise_kk_local_init_h, remote_s_public_key.as_bytes()); - let noise_kk_remote_init_h = mix_hash(sha512, &INITIAL_H_REKEY, remote_s_public_key.as_bytes()); - let noise_kk_remote_init_h = mix_hash(sha512, &noise_kk_remote_init_h, self.0.static_keypair.public_key_bytes()); + let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_ee1peekem1pskpsp); + // We must make sure the ratchet key is saved before we transition. + let new_ratchet_state = + RatchetState::new_nonempty(rk, rf, NonZeroU64::new(handshake_state.ratchet_state.chain_len() + 1).unwrap()); + let result = app.save_ratchet_state( + &remote_s_public_key, + &application_data, + [&true_ratchet_states[0], &true_ratchet_states[1]], + [&new_ratchet_state, &RatchetState::Null], + current_time, + ); + if let Err(e) = result { + return Err(ReceiveError::RatchetIoError(e)); + } - let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_ee1peekem1pskpsp); - // We must make sure the ratchet key is saved before we transition. - let new_ratchet_state = - RatchetState::new_nonempty(rk, rf, NonZeroU64::new(handshake_state.ratchet_state.chain_len() + 1).unwrap()); - let result = app.save_ratchet_state( - &remote_s_public_key, - &application_data, - [&true_ratchet_states[0], &true_ratchet_states[1]], - [&new_ratchet_state, &RatchetState::Null], - current_time, - ); - if let Err(e) = result { + let mut session_queue = self.0.session_queue.lock().unwrap(); + let queue_idx = session_queue.reserve_index(); + let session = Arc::new(Session { + context: Arc::downgrade(&self.0), + queue_idx, + application_data, + remote_static_key: remote_s_public_key, + send_counter: AtomicU64::new(INIT_COUNTER), + session_has_expired: AtomicBool::new(false), + counter_antireplay_window: std::array::from_fn(|_| AtomicU64::new(0)), + state_machine_lock: Mutex::new(()), + state: RwLock::new(SessionMutableState { + ratchet_states: [new_ratchet_state.clone(), RatchetState::Null], + cipher_states: [ + Some(SessionKey::new( + hmac, + noise_ck, + handshake_state.local_key_id, + handshake_state.remote_key_id, + INIT_COUNTER, + true, + )), + None, + ], + current_key: 0, + outgoing_offer: KeyConfirm { + next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), + timeout: current_time.saturating_add(Application::EXPIRATION_TIMEOUT_MS), + }, + }), + header_receive_cipher: Application::PrpDec::new(handshake_state.header_receive_key.as_ref()), + header_send_cipher, + kex_send_cipher: Mutex::new(Some(Application::AeadEnc::new(kex_key_b2a.as_ref()))), + kex_receive_cipher: Mutex::new(Some(Application::AeadDec::new(kex_key_a2b.as_ref()))), + noise_kk_ss, + noise_kk_local_init_h, + noise_kk_remote_init_h, + defrag: std::array::from_fn(|_| Mutex::new(Fragged::new())), + was_bob: true, + }); + let timer = Reverse(current_time.saturating_add(Application::RETRY_INTERVAL_MS)); + session_queue.push_reserved(queue_idx, Arc::downgrade(&session), timer); + drop(session_queue); + // There is the miniscule possibility this key id is already + // in use, in which case we have to drop this session like + // nothing ever happened. + let mut session_map = self.0.session_map.write().unwrap(); + if let std::collections::hash_map::Entry::Vacant(e) = session_map.entry(handshake_state.local_key_id) { + e.insert((Arc::downgrade(&session), false)); + drop(session_map); + let _ = + session.send_control(&session.state.read().unwrap(), send_unassociated_reply, PACKET_TYPE_KEY_CONFIRM, &[]); + + app.event_log(LogEvent::ReceiveValidXK3(&session.application_data), current_time); + return Ok(ReceiveResult::Session(session, SessionEvent::NewSession)); + } else { + // This can occur if we accidentally generate a key id collision. + // There is an extremely short amount of time during which + // another session can steal this session's id, we'll have to + // restart the handshake in this case. + return Err(byzantine_fault!(FaultType::UnknownLocalKeyId, true)); + } + } + Err(e) => { return Err(ReceiveError::RatchetIoError(e)); } - - - let mut session_queue = self.0.session_queue.lock().unwrap(); - let queue_idx = session_queue.reserve_index(); - let session = Arc::new(Session { - context: Arc::downgrade(&self.0), - queue_idx, - application_data, - remote_static_key: remote_s_public_key, - send_counter: AtomicU64::new(INIT_COUNTER), - session_has_expired: AtomicBool::new(false), - counter_antireplay_window: std::array::from_fn(|_| AtomicU64::new(0)), - state_machine_lock: Mutex::new(()), - state: RwLock::new(SessionMutableState { - ratchet_states: [new_ratchet_state.clone(), RatchetState::Null], - cipher_states: [ - Some(SessionKey::new( - hmac, - noise_ck, - handshake_state.local_key_id, - handshake_state.remote_key_id, - INIT_COUNTER, - true, - )), - None, - ], - current_key: 0, - outgoing_offer: KeyConfirm { - next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), - timeout: current_time.saturating_add(Application::EXPIRATION_TIMEOUT_MS), - }, - }), - header_receive_cipher: Application::PrpDec::new(handshake_state.header_receive_key.as_ref()), - header_send_cipher, - kex_send_cipher: Mutex::new(Some(Application::AeadEnc::new(kex_key_b2a.as_ref()))), - kex_receive_cipher: Mutex::new(Some(Application::AeadDec::new(kex_key_a2b.as_ref()))), - noise_kk_ss, - noise_kk_local_init_h, - noise_kk_remote_init_h, - defrag: std::array::from_fn(|_| Mutex::new(Fragged::new())), - was_bob: true, - }); - let timer = Reverse(current_time.saturating_add(Application::RETRY_INTERVAL_MS)); - session_queue.push_reserved(queue_idx, Arc::downgrade(&session), timer); - drop(session_queue); - // There is the miniscule possibility this key id is already - // in use, in which case we have to drop this session like - // nothing ever happened. - let mut session_map = self.0.session_map.write().unwrap(); - if let std::collections::hash_map::Entry::Vacant(e) = session_map.entry(handshake_state.local_key_id) { - e.insert((Arc::downgrade(&session), false)); - drop(session_map); - let _ = session.send_control(&session.state.read().unwrap(), send_unassociated_reply, PACKET_TYPE_KEY_CONFIRM, &[]); - - app.event_log(LogEvent::ReceiveValidXK3(&session.application_data), current_time); - return Ok(ReceiveResult::Session(session, SessionEvent::NewSession)); - } else { - // This can occur if we accidentally generate a key id collision. - // There is an extremely short amount of time during which - // another session can steal this session's id, we'll have to - // restart the handshake in this case. - return Err(byzantine_fault!(FaultType::UnknownLocalKeyId, true)); - } } - Err(e) => { - return Err(ReceiveError::RatchetIoError(e)); - } - } } else { if !responder_silently_rejects { send_reject(); @@ -1829,6 +1833,7 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu fragment: &mut [u8], current_time: i64, ) -> Result, ReceiveError> { + let kex_lock = session.state_machine_lock.lock().unwrap(); let state = session.state.read().unwrap(); let mut c = session.kex_receive_cipher.lock().unwrap(); let message = decrypt_control( @@ -1841,17 +1846,25 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu session.update_receive_window(counter); use OfferStateMachine::*; return match packet_type { - PACKET_TYPE_SESSION_REJECTED => match &state.outgoing_offer { - NoiseXKPattern1or3(_) => { + PACKET_TYPE_SESSION_REJECTED => { + if let NoiseXKPattern1or3(_) = &state.outgoing_offer { drop(state); + let mut state = session.state.write().unwrap(); + if let NoiseXKPattern1or3(state) = &mut state.outgoing_offer { + // We have to make sure that a different thread cannot receive + // `PACKET_TYPE_KEY_CONFIRM` at the same time. + state.offer = NoiseXKAliceHandshakeState::Rejected; + } + drop(state); + drop(kex_lock); Ok(ReceiveResult::Session(session, SessionEvent::Rejected)) + } else { + Err(byzantine_fault!(FaultType::OutOfSequence, false)) } - _ => Err(byzantine_fault!(FaultType::OutOfSequence, false)), - }, + } PACKET_TYPE_KEY_CONFIRM => { drop(state); app.event_log(LogEvent::ReceiveValidKeyConfirm(&session), current_time); - let kex_lock = session.state_machine_lock.lock().unwrap(); let mut state = session.state.write().unwrap(); // We only want to stop sending NoiseKKPattern2 offers when the latest derived // key is confirmed. And we only want to do that once. @@ -1904,7 +1917,6 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu PACKET_TYPE_ACK => { drop(state); app.event_log(LogEvent::ReceiveValidAck(&session), current_time); - let kex_lock = session.state_machine_lock.lock().unwrap(); let mut state = session.state.write().unwrap(); // Check if we should end any current offers and transition back to Normal state if let KeyConfirm { .. } = &state.outgoing_offer { @@ -1918,10 +1930,6 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu app.event_log(LogEvent::ReceiveUncheckedKK1, current_time); let message = &mut message[..NoiseKKPattern1or2::SIZE]; let noise_pattern1: &NoiseKKPattern1or2 = byte_array_as_proto_buffer(message); - - drop(state); - let kex_lock = session.state_machine_lock.lock().unwrap(); - let state = session.state.read().unwrap(); // We need the following operation to be atomic with the change of offer type let (should_rekey_as_bob, chosen_id) = match &state.outgoing_offer { // Check rekey rate limits. @@ -2072,9 +2080,6 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu let message = &mut message[..NoiseKKPattern1or2::SIZE]; let noise_pattern2: &NoiseKKPattern1or2 = byte_array_as_proto_buffer(message); - drop(state); - let kex_lock = session.state_machine_lock.lock().unwrap(); - let state = session.state.read().unwrap(); if let NoiseKKPattern1 { new_key_id, noise_e_secret, noise_ck, noise_h_pskep, .. } = &state.outgoing_offer { // Noise process pattern2 e token. let mut noise_ee = Secret::new(); @@ -2496,7 +2501,7 @@ fn encrypt_control( let fragment_len = packet.len() + HEADER_SIZE + AES_GCM_TAG_SIZE; c.set_iv(&create_message_nonce(packet_type, counter)); - if !packet.is_empty(){ + if !packet.is_empty() { fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE].copy_from_slice(packet); c.encrypt_in_place(&mut fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE]); } @@ -2506,7 +2511,12 @@ fn encrypt_control( (fragment, fragment_len) } #[inline] -fn decrypt_control<'a, IoError>(c: &mut impl AesGcmDec, packet_type: u8, counter: u64, fragment: &'a mut [u8]) -> Result<&'a mut [u8], ReceiveError> { +fn decrypt_control<'a, IoError>( + c: &mut impl AesGcmDec, + packet_type: u8, + counter: u64, + fragment: &'a mut [u8], +) -> Result<&'a mut [u8], ReceiveError> { let fragment_len = fragment.len(); if !(CONTROL_PACKET_MIN_SIZE..=CONTROL_PACKET_MAX_SIZE).contains(&fragment_len) { return Err(byzantine_fault!(FaultType::InvalidPacket, false)); From 712fb8b2de0d40a65a2c2e49b34c0252abd8e3cd Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 19 Jul 2023 19:44:41 -0400 Subject: [PATCH 05/50] improved multithreading --- src/zssp.rs | 53 +++++++++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/src/zssp.rs b/src/zssp.rs index 8896b6f..8375e48 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -192,6 +192,7 @@ enum OfferStateMachine { next_retry_time: AtomicI64, timeout: i64, }, // -> Normal + Null, } pub(crate) struct NoiseXKBobHandshakeState { @@ -234,7 +235,6 @@ enum NoiseXKAliceHandshakeState { noise_message: [u8; NoiseXKPattern3::MAX_SIZE], noise_message_len: usize, }, - Rejected, } struct SessionKey { @@ -352,15 +352,13 @@ impl Context { if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { if handshake_state.timeout <= current_time { app.event_log(LogEvent::ServiceXKTimeout(&session), current_time); - if !handshake_state.reinitialize( + handshake_state.reinitialize( &session, &ratchet_state, &mut self.0.session_map.write().unwrap(), &mut self.0.rng.lock().unwrap(), current_time, - ) { - session.expire_inner(&self.0, &mut session_queue); - } + ); } } } else if let Some((mut send, mut mtu)) = send_to(&session) { @@ -391,7 +389,6 @@ impl Context { Some(&session.header_send_cipher), ); } - NoiseXKAliceHandshakeState::Rejected => {} } } retry_next @@ -404,6 +401,7 @@ impl Context { if *timeout <= current_time { app.event_log(LogEvent::ServiceKKTimeout(&session), current_time); next_retry_time.store(i64::MAX, Ordering::Relaxed); + drop(state); session.expire_inner(&self.0, &mut session_queue); } else { let packet_type = if let NoiseKKPattern1 { .. } = &state.outgoing_offer { @@ -427,6 +425,7 @@ impl Context { if *timeout <= current_time { app.event_log(LogEvent::ServiceKeyConfirmTimeout(&session), current_time); next_retry_time.store(i64::MAX, Ordering::Relaxed); + drop(state); session.expire_inner(&self.0, &mut session_queue); } else { app.event_log(LogEvent::ServiceKeyConfirmResend(&session), current_time); @@ -437,6 +436,7 @@ impl Context { retry_next } } + Null => retry_next, }; session_queue.change_priority(queue_idx, Reverse(next_timer)); } @@ -1850,16 +1850,13 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu if let NoiseXKPattern1or3(_) = &state.outgoing_offer { drop(state); let mut state = session.state.write().unwrap(); - if let NoiseXKPattern1or3(state) = &mut state.outgoing_offer { - // We have to make sure that a different thread cannot receive - // `PACKET_TYPE_KEY_CONFIRM` at the same time. - state.offer = NoiseXKAliceHandshakeState::Rejected; - } + state.outgoing_offer = OfferStateMachine::Null; drop(state); drop(kex_lock); Ok(ReceiveResult::Session(session, SessionEvent::Rejected)) } else { - Err(byzantine_fault!(FaultType::OutOfSequence, false)) + // This can occur naturally because of control packet resends. + Err(byzantine_fault!(FaultType::OutOfSequence, true)) } } PACKET_TYPE_KEY_CONFIRM => { @@ -1877,6 +1874,7 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu (false, false, SessionEvent::Control) } } + Null => (false, false, SessionEvent::Control), _ => (true, false, SessionEvent::Control), }; if try_delete { @@ -1915,16 +1913,19 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu Ok(ReceiveResult::Session(session, ret)) } PACKET_TYPE_ACK => { - drop(state); - app.event_log(LogEvent::ReceiveValidAck(&session), current_time); - let mut state = session.state.write().unwrap(); - // Check if we should end any current offers and transition back to Normal state if let KeyConfirm { .. } = &state.outgoing_offer { + drop(state); + app.event_log(LogEvent::ReceiveValidAck(&session), current_time); + let mut state = session.state.write().unwrap(); + // Check if we should end any current offers and transition back to Normal state state.outgoing_offer = new_normal_state(context.0.rng.lock().unwrap().next_u64(), current_time); + drop(kex_lock); + drop(state); + Ok(ReceiveResult::Session(session, SessionEvent::Control)) + } else { + // This can occur naturally because of control packet resends. + Err(byzantine_fault!(FaultType::OutOfSequence, true)) } - drop(state); - drop(kex_lock); - Ok(ReceiveResult::Session(session, SessionEvent::Control)) } PACKET_TYPE_NOISE_KK_PATTERN_1 => { app.event_log(LogEvent::ReceiveUncheckedKK1, current_time); @@ -2215,7 +2216,7 @@ impl Session { #[inline] pub fn established(&self) -> bool { let state = self.state.read().unwrap(); - !matches!(&state.outgoing_offer, OfferStateMachine::NoiseXKPattern1or3(_)) + !matches!(&state.outgoing_offer, OfferStateMachine::NoiseXKPattern1or3(_) | OfferStateMachine::Null) } /// The static public key of the remote peer. #[inline] @@ -2251,7 +2252,7 @@ impl Session { session_queue.remove(self.queue_idx); self.session_has_expired.store(true, Ordering::Relaxed); let _kex_lock = self.state_machine_lock.lock().unwrap(); - let state = self.state.read().unwrap(); + let mut state = self.state.write().unwrap(); let mut session_map = context.session_map.write().unwrap(); for key in &state.cipher_states { if let Some(pre_id) = key.as_ref().map(|k| k.local_key_id) { @@ -2259,12 +2260,12 @@ impl Session { } } use OfferStateMachine::*; - let id = match &state.outgoing_offer { - NoiseXKPattern1or3(handshake_state) => handshake_state.local_key_id, - NoiseKKPattern1 { new_key_id, .. } => *new_key_id, - _ => return, + match &state.outgoing_offer { + NoiseXKPattern1or3(handshake_state) => session_map.remove(&handshake_state.local_key_id), + NoiseKKPattern1 { new_key_id, .. } => session_map.remove(new_key_id), + _ => None, }; - session_map.remove(&id); + state.outgoing_offer = OfferStateMachine::Null; } /// Get the next outgoing counter value. From 20736ee1088238b1e55ce015e7fb9317a94e29f7 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Sat, 22 Jul 2023 11:50:44 -0400 Subject: [PATCH 06/50] made was_bob pub --- src/zssp.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/zssp.rs b/src/zssp.rs index 8375e48..366307f 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -117,6 +117,8 @@ pub enum IncomingSessionAction { pub struct Session { /// An arbitrary application defined object associated with each session. pub application_data: Application::Data, + /// Is true if the local peer acted as Bob, the responder in the initial key exchange. + pub was_bob: bool, /// The receive context associated with this session, /// only this context can receive messages from the remote peer. context: Weak>, @@ -145,7 +147,6 @@ pub struct Session { noise_kk_ss: Secret, noise_kk_local_init_h: [u8; NOISE_HASHLEN], noise_kk_remote_init_h: [u8; NOISE_HASHLEN], - was_bob: bool, } /// `AesGcm` is not threadsafe, but it is threadsafe when inside a `Mutex`. unsafe impl Send for Session {} From a7f814e15dab137d800187dcdcbb5e001236775b Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 25 Jul 2023 10:37:40 -0400 Subject: [PATCH 07/50] fixed an old test --- src/frag_cache.rs | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/frag_cache.rs b/src/frag_cache.rs index ebb428e..62ddfc0 100644 --- a/src/frag_cache.rs +++ b/src/frag_cache.rs @@ -234,9 +234,20 @@ impl Drop for UnassociatedFragCache { } } -/* #[test] fn test_cache() { + use std::sync::Mutex; + fn xorshift64_random() -> u64 { + static XORSHIFT64_STATE: Mutex = Mutex::new(12); + let mut x = XORSHIFT64_STATE.lock().unwrap(); + *x ^= x.wrapping_shr(12); + *x ^= x.wrapping_shl(25); + *x ^= x.wrapping_shr(27); + let r = *x; + drop(x); + r.wrapping_mul(0x2545F4914F6CDD1Du64) + } + let mut cache = UnassociatedFragCache::new(); let mut assembled = Assembled::new(); @@ -245,8 +256,8 @@ fn test_cache() { let mut in_progress_fragments = 0; // A basic fuzzer for testing the cache. for i in 0..5000u32 { - let fragment_count = (random::xorshift64_random() as usize % MAX_FRAGMENTS) + 1; - let r = random::xorshift64_random() as u8; + let fragment_count = (xorshift64_random() as usize % MAX_FRAGMENTS) + 1; + let r = xorshift64_random() as u8; if r & 1 == 0 { let mut packet = Vec::new(); for j in 0..fragment_count { @@ -256,7 +267,7 @@ fn test_cache() { in_progress.push((i, fragment_count as u8, packet)); } else { assembled.empty(); - let drop = random::xorshift64_random() as usize % (2 * fragment_count); + let drop = xorshift64_random() as usize % (2 * fragment_count); for j in 0..fragment_count { if drop != j { let fragment = vec![0, 1, 2, 3, 4, 5, 6, r]; @@ -279,11 +290,11 @@ fn test_cache() { } if r > 200 { if in_progress.len() > 0 { - let to_remain = (random::xorshift64_random() as usize % in_progress_fragments) + 16; + let to_remain = (xorshift64_random() as usize % in_progress_fragments) + 16; while in_progress_fragments > to_remain { - let (id, fragment_count, mut packet) = in_progress.swap_remove(random::xorshift64_random() as usize % in_progress.len()); - for _ in 0..((random::xorshift64_random() as usize % packet.len()) + 1) { - let (no, fragment) = packet.swap_remove(random::xorshift64_random() as usize % packet.len()); + let (id, fragment_count, mut packet) = in_progress.swap_remove(xorshift64_random() as usize % in_progress.len()); + for _ in 0..((xorshift64_random() as usize % packet.len()) + 1) { + let (no, fragment) = packet.swap_remove(xorshift64_random() as usize % packet.len()); assembled.empty(); let mut nonce = [0; 10]; @@ -304,4 +315,3 @@ fn test_cache() { } } } - */ From 0ea6935b6e715c0dcf8e59e6bc9ec6d020ebf9a4 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 25 Jul 2023 11:33:27 -0400 Subject: [PATCH 08/50] switched to try_into syntax --- src/zssp.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/zssp.rs b/src/zssp.rs index 366307f..77b4f9f 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -628,8 +628,7 @@ impl Context { let mut assembled_packet = Assembled::new(); // needs to outlive the block below let mut incoming = None; let (session, packet_type, fragments) = { - let mut local_key_id = [0u8; SESSION_ID_SIZE]; - local_key_id.copy_from_slice(&incoming_physical_packet[0..SESSION_ID_SIZE]); + let local_key_id = incoming_physical_packet[0..SESSION_ID_SIZE].try_into().unwrap(); // `from_ne_bytes` because this id was generated locally. if let Some(local_key_id) = NonZeroU32::new(u32::from_ne_bytes(local_key_id)) { let session_map = self.0.session_map.read().unwrap(); @@ -902,9 +901,7 @@ impl Context { IncomingSessionAction::Allow => {} IncomingSessionAction::Challenge => { let response: &ChallengeResponse = byte_array_as_proto_buffer(&message[p_auth_end..message_size]); - let mut counter = 0u64.to_ne_bytes(); - counter.copy_from_slice(&response.challenge_counter); - let counter = u64::from_be_bytes(counter); + let counter = u64::from_be_bytes(response.challenge_counter.try_into().unwrap()); sha512.reset(); let mut hasher = ShaHasher(sha512); @@ -2572,10 +2569,8 @@ fn create_message_nonce(packet_type: u8, counter: u64) -> [u8; AES_GCM_IV_SIZE] /// returns `(fragment_count, fragment_no, packet_type, counter, header_nonce)`. #[inline(always)] fn parse_packet_header(packet: &[u8]) -> (u8, u8, u8, u64, [u8; 10]) { - let mut header_nonce = [0; 10]; - let mut counter = 0u64.to_ne_bytes(); - header_nonce.copy_from_slice(&packet[6..16]); - counter.copy_from_slice(&packet[8..16]); + let header_nonce = packet[6..16].try_into().unwrap(); + let counter = packet[8..16].try_into().unwrap(); // We intentionally ignore the version number for future revisions. (packet[4], packet[5], packet[7], u64::from_be_bytes(counter), header_nonce) } From e137ecc209ff254c08276abc36edfeb2a80904a6 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 25 Jul 2023 11:34:19 -0400 Subject: [PATCH 09/50] added volatile write destruction --- src/crypto/secret.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/crypto/secret.rs b/src/crypto/secret.rs index 60399b3..461470f 100644 --- a/src/crypto/secret.rs +++ b/src/crypto/secret.rs @@ -1,5 +1,5 @@ // (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. -use std::convert::TryInto; +use std::{convert::TryInto, ptr::write_volatile}; /// Constant time byte slice equality. #[inline] @@ -88,14 +88,18 @@ impl Secret { impl Drop for Secret { fn drop(&mut self) { - self.0.fill(0); + unsafe { + for v in self.0.iter_mut() { + write_volatile(v, 0u8); + } + } } } impl Default for Secret { #[inline(always)] fn default() -> Self { - Self([0_u8; L]) + Self([0u8; L]) } } From 4a038f4e2c5ae2bf17d8b85704bf388f9e1d0bbb Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 25 Jul 2023 11:34:29 -0400 Subject: [PATCH 10/50] added docs --- src/crypto/aes_gcm.rs | 16 ++++++++++++++++ src/crypto/p384.rs | 16 +++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/crypto/aes_gcm.rs b/src/crypto/aes_gcm.rs index 250c587..303280e 100644 --- a/src/crypto/aes_gcm.rs +++ b/src/crypto/aes_gcm.rs @@ -4,6 +4,14 @@ pub const AES_GCM_TAG_SIZE: usize = 16; pub const AES_GCM_IV_SIZE: usize = 12; pub const AES_GCM_KEY_SIZE: usize = super::aes::AES_256_KEY_SIZE; +/// The order of calls to this trait is always +/// `set_iv` -> `set_aad` -> `encrypt` -> `finish_encrypt`. +/// `set_aad` and `encrypt` may not always be called. `encrypt_in_place` may be called instead of +/// `encrypt`. `encrypt` may be called multiple times, it should start encryption of the input at the point in +/// the keystream where encryption previously ended. +/// An instance of this trait may be reused multiple times, each reuse should use the same key. +/// If it is reused, `set_iv` will always be the very next call after `finish_encrypt`. +/// /// Implementations of this trait does not have to be Send + Sync, /// but if it is wrapped in a `Mutex` it must satisfy the requirements of Send + Sync. pub trait AesGcmEnc { @@ -20,6 +28,14 @@ pub trait AesGcmEnc { fn finish_encrypt(&mut self, output: &mut [u8; AES_GCM_TAG_SIZE]); } +/// The order of calls to this trait is always +/// `set_iv` -> `set_aad` -> `decrypt` -> `finish_decrypt`. +/// `set_aad` and `decrypt` may not always be called. `decrypt_in_place` may be called instead of +/// `decrypt`. `decrypt` may be called multiple times, it should start decryption of the input at the point in +/// the keystream where decryption previously ended. +/// An instance of this trait may be reused multiple times, each reuse should use the same key. +/// If it is reused, `set_iv` will always be the very next call after `finish_decrypt`. +/// /// Implementations of this trait does not have to be Send + Sync, /// but if it is wrapped in a `Mutex` it must satisfy the requirements of Send + Sync. pub trait AesGcmDec { diff --git a/src/crypto/p384.rs b/src/crypto/p384.rs index 2a14067..f524972 100644 --- a/src/crypto/p384.rs +++ b/src/crypto/p384.rs @@ -8,9 +8,14 @@ pub const P384_ECDH_SHARED_SECRET_SIZE: usize = 48; /// A NIST P-384 ECDH/ECDSA public key. pub trait P384PublicKey: Sized + Send + Sync { /// Create a p384 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. 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 standard 49 byte NIST encoding of P384 public keys. fn as_bytes(&self) -> &[u8; P384_PUBLIC_KEY_SIZE]; } @@ -24,8 +29,17 @@ pub trait P384KeyPair: Send + Sync { fn generate(rng: &mut Self::Rng) -> Self; /// Get the raw bytes that uniquely define the public key. + /// + /// This must output the standard 49 byte NIST encoding of P384 public keys. fn public_key_bytes(&self) -> &[u8; P384_PUBLIC_KEY_SIZE]; - /// Perform ECDH key agreement, returning the raw (un-hashed!) ECDH secret. + /// Perform ECDH key agreement, writing the raw (un-hashed!) ECDH secret to `output`. + /// + /// **CRITICAL**: This function must return `false` if key agreement between this private key and + /// the input `other_public` 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 + /// library that has already implemented this correctly. + /// + /// If this function returns `false` then the contents of `output` will be discarded. fn agree(&self, other_public: &Self::PublicKey, output: &mut [u8; P384_ECDH_SHARED_SECRET_SIZE]) -> bool; } From 8b1105f36f14a3fc9331b57223651639c581673d Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 25 Jul 2023 12:00:41 -0400 Subject: [PATCH 11/50] added docs --- src/crypto/aes.rs | 18 ++++++++++++++++++ src/crypto/aes_gcm.rs | 12 ++++++++---- src/crypto/p384.rs | 2 ++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/crypto/aes.rs b/src/crypto/aes.rs index 842e1ed..637d009 100644 --- a/src/crypto/aes.rs +++ b/src/crypto/aes.rs @@ -3,18 +3,36 @@ pub const AES_256_BLOCK_SIZE: usize = 16; pub const AES_256_KEY_SIZE: usize = 32; +/// 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. +/// +/// Instances must securely delete their keys when dropped or reset. pub trait AesEnc: Send + Sync { fn new(key: &[u8; AES_256_KEY_SIZE]) -> Self; + /// Change the encryption key to `key` so that all future encryption is performed with it. + /// This function is very rarely called so it does not have to be particularly efficient. fn reset(&self, key: &[u8; AES_256_KEY_SIZE]); + /// Decrypt 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`. fn encrypt_in_place(&self, block: &mut [u8; AES_256_BLOCK_SIZE]); } +/// A trait for decrypting individual blocks of plaintext using AES-256. +/// +/// Instances must securely delete their keys when dropped or reset. pub trait AesDec: Send + Sync { fn new(key: &[u8; AES_256_KEY_SIZE]) -> Self; + /// Change the decryption key to `key` so that all future decryption is performed with it. + /// This function is very rarely called so it does not have to be particularly efficient. fn reset(&self, key: &[u8; AES_256_KEY_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`. fn decrypt_in_place(&self, block: &mut [u8; AES_256_BLOCK_SIZE]); } diff --git a/src/crypto/aes_gcm.rs b/src/crypto/aes_gcm.rs index 303280e..c8328ed 100644 --- a/src/crypto/aes_gcm.rs +++ b/src/crypto/aes_gcm.rs @@ -12,8 +12,10 @@ pub const AES_GCM_KEY_SIZE: usize = super::aes::AES_256_KEY_SIZE; /// An instance of this trait may be reused multiple times, each reuse should use the same key. /// If it is reused, `set_iv` will always be the very next call after `finish_encrypt`. /// -/// Implementations of this trait does not have to be Send + Sync, -/// but if it is wrapped in a `Mutex` it must satisfy the requirements of Send + Sync. +/// Implementations of this trait do not have to be Send + Sync, +/// but if instances are wrapped in a `Mutex` they must satisfy the requirements of Send + Sync. +/// +/// Instances must securely delete their keys when dropped. pub trait AesGcmEnc { fn new(key: &[u8; AES_GCM_KEY_SIZE]) -> Self; @@ -36,8 +38,10 @@ pub trait AesGcmEnc { /// An instance of this trait may be reused multiple times, each reuse should use the same key. /// If it is reused, `set_iv` will always be the very next call after `finish_decrypt`. /// -/// Implementations of this trait does not have to be Send + Sync, -/// but if it is wrapped in a `Mutex` it must satisfy the requirements of Send + Sync. +/// Implementations of this trait do not have to be Send + Sync, +/// but if instances are wrapped in a `Mutex` they must satisfy the requirements of Send + Sync. +/// +/// Instances must securely delete their keys when dropped. pub trait AesGcmDec { fn new(key: &[u8; AES_GCM_KEY_SIZE]) -> Self; diff --git a/src/crypto/p384.rs b/src/crypto/p384.rs index f524972..d340356 100644 --- a/src/crypto/p384.rs +++ b/src/crypto/p384.rs @@ -20,6 +20,8 @@ pub trait P384PublicKey: Sized + Send + Sync { } /// A NIST P-384 ECDH/ECDSA public/private key pair. +/// +/// Instances must securely delete the private key when dropped. pub trait P384KeyPair: Send + Sync { type PublicKey: P384PublicKey; type Rng: RngCore + CryptoRng; From 4e7e33b468eeb0b42b77f256935badc170533934 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 25 Jul 2023 12:23:53 -0400 Subject: [PATCH 12/50] removed inline --- src/applicationlayer.rs | 1 - src/fragged.rs | 6 ------ src/indexed_heap.rs | 1 - src/ratchet_state.rs | 8 -------- src/symmetric_state.rs | 5 ----- src/zssp.rs | 29 ----------------------------- 6 files changed, 50 deletions(-) diff --git a/src/applicationlayer.rs b/src/applicationlayer.rs index 0485f7f..ff0a957 100644 --- a/src/applicationlayer.rs +++ b/src/applicationlayer.rs @@ -191,6 +191,5 @@ pub trait ApplicationLayer: Sized { ) -> Result<(), Self::IoError>; #[allow(unused)] - #[inline] fn event_log(&self, event: LogEvent, current_time: i64) {} } diff --git a/src/fragged.rs b/src/fragged.rs index a9eb2cc..77ee095 100644 --- a/src/fragged.rs +++ b/src/fragged.rs @@ -30,13 +30,11 @@ impl Assembled { } } impl AsRef<[Fragment]> for Assembled { - #[inline(always)] fn as_ref(&self) -> &[Fragment] { unsafe { &*slice_from_raw_parts(self.0.as_ptr().cast::(), self.1) } } } impl Drop for Assembled { - #[inline(always)] fn drop(&mut self) { self.empty() } @@ -52,7 +50,6 @@ pub struct Fragged { } impl Fragged { - #[inline(always)] pub fn new() -> Self { debug_assert!(MAX_FRAGMENTS <= 64); unsafe { zeroed() } @@ -64,7 +61,6 @@ impl Fragged { /// be reused to assemble another packet. /// /// Will check that aad is the same for all fragments. - #[inline] pub(crate) fn assemble( &mut self, nonce: [u8; 10], @@ -107,7 +103,6 @@ impl Fragged { } /// Drops any remaining fragments and resets this object. - #[inline(always)] pub fn drop_in_place(&mut self) { if needs_drop::() { let mut have = self.have; @@ -129,7 +124,6 @@ impl Fragged { } impl Drop for Fragged { - #[inline(always)] fn drop(&mut self) { self.drop_in_place(); } diff --git a/src/indexed_heap.rs b/src/indexed_heap.rs index 8a0547c..70085dc 100644 --- a/src/indexed_heap.rs +++ b/src/indexed_heap.rs @@ -42,7 +42,6 @@ impl IndexedBinaryHeap { .first_mut() .map(|entry| (&mut entry.0, &entry.1, BinaryHeapIndex(entry.2, self.map[entry.2].1))) } - #[inline] fn swap(&mut self, a: usize, b: usize) { self.map[self.data[a].2].0 = b; self.map[self.data[b].2].0 = a; diff --git a/src/ratchet_state.rs b/src/ratchet_state.rs index fddbc57..273a358 100644 --- a/src/ratchet_state.rs +++ b/src/ratchet_state.rs @@ -19,38 +19,30 @@ pub enum RatchetState { } use RatchetState::*; impl RatchetState { - #[inline] pub fn new_nonempty(key: Secret, fingerprint: Secret, chain_len: NonZeroU64) -> Self { NonEmpty(NonEmptyRatchetState { key, fingerprint, chain_len }) } - #[inline] pub fn new_initial_states() -> [RatchetState; 2] { [RatchetState::Empty, RatchetState::Null] } - #[inline] pub fn is_null(&self) -> bool { matches!(self, Null) } - #[inline] pub fn is_empty(&self) -> bool { matches!(self, Empty) } - #[inline] pub fn nonempty(&self) -> Option<&NonEmptyRatchetState> { match self { NonEmpty(rs) => Some(rs), _ => None, } } - #[inline] pub fn chain_len(&self) -> u64 { self.nonempty().map_or(0, |rs| rs.chain_len.get()) } - #[inline] pub fn fingerprint(&self) -> Option<&[u8; RATCHET_SIZE]> { self.nonempty().map(|rs| rs.fingerprint.as_ref()) } - #[inline] pub fn key(&self) -> Option<&[u8; RATCHET_SIZE]> { const ZERO_KEY: [u8; RATCHET_SIZE] = [0u8; RATCHET_SIZE]; match self { diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index 6f6ac6f..e6e5e52 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -33,7 +33,6 @@ impl SymmetricState { // `InitializeKey` would be completely pointless. } /// Corresponds to Noise `MixKey` followed by `InitializeKey`. - #[inline(always)] pub(crate) fn mix_key_initialize_key(&mut self, hm: &mut impl HmacSha512, input_key_material: &[u8]) -> Secret { let mut next_ck = Secret::new(); let mut temp_k = [0u8; NOISE_HASHLEN]; @@ -83,7 +82,6 @@ impl SymmetricState { /// is forward secrect and is cryptographically independent from all other produced keys. /// Based on Noise's unstable ASK mechanism, using KBKDF instead of HKDF. /// https://github.com/noiseprotocol/noise_wiki/wiki/Additional-Symmetric-Keys. - #[inline(always)] pub(crate) fn get_ask2( &self, hm: &mut impl HmacSha512, @@ -99,7 +97,6 @@ impl SymmetricState { ) } /// Corresponds to Noise `Split`. - #[inline(always)] pub(crate) fn split(self, hm: &mut impl HmacSha512) -> (Secret, Secret) { let mut temp_k1 = [0u8; NOISE_HASHLEN]; let mut temp_k2 = [0u8; NOISE_HASHLEN]; @@ -111,7 +108,6 @@ impl SymmetricState { Secret::from_bytes_then_delete(&mut temp_k2[..AES_256_KEY_SIZE]), ) } - #[inline(always)] fn label(&self) -> [u8; 4] { [b'Z', b'S', b'S', self.token_counter] } @@ -126,7 +122,6 @@ impl SymmetricState { /// * L = `num_outputs*512u16` /// We have intentionally made every input small and fixed size to avoid unnecessary complexity /// and data representation ambiguity. - #[inline(always)] fn kbkdf( &self, hm: &mut impl HmacSha512, diff --git a/src/zssp.rs b/src/zssp.rs index 77b4f9f..98f9a3f 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -292,7 +292,6 @@ impl Context { /// 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. - #[inline] pub fn service bool>( &self, app: &Application, @@ -471,7 +470,6 @@ impl Context { /// for the upper protocol to authenticate and approve of Alice's identity. /// * `current_time` - Current time in milliseconds. Does not have to be monotonic, nor synced /// with the remote peer. Used to determine when this offer should be resent. - #[inline] pub fn open( &self, app: &Application, @@ -600,7 +598,6 @@ impl Context { /// * `current_time` - Current time in milliseconds. Does not have to be monotonic, nor synced /// with the remote peer. Used to check the state of local offers we may currently have or want /// to put in-flight. - #[inline] pub fn receive<'a, SendFn: FnMut(&mut [u8]) -> bool>( &self, app: &Application, @@ -1648,7 +1645,6 @@ impl Context { /// * `send` - Function to call to send physical packet(s); the buffer passed to `send` is a /// slice of `data` /// * `current_time` - Current time in milliseconds - #[inline] pub fn send_empty(&self, session: &Arc>, send: impl FnMut(&mut [u8]) -> bool, current_time: i64) -> Result<(), SendError> { self.send(session, send, &mut [0u8; MIN_TRANSPORT_MTU], &[], current_time) } @@ -1660,7 +1656,6 @@ impl Context { /// * `mtu_sized_buffer` - A writable work buffer whose size equals the MTU /// * `data` - Data to send /// * `current_time` - Current time in milliseconds - #[inline] pub fn send( &self, session: &Arc>, @@ -1730,7 +1725,6 @@ impl Context { Ok(()) } /// Update the challenge window, returning true if the challenge is still valid. - #[inline(always)] fn check_challenge_window(&self, counter: u64) -> bool { let slot = &self.0.challenge_antireplay_window[(counter as usize) % self.0.challenge_antireplay_window.len()]; let counter = counter.wrapping_add(1); @@ -1738,7 +1732,6 @@ impl Context { prev_counter < counter } /// Update the challenge window, returning true if the challenge is still valid. - #[inline(always)] fn update_challenge_window(&self, counter: u64) -> bool { let slot = &self.0.challenge_antireplay_window[(counter as usize) % self.0.challenge_antireplay_window.len()]; let counter = counter.wrapping_add(1); @@ -2188,7 +2181,6 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu impl Session { /// This can only fail with `MaxKeyLifetimeExceeded` or `SessionNotEstablished`. - #[inline] fn send_control( &self, state: &SessionMutableState, @@ -2211,25 +2203,21 @@ impl Session { Ok(()) } /// Check whether this session is established. - #[inline] pub fn established(&self) -> bool { let state = self.state.read().unwrap(); !matches!(&state.outgoing_offer, OfferStateMachine::NoiseXKPattern1or3(_) | OfferStateMachine::Null) } /// The static public key of the remote peer. - #[inline] pub fn remote_s_public_key(&self) -> &Application::PublicKey { &self.remote_static_key } /// The current ratchet state of this session. /// The returned values are sensitive and should be securely erased before being dropped. - #[inline] pub fn ratchet_states(&self) -> [RatchetState; 2] { let state = self.state.read().unwrap(); state.ratchet_states.clone() } /// The current ratchet count of this session. - #[inline] pub fn ratchet_count(&self) -> u64 { self.state.read().unwrap().ratchet_states[0].chain_len() } @@ -2267,7 +2255,6 @@ impl Session { } /// Get the next outgoing counter value. - #[inline(always)] fn get_next_outgoing_counter(&self) -> Result { if self.session_has_expired.load(Ordering::Relaxed) { Err(SendError::SessionExpired) @@ -2283,7 +2270,6 @@ impl Session { } } /// Check the receive window without mutating state. - #[inline(always)] fn check_receive_window(&self, counter: u64) -> bool { let slot = &self.counter_antireplay_window[(counter as usize) % self.counter_antireplay_window.len()]; let counter = counter.wrapping_add(1); @@ -2292,7 +2278,6 @@ impl Session { } /// Update the receive window, returning true if the packet is still valid. /// This should only be called after the packet is authenticated. - #[inline(always)] fn update_receive_window(&self, counter: u64) -> bool { let slot = &self.counter_antireplay_window[(counter as usize) % self.counter_antireplay_window.len()]; let counter = counter.wrapping_add(1); @@ -2311,7 +2296,6 @@ impl Drop for Session { impl NoiseXKAliceHandshake { /// Can only fail with `OpenError::InvalidPublicKey` because of remote_s_public_key. /// Corresponds to Noise `Initialize`. - #[inline] fn initialize( local_key_id: NonZeroU32, remote_s_public_key: &Application::PublicKey, @@ -2446,7 +2430,6 @@ fn process_timer(timer: &AtomicI64, wait_time: i64, current_time: i64) -> Option } /// Corresponds to Noise `EncryptAndHash`. -#[inline] fn encrypt_and_hash( sha512: &mut Application::Hash, noise_k: &Secret, @@ -2467,7 +2450,6 @@ fn encrypt_and_hash( mix_hash(sha512, noise_h, message) } /// Corresponds to Noise `DecryptAndHash`. -#[inline] fn decrypt_and_hash( sha512: &mut Application::Hash, noise_k: &Secret, @@ -2487,7 +2469,6 @@ fn decrypt_and_hash( (gcm.finish_decrypt((&message[auth_start..]).try_into().unwrap()), noise_h_c) } /// Encrypt a standardized control packet. -#[inline] fn encrypt_control( c: &mut impl AesGcmEnc, header_cipher: &impl AesEnc, @@ -2509,7 +2490,6 @@ fn encrypt_control( header_cipher.encrypt_in_place((&mut fragment[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]).try_into().unwrap()); (fragment, fragment_len) } -#[inline] fn decrypt_control<'a, IoError>( c: &mut impl AesGcmDec, packet_type: u8, @@ -2530,7 +2510,6 @@ fn decrypt_control<'a, IoError>( Ok(&mut fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE]) } -#[inline(always)] fn set_packet_header(packet: &mut [u8], fragment_count: u8, fragment_no: u8, packet_type: u8, remote_key_id: u32, counter_or_id: u64) { debug_assert!(packet.len() >= MIN_PACKET_SIZE); debug_assert!(fragment_count > 0); @@ -2558,7 +2537,6 @@ fn set_packet_header(packet: &mut [u8], fragment_count: u8, fragment_no: u8, pac /// 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. -#[inline(always)] fn create_message_nonce(packet_type: u8, counter: u64) -> [u8; AES_GCM_IV_SIZE] { let mut ret = [0u8; AES_GCM_IV_SIZE]; ret[3] = packet_type; @@ -2567,7 +2545,6 @@ fn create_message_nonce(packet_type: u8, counter: u64) -> [u8; AES_GCM_IV_SIZE] ret } /// returns `(fragment_count, fragment_no, packet_type, counter, header_nonce)`. -#[inline(always)] fn parse_packet_header(packet: &[u8]) -> (u8, u8, u8, u64, [u8; 10]) { let header_nonce = packet[6..16].try_into().unwrap(); let counter = packet[8..16].try_into().unwrap(); @@ -2658,7 +2635,6 @@ fn generate_key_id( } impl SessionKey { - #[inline(always)] fn new( hmac: &mut Application::HmacHash, ck: SymmetricState, @@ -2685,7 +2661,6 @@ impl SessionKey { } } - #[inline(always)] fn get_send_cipher(&self, counter: u64) -> Result, SendError> { if counter < self.expire_at_counter { Ok(self.send_cipher_pool[(counter as usize) % self.send_cipher_pool.len()].lock().unwrap()) @@ -2694,7 +2669,6 @@ impl SessionKey { } } - #[inline(always)] fn get_receive_cipher(&self, counter: u64) -> MutexGuard { let idx = (counter as usize) % self.receive_cipher_pool.len(); self.receive_cipher_pool[idx].lock().unwrap() @@ -2702,7 +2676,6 @@ impl SessionKey { } /// MixHash to update 'h' during negotiation. -#[inline(always)] fn mix_hash(hasher: &mut impl Sha512, h: &[u8; NOISE_HASHLEN], m: &[u8]) -> [u8; NOISE_HASHLEN] { let mut output = [0u8; NOISE_HASHLEN]; hasher.reset(); @@ -2713,7 +2686,6 @@ fn mix_hash(hasher: &mut impl Sha512, h: &[u8; NOISE_HASHLEN], m: &[u8]) -> [u8; } /// Check if the proof of work attached to the first message contains the correct number of leading /// zeros. -#[inline(always)] fn verify_pow(hasher: &mut Application::Hash, response: &[u8]) -> bool { if Application::PROOF_OF_WORK_BIT_DIFFICULTY == 0 { return true; @@ -2725,7 +2697,6 @@ fn verify_pow(hasher: &mut Application::Hash, res let n = u32::from_be_bytes(output[..4].try_into().unwrap()); n.leading_zeros() >= Application::PROOF_OF_WORK_BIT_DIFFICULTY } -#[inline(always)] fn from_bytes_agreement( public: &[u8], private: &Application::KeyPair, From a6092a56be576e12b3a330da7f10f713a3cac227 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 25 Jul 2023 12:25:18 -0400 Subject: [PATCH 13/50] removed inline --- src/crypto/secret.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/crypto/secret.rs b/src/crypto/secret.rs index 461470f..902eb6d 100644 --- a/src/crypto/secret.rs +++ b/src/crypto/secret.rs @@ -2,7 +2,6 @@ use std::{convert::TryInto, ptr::write_volatile}; /// Constant time byte slice equality. -#[inline] pub fn secure_eq + ?Sized, B: AsRef<[u8]> + ?Sized>(a: &A, b: &B) -> bool { let (a, b) = (a.as_ref(), b.as_ref()); if a.len() == b.len() { @@ -31,7 +30,6 @@ pub struct Secret(pub [u8; L]); impl Secret { /// Create a new all-zero secret. - #[inline(always)] pub fn new() -> Self { Self([0_u8; L]) } @@ -45,30 +43,25 @@ impl Secret { /// This is unsafe because it will not destroy the contents of its input. /// # Safety /// Make sure the contents of the input are securely deleted. - #[inline(always)] pub unsafe fn from_bytes(b: &[u8]) -> Self { Self(b.try_into().unwrap()) } - #[inline(always)] pub fn as_ptr(&self) -> *const u8 { self.0.as_ptr() } - #[inline(always)] pub fn as_bytes(&self) -> &[u8; L] { &self.0 } /// Get the first N bytes of this secret as a fixed length array. - #[inline(always)] pub fn first_n(&self) -> &[u8; N] { assert!(N <= L); unsafe { &*self.0.as_ptr().cast() } } /// Clone the first N bytes of this secret as another secret. - #[inline(always)] pub fn first_n_clone(&self) -> Secret { Secret::(*self.first_n()) } @@ -97,35 +90,30 @@ impl Drop for Secret { } impl Default for Secret { - #[inline(always)] fn default() -> Self { Self([0u8; L]) } } impl AsRef<[u8]> for Secret { - #[inline(always)] fn as_ref(&self) -> &[u8] { &self.0 } } impl AsRef<[u8; L]> for Secret { - #[inline(always)] fn as_ref(&self) -> &[u8; L] { &self.0 } } impl AsMut<[u8]> for Secret { - #[inline(always)] fn as_mut(&mut self) -> &mut [u8] { &mut self.0 } } impl AsMut<[u8; L]> for Secret { - #[inline(always)] fn as_mut(&mut self) -> &mut [u8; L] { &mut self.0 } From 270e39d1650ff16871aaf659b459928545929b6c Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 25 Jul 2023 12:28:09 -0400 Subject: [PATCH 14/50] reset gitignore --- .gitignore | 14 -------------- Cargo.lock | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 14 deletions(-) create mode 100644 Cargo.lock diff --git a/.gitignore b/.gitignore index 7dd2bd4..ea8c4bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1 @@ /target -/**/target -/**/Cargo.lock - -.DS_* -.Icon* -._* -*.o -*.so -*.dylib -*.dSYM -*.a -/.idea -/.nova -*.secret diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..cd3ec00 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,33 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "pqc_kyber" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c578a7eab95d8649f115dd6f90894d167766e82a6bcf66ff25f60e7dfc857e" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "zssp" +version = "0.0.3" +dependencies = [ + "hex-literal", + "pqc_kyber", + "rand_core", +] From e6df05b6b0faad17812c6da12473b4fe8d8d8d75 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 7 Aug 2023 09:03:55 -0400 Subject: [PATCH 15/50] integrated some files --- Cargo.lock | 18 +- Cargo.toml | 3 +- src/antireplay.rs | 24 + src/applicationlayer.rs | 264 ++-- src/challenge.rs | 105 ++ src/context.rs | 438 ++++++ src/crypto/aes.rs | 34 +- src/crypto/aes_gcm.rs | 57 - src/crypto/kyber1024.rs | 36 + src/crypto/mod.rs | 17 +- src/crypto/p384.rs | 43 +- src/crypto/secret.rs | 127 -- src/crypto/sha512.rs | 43 +- src/lib.rs | 23 +- src/log_event.rs | 4 +- src/proto.rs | 303 +--- src/proto_old.rs | 296 ++++ src/ratchet_state.rs | 130 +- src/ratchet_state_old.rs | 62 + src/result.rs | 162 +++ src/symmetric_state.rs | 264 ++-- src/symmetric_state_old.rs | 156 +++ src/zeta.rs | 1394 +++++++++++++++++++ src/zssp copy.rs | 2704 ++++++++++++++++++++++++++++++++++++ src/zssp.rs | 140 +- 25 files changed, 6011 insertions(+), 836 deletions(-) create mode 100644 src/antireplay.rs create mode 100644 src/challenge.rs create mode 100644 src/context.rs delete mode 100644 src/crypto/aes_gcm.rs create mode 100644 src/crypto/kyber1024.rs delete mode 100644 src/crypto/secret.rs create mode 100644 src/proto_old.rs create mode 100644 src/ratchet_state_old.rs create mode 100644 src/result.rs create mode 100644 src/symmetric_state_old.rs create mode 100644 src/zeta.rs create mode 100644 src/zssp copy.rs diff --git a/Cargo.lock b/Cargo.lock index cd3ec00..e0caaa4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,10 +3,13 @@ version = 3 [[package]] -name = "hex-literal" -version = "0.4.1" +name = "arrayvec" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +dependencies = [ + "zeroize", +] [[package]] name = "pqc_kyber" @@ -23,11 +26,18 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" + [[package]] name = "zssp" version = "0.0.3" dependencies = [ - "hex-literal", + "arrayvec", "pqc_kyber", "rand_core", + "zeroize", ] diff --git a/Cargo.toml b/Cargo.toml index 74aa8d5..ca0967b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,4 +13,5 @@ doc = true [dependencies] pqc_kyber = { version = "0.6.0", default-features = false, features = ["kyber1024", "std"] } rand_core = "0.6.4" -hex-literal = "0.4.1" +zeroize = { version = "1.6.0" } +arrayvec = { version = "0.7.4", default-features = false, features = ["std", "zeroize"] } diff --git a/src/antireplay.rs b/src/antireplay.rs new file mode 100644 index 0000000..80cd707 --- /dev/null +++ b/src/antireplay.rs @@ -0,0 +1,24 @@ +use std::sync::atomic::{AtomicU64, Ordering}; + +pub struct Window ([AtomicU64; L]); + +impl Window { + pub fn new() -> Self { + Self(std::array::from_fn(|_| AtomicU64::new(0))) + } + /// Check the window without mutating state. + pub fn check(&self, counter: u64) -> bool { + let slot = &self.0[(counter as usize) % self.0.len()]; + let counter = counter.wrapping_add(1); + let prev_counter = slot.load(Ordering::Relaxed); + prev_counter < counter && counter.wrapping_sub(prev_counter) <= MAX + } + /// Update the window, returning true if the packet is still valid. + /// This should only be called after the packet is authenticated. + pub fn update(&self, counter: u64) -> bool { + let slot = &self.0[(counter as usize) % self.0.len()]; + let counter = counter.wrapping_add(1); + let prev_counter = slot.fetch_max(counter, Ordering::Relaxed); + prev_counter < counter && counter.wrapping_sub(prev_counter) <= MAX + } +} diff --git a/src/applicationlayer.rs b/src/applicationlayer.rs index ff0a957..7f18f60 100644 --- a/src/applicationlayer.rs +++ b/src/applicationlayer.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + /* 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/. @@ -5,15 +7,89 @@ * (c) ZeroTier, Inc. * https://www.zerotier.com/ */ -use std::sync::Arc; - -use crate::crypto::aes::{AesDec, AesEnc}; -use crate::crypto::aes_gcm::{AesGcmDec, AesGcmEnc}; +use crate::crypto::aes::{AesDec, AesEnc, HighThroughputAesGcmPool, LowThroughputAesGcm}; +use crate::crypto::kyber1024::Kyber1024PrivateKey; use crate::crypto::p384::{P384KeyPair, P384PublicKey}; use crate::crypto::rand_core::{CryptoRng, RngCore}; -use crate::crypto::sha512::{HmacSha512, Sha512}; +use crate::crypto::sha512::{HmacSha512, HashSha512}; use crate::RatchetState; -use crate::{log_event::LogEvent, Session, RATCHET_SIZE}; +use crate::proto::RATCHET_SIZE; +use crate::zssp::Session; +//use crate::{log_event::LogEvent, Session}; + +/// 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. +pub struct Settings { + /// Timeout for how long Alice should wait for Bob to confirm that the Noise_XK handshake + /// was completed successfully. The handshake attempt will be assumed as failed and + /// restarted if Bob does not respond by this cut-off. + pub initial_offer_timeout: u64, + /// Timeout for how long ZSSP should wait before expiring and closing a session when it has + /// lingered in certain states for too long, primarily the rekeying states. + /// If a remote peer does not send the correct information to rekey a session before this + /// timeout then the session will close. + pub rekey_timeout: u64, + /// How long until rekeying should occur for each new session key. + pub rekey_after_time: u64, + /// Maximum random jitter to subtract from the rekey after time timer. + /// Must be greater than 0. + /// This prevents rekeying from occurring predictably on the hour, so traffic analysis is harder. + pub rekey_time_max_jitter: u64, + /// How many key uses may occur before the session starts attempting to rekey. + /// The session will forceably close at 2^32 key uses so it is recommended this value be smaller. + pub rekey_after_key_uses: u64, + /// Retry interval for outgoing connection initiation or rekey attempts. + /// + /// Retry attempts will be no more often than this, but the delay may end up being + /// slightly more in some cases based on the rate of calls to `service`. + pub resend_time: u64, + /// How long fragments are allowed to linger in the defragmentation buffer before they are dropped. + /// This implementation of a defrag buffer only bounds memory consumption based on this value. + pub fragment_assembly_timeout: u64, +} +impl Settings { + /// Default value for the `initial_offer_timeout`. + /// The default value is 10 seconds in ms. + pub const INITIAL_OFFER_TIMEOUT_MS: u64 = 10 * 1000; + /// Default value for the `rekey_timeout`. + /// The default value is 1 minute in ms. + pub const REKEY_TIMEOUT_MS: u64 = 60 * 1000; + /// Default value for the `rekey_after_time`. + /// The default value is 1 hour in ms. + pub const REKEY_AFTER_TIME_MS: u64 = 60 * 60 * 1000; + /// Default value for the `rekey_time_max_jitter`. + /// The default is 10 minutes in ms. + pub const REKEY_AFTER_TIME_MAX_JITTER_MS: u64 = 10 * 60 * 1000; + /// Default value for the `rekey_after_key_uses`. + /// The default is 2^30. + pub const REKEY_AFTER_KEY_USES: u64 = 1 << 30; + /// Default value for the `resend_time`. + /// The default is 1 second in ms. + pub const RESEND_TIME: u64 = 1000; + /// Default value for the `fragment_assembly_timeout`. + /// The default is 5 seconds in ms. + pub const FRAGMENT_ASSEMBLY_TIMEOUT_MS: u64 = 5 * 1000; + /// Create an instance of Settings with all default values. + /// These defaults are in units of milliseconds, so if these defaults are used, `App::time` + /// must return timestamps in unts of milliseconds as well. + pub const fn new_ms() -> Self { + Self { + initial_offer_timeout: Self::INITIAL_OFFER_TIMEOUT_MS, + rekey_timeout: Self::REKEY_TIMEOUT_MS, + rekey_after_time: Self::REKEY_AFTER_TIME_MS, + rekey_time_max_jitter: Self::REKEY_AFTER_TIME_MAX_JITTER_MS, + rekey_after_key_uses: Self::REKEY_AFTER_KEY_USES, + resend_time: Self::RESEND_TIME, + fragment_assembly_timeout: Self::FRAGMENT_ASSEMBLY_TIMEOUT_MS, + } + } +} +impl Default for Settings { + fn default() -> Self { + Self::new_ms() + } +} /// Trait to implement to integrate the session into an application. /// @@ -25,84 +101,51 @@ use crate::{log_event::LogEvent, Session, RATCHET_SIZE}; /// set to the same values. Changing these constants is generally discouraged unless you know /// what you are doing. pub trait ApplicationLayer: Sized { - /// Retry interval for outgoing connection initiation or rekey attempts. - /// - /// Retry attempts will be no more often than this, but the delay may end up being - /// slightly more in some cases depending on where in the cycle the initial attempt - /// falls. - /// - /// Default value is 1 second. - const RETRY_INTERVAL_MS: i64 = 1000; - /// Timeout for how long Alice should wait for Bob to confirm that the Noise_XK handshake - /// was completed successfully. The handshake attempt will be assumed as failed and - /// restarted if Bob does not respond by this cut-off. - /// - /// Default is 10 seconds. - const INITIAL_OFFER_TIMEOUT_MS: i64 = 10 * 1000; - /// Timeout for how long ZSSP should wait before expiring and closing a session when it has - /// lingered in certain states for too long, primarily the rekeying states. - /// If a remote peer does not send the correct information to rekey a session before this - /// timeout then the session will close. - /// - /// Default is 1 minute. - const EXPIRATION_TIMEOUT_MS: i64 = 60 * 1000; - /// Start attempting to rekey after a key has been in use for this many milliseconds. - /// - /// Default is 1 hour. - const REKEY_AFTER_TIME_MS: i64 = 1000 * 60 * 60; - /// Maximum random jitter to subtract from the rekey after time timer. - /// Must be greater than 0 and less than u32::MAX. - /// This prevents rekeying from occurring predictably on the hour, so traffic analysis is harder. - /// - /// Default is 10 minutes. - const REKEY_AFTER_TIME_MAX_JITTER_MS: i64 = 1000 * 60 * 10; - /// Rekey after this many key uses. - /// - /// The default is 1/4 the recommended NIST limit for AES-GCM. Unless you are transferring - /// a massive amount of data REKEY_AFTER_TIME_MS is probably going to kick in first. - const REKEY_AFTER_USES: u64 = 1073741824; - - /// Hard expiration of a key after this many uses. - /// - /// Attempting to encrypt more than this many messages with a key will cause a hard error - /// and prevent all encryption. - /// This should basically never occur in practice because of rekeying. - /// - /// Default value is 2^32 - 1, one less than NIST's recommended limit. - /// https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf - const EXPIRE_AFTER_USES: u64 = 4294967295; - - /// Determines how computationally difficult the proof of work is when Bob challenges Alice. - /// It is extremely computationally expensive on Bob to process Alice's initiation packet. So - /// Bob has the option to challenge Alice to prove ownership of address and to prove work before - /// they attempt process Alice's initiation packet. - /// The amount of computational work Alice has to prove increases exponentially with this value. - /// - /// This value must be between 0 and 32 (inclusive). - /// - /// Default is 13, which, on a modern processor, ensures Alice will have to do about as much - /// computational work as Bob will when they process Alice's initiation packet. - const PROOF_OF_WORK_BIT_DIFFICULTY: u32 = 13; + /// 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(); type Rng: CryptoRng + RngCore; + /// The implementation of AES-256 Encryption that ZSSP should use. + /// + /// FIPS compliance requires use of a FIPS certified implementation. type PrpEnc: AesEnc; + /// The implementation of AES-256 Decryption that ZSSP should use. + /// + /// FIPS compliance requires use of a FIPS certified implementation. type PrpDec: AesDec; - type AeadEnc: AesGcmEnc; - type AeadDec: AesGcmDec; + type Aead: LowThroughputAesGcm; + type AeadPool: HighThroughputAesGcmPool; - type Hash: Sha512; + /// The implementation of SHA-512 that ZSSP should use. + /// + /// FIPS compliance requires use of a FIPS certified implementation. + type Hash: HashSha512; type HmacHash: HmacSha512; - + /// The implementation of P-384 public keys that ZSSP should use. + /// + /// FIPS compliance requires a FIPS certified implementation. type PublicKey: P384PublicKey; - type KeyPair: P384KeyPair; + /// The implementation of P-384 private keys that ZSSP should use. + /// + /// FIPS compliance requires use of a FIPS certified implementation. + type KeyPair: P384KeyPair; + /// The implementation of Kyber1024 that ZSSP should use. + /// + /// No implementation of Kyber1024 can be FIPS certified, but this is not required + /// for ZSSP to achieve FIPS compliance. + type Kem: Kyber1024PrivateKey; - type IoError: std::fmt::Debug; + /// A user-defined error returned when the `ApplicationLayer` fails to access persistent storage + /// for a peer's ratchet states. + type StorageError: std::error::Error; /// Type for arbitrary opaque object for use by the application that is attached to /// each session. - type Data; + type SessionData; /// Data type for incoming packet buffers. /// @@ -116,6 +159,12 @@ pub trait ApplicationLayer: Sized { /// It will be dropped as soon as the session is established. type LocalIdentityBlob: AsRef<[u8]>; + /// 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 + /// should rekey. + fn time(&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 /// then on they should be using non-empty ratchet states. @@ -125,7 +174,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, current_time: i64) -> bool; + fn hello_requires_recognized_ratchet(&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. @@ -141,7 +190,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>, current_time: i64) -> bool; + fn initiator_disallows_downgrade(&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 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]) -> 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. @@ -154,19 +210,36 @@ 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], current_time: i64) -> Result; - - /// Lookup a specific ratchet state based on the identity of the peer being communicated with. + 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. + /// + /// 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 + /// function `ApplicationLayer::check_accept_session`. fn restore_by_identity( &self, remote_static_key: &Self::PublicKey, - application_data: &Self::Data, - current_time: i64, - ) -> Result<[RatchetState; 2], Self::IoError>; - /// Atomically save the given `new_ratchet_states` to persistent storage. - /// `pre_ratchet_states` contains what should be the previous contents of persistent storage. + 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`. /// /// 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 @@ -184,12 +257,27 @@ pub trait ApplicationLayer: Sized { fn save_ratchet_state( &self, remote_static_key: &Self::PublicKey, - application_data: &Self::Data, - pre_ratchet_states: [&RatchetState; 2], - new_ratchet_states: [&RatchetState; 2], - current_time: i64, - ) -> Result<(), Self::IoError>; + session_data: &Self::SessionData, + update_data: RatchetUpdate<'_>, + ) -> Result<(), Self::StorageError>; - #[allow(unused)] - fn event_log(&self, event: LogEvent, current_time: i64) {} + /// 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 + /// 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>); +} + +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>, +} + +pub struct AcceptAction { + pub session_data: Option, + pub responder_disallows_downgrade: bool, + pub responder_silently_rejects: bool, } diff --git a/src/challenge.rs b/src/challenge.rs new file mode 100644 index 0000000..76e6cc5 --- /dev/null +++ b/src/challenge.rs @@ -0,0 +1,105 @@ +use std::hash::Hasher; +use std::sync::atomic::{AtomicU64, Ordering}; + +use rand_core::{CryptoRng, RngCore}; + +use crate::antireplay::Window; +use crate::crypto::{secure_eq, sha512::{HashSha512, SHA512_HASH_SIZE}}; +use crate::proto::*; + +pub struct ChallengeContext { + counter: AtomicU64, + antireplay_window: Window, + 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], + pre_response: &mut [u8; CHALLENGE_SIZE], +) { + if &challenge[POW_START..] == &pre_response[POW_START..] { + pre_response.copy_from_slice(challenge); + let mut pow = rng.next_u64(); + let mut work_buf = [0u8; SHA512_HASH_SIZE]; + loop { + pre_response[POW_START..].copy_from_slice(&pow.to_be_bytes()); + if verify_pow::(pre_response, &mut work_buf) { + return; + } + pow = pow.wrapping_add(1); + } + } +} + +impl ChallengeContext { + pub fn new(rng: &mut Rng) -> Self { + let mut salt = [0u8; SALT_SIZE]; + rng.fill_bytes(&mut salt); + Self { + counter: AtomicU64::new(0), + antireplay_window: Window::new(), + salt, + } + } + /// Corresponds to Algorithm 12 found in Section 5. + pub fn process_hello( + &self, + addr: &impl std::hash::Hash, + response: &[u8; CHALLENGE_SIZE], + ) -> Result { + let c = u64::from_be_bytes(response[..COUNTER_SIZE].try_into().unwrap()); + let mut work_buf = [0u8; SHA512_HASH_SIZE]; + if self.antireplay_window.check(c) && secure_eq(&response[COUNTER_SIZE..POW_START], &self.create_mac::(c, addr)) && verify_pow::(response, &mut work_buf) { + self.antireplay_window.update(c); + Ok(true) + } else { + let mut challenge = [0u8; CHALLENGE_SIZE]; + let d = self.counter.fetch_add(1, Ordering::Relaxed); + challenge[..COUNTER_SIZE].copy_from_slice(&d.to_be_bytes()); + challenge[COUNTER_SIZE..POW_START].copy_from_slice(&self.create_mac::(d, addr)); + challenge[POW_START..].copy_from_slice(&response[POW_START..]); + Err(challenge) + } + } + 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()); + addr.hash(&mut hasher); + hasher.write(&self.salt); + drop(hasher); + + let mut mac = [0u8; SHA512_HASH_SIZE]; + h.finish_and_reset(&mut mac); + mac[..MAC_SIZE].try_into().unwrap() + } +} + +/// 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> { + fn finish(&self) -> u64 { + unimplemented!() + } + fn write(&mut self, bytes: &[u8]) { + self.0.update(bytes) + } +} + +/// Check if the proof of work attached to the first message contains the correct number of leading +/// zeros. +fn verify_pow(response: &[u8], work_buf: &mut [u8; SHA512_HASH_SIZE]) -> bool { + let mut hasher = Hash::new(); + hasher.update(response); + hasher.finish_and_reset(work_buf); + let n = u32::from_be_bytes(work_buf[..4].try_into().unwrap()); + n.leading_zeros() >= DIFFICULTY +} diff --git a/src/context.rs b/src/context.rs new file mode 100644 index 0000000..ba69585 --- /dev/null +++ b/src/context.rs @@ -0,0 +1,438 @@ +use rand_core::RngCore; +use std::cmp::Reverse; +use std::collections::hash_map::Entry; +use std::collections::HashMap; +use std::hash::Hash; +use std::num::NonZeroU32; +use std::sync::{Arc, Mutex, Weak, RwLock}; + +use crate::applicationlayer::ApplicationLayer; +use crate::crypto::aes::{AES_256_KEY_SIZE, AES_GCM_IV_SIZE}; +use crate::indexed_heap::IndexedBinaryHeap; +//use crate::fragmentation::{send_with_fragmentation, DefragBuffer}; +use crate::proto::*; +use crate::result::{byzantine_fault, 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 { + ($app:expr, $event:expr) => { + #[cfg(feature = "logging")] + $app.event_log($event); + }; +} +pub(crate) use log; + +/// Session context for local application. +/// +/// Each application using ZSSP must create an instance of this to own sessions and +/// 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 { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +pub(crate) type SessionMap = RwLock>>>; + +pub(crate) struct ContextInner { + pub(crate) rng: Mutex, + pub(crate) s_secret: App::KeyPair, + pub(crate) session_queue: Mutex>, Reverse>>, + pub(crate) session_map: SessionMap, + //pub(crate) b2_map: Mutex>>, + + //hello_defrag: Mutex, + challenge: ChallengeContext, +} + +/// 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() +} + +impl Context { + /// Create a new session 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), + 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()), + })) + } + /// 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; + } + + /// 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 + /// * `mtu` - MTU for initial packets + /// * `static_remote_key` - Remote side's static public NIST P-384 key + /// * `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 + pub fn open( + &self, + app: App, + send: impl FnMut(Vec) -> bool, + mut mtu: usize, + static_remote_key: App::PublicKey, + session_data: App::SessionData, + identity: Vec, + ) -> Result>, OpenError> { + mtu = mtu.max(MIN_TRANSPORT_MTU); + let ctx = &self.0; + + // Process zeta layer. + trans_to_a1( + app, + &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); + }, + ) + } + + /// Receive, authenticate, decrypt, and process a physical wire packet. + /// + /// * `app` - Interface to application using ZSSP + /// * `send_unassociated_reply` - Function to send reply packets directly when no session exists + /// * `send_unassociated_mtu` - MTU for unassociated replies + /// * `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>( + &self, + app: App, + send_unassociated_reply: impl FnMut(Vec) -> bool, + mut send_unassociated_mtu: usize, + send_to: impl FnOnce(&Arc>) -> Option<(SendFn, usize)>, + remote_address: &impl Hash, + raw_fragment: Vec, + ) -> Result, ReceiveError> { + use crate::result::FaultType::*; + send_unassociated_mtu = send_unassociated_mtu.max(MIN_TRANSPORT_MTU); + let ctx = &self.0; + + // 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()); + 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)); + } + })?; + 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 (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)?; + SessionEvent::Data(assembled_packet) + } + PACKET_TYPE_HANDSHAKE_RESPONSE => { + log!(app, ReceivedRawX2); + received_x2_trans( + &mut zeta, + &session, + &app, + &ctx, + kid_recv, + to_aes_nonce(&pn), + assembled_packet, + send_associated, + )?; + log!(app, X2IsAuthSentX3(&session)); + SessionEvent::Control + } + 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)?; + log!(app, KeyConfirmIsAuthSentAck(&session)); + if result { + SessionEvent::Established + } else { + SessionEvent::Control + } + } + PACKET_TYPE_ACK => { + log!(app, ReceivedRawAck); + received_c2_trans(&mut zeta, &app, &ctx.rng, kid_recv, to_aes_nonce(&pn), assembled_packet)?; + log!(app, AckIsAuth(&session)); + SessionEvent::Control + } + PACKET_TYPE_REKEY_INIT => { + log!(app, ReceivedRawK1); + received_k1_trans( + &mut zeta, + &session, + &app, + &ctx.rng, + &ctx.session_map, + &ctx.s_secret, + kid_recv, + to_aes_nonce(&pn), + assembled_packet, + send_associated, + )?; + log!(app, K1IsAuthSentK2(&session)); + SessionEvent::Control + } + PACKET_TYPE_REKEY_COMPLETE => { + log!(app, ReceivedRawK2); + received_k2_trans(&mut zeta, &app, kid_recv, to_aes_nonce(&pn), assembled_packet, send_associated)?; + log!(app, K2IsAuthSentKeyConfirm(&session)); + SessionEvent::Control + } + PACKET_TYPE_SESSION_REJECTED => { + log!(app, ReceivedRawD); + received_d_trans(&mut zeta, kid_recv, to_aes_nonce(&pn), assembled_packet)?; + log!(app, DIsAuthClosedSession(&session)); + SessionEvent::Rejected + } + _ => return Err(byzantine_fault!(InvalidPacket, true)), // This is unreachable. + }; + drop(zeta); + Ok(ReceiveOk::Session(session, ret)) + } else { + Ok(ReceiveOk::Unassociated) + } + } else { + let mut b2_map = ctx.b2_map.lock().unwrap(); + 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)) + } + })?; + 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, + ); + })?; + log!(app, X3IsAuthSentKeyConfirm(&session)); + Ok(ReceiveOk::Session(session, SessionEvent::NewSession)) + } else { + Ok(ReceiveOk::Unassociated) + } + } else { + // When sessions are added or dropped or packets arrive extremely delayed it is + // possible to receive no longer recognized key ids. + Err(byzantine_fault!(UnknownLocalKeyId, false)) + } + } + } else { + // Process recv fragmentation layer. + 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 { + Ok(()) + } 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()); + if let Err(challenge) = result { + log!(app, X1FailedChallengeSentNewChallenge); + 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()); + send_with_fragmentation::( + send_unassociated_reply, + send_unassociated_mtu, + 0, + to_packet_nonce(&nonce), + &challenge_packet, + None, + ); + // If we issue a challenge the first hello packet will always fail. + return Err(byzantine_fault!(FailedAuth, false)); + } else if let Ok(true) = result { + log!(app, X1SucceededChallenge); + } + 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), + ); + })?; + log!(app, X1IsAuthSentX2); + Ok(ReceiveOk::Unassociated) + } else if p == PACKET_TYPE_CHALLENGE { + log!(app, ReceivedRawChallenge); + // Process recv challenge layer. + 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()) { + let mut zeta = session.0.lock().unwrap(); + respond_to_challenge(&mut zeta, &ctx.rng, &assembled_packet[KID_SIZE..].try_into().unwrap()); + log!(app, ChallengeIsAuth(&session)); + return Ok(ReceiveOk::Unassociated); + } + } + Err(byzantine_fault!(UnknownLocalKeyId, true)) + } else { + Err(byzantine_fault!(InvalidPacket, true)) + } + } else { + Ok(ReceiveOk::Unassociated) + } + } + } + + /// Send data over the session. + /// + /// * `session` - The session to send to + /// * `send` - Function to call to send physical packet(s); the buffer passed to `send` is a + /// 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> { + mtu = mtu.max(MIN_TRANSPORT_MTU); + let mut zeta = session.0.lock().unwrap(); + send_payload(&mut zeta, payload, |Packet(kid, nonce, payload), hk| { + send_with_fragmentation::(send, mtu, *kid, to_packet_nonce(nonce), &payload, hk); + }) + } + + /// Perform periodic background service and cleanup tasks. + /// + /// 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. + /// + /// * `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 { + let ctx = &self.0; + let sessions = ctx.sessions.lock().unwrap(); + 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(); + service( + &mut zeta, + &session, + ctx, + &app, + current_time, + |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); + } + }, + ); + next_timer = next_timer.min(zeta.next_timer()); + zeta.defrag.service::(current_time); + } + } + ctx.hello_defrag.lock().unwrap().service::(current_time); + (App::SETTINGS.resend_time as i64).min(next_timer - current_time) + } +} diff --git a/src/crypto/aes.rs b/src/crypto/aes.rs index 637d009..baec450 100644 --- a/src/crypto/aes.rs +++ b/src/crypto/aes.rs @@ -1,7 +1,9 @@ // (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. -pub const AES_256_BLOCK_SIZE: usize = 16; pub const AES_256_KEY_SIZE: usize = 32; +pub const AES_256_BLOCK_SIZE: usize = 16; +pub const AES_GCM_TAG_SIZE: usize = 16; +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 @@ -36,3 +38,33 @@ pub trait AesDec: Send + Sync { /// The plaintext should be written directly back out to `block`. fn decrypt_in_place(&self, block: &mut [u8; AES_256_BLOCK_SIZE]); } + + +pub trait AesGcmEncContext { + fn encrypt(&mut self, input: &[u8], output: &mut [u8]); + + fn finish(&mut self) -> [u8; AES_GCM_TAG_SIZE]; +} + +pub trait AesGcmDecContext { + fn decrypt(&mut self, input: &[u8], output: &mut [u8]); + + #[must_use] + fn finish(&mut self, tag: &[u8; AES_GCM_TAG_SIZE]) -> bool; +} + +pub trait HighThroughputAesGcmPool: Send + Sync { + type EncContext<'a>: AesGcmEncContext where Self: 'a; + type DecContext<'a>: AesGcmDecContext where Self: 'a; + + fn new(encrypt_key: &[u8; AES_256_KEY_SIZE], decrypt_key: &[u8; AES_256_KEY_SIZE]) -> Self; + + fn start_enc<'a>(&'a self, iv: &[u8; AES_GCM_IV_SIZE]) -> Self::EncContext<'a>; + fn start_dec<'a>(&'a self, iv: &[u8; AES_GCM_IV_SIZE]) -> Self::DecContext<'a>; +} + +pub trait LowThroughputAesGcm { + fn encrypt_in_place(key: &[u8; AES_256_KEY_SIZE], iv: &[u8; AES_GCM_IV_SIZE], aad: &[u8], data: &mut [u8]) -> [u8; AES_GCM_TAG_SIZE]; + #[must_use] + fn decrypt_in_place(key: &[u8; AES_256_KEY_SIZE], iv: &[u8; AES_GCM_IV_SIZE], aad: &[u8], data: &mut [u8], tag: &[u8; AES_GCM_TAG_SIZE]) -> bool; +} diff --git a/src/crypto/aes_gcm.rs b/src/crypto/aes_gcm.rs deleted file mode 100644 index c8328ed..0000000 --- a/src/crypto/aes_gcm.rs +++ /dev/null @@ -1,57 +0,0 @@ -// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. - -pub const AES_GCM_TAG_SIZE: usize = 16; -pub const AES_GCM_IV_SIZE: usize = 12; -pub const AES_GCM_KEY_SIZE: usize = super::aes::AES_256_KEY_SIZE; - -/// The order of calls to this trait is always -/// `set_iv` -> `set_aad` -> `encrypt` -> `finish_encrypt`. -/// `set_aad` and `encrypt` may not always be called. `encrypt_in_place` may be called instead of -/// `encrypt`. `encrypt` may be called multiple times, it should start encryption of the input at the point in -/// the keystream where encryption previously ended. -/// An instance of this trait may be reused multiple times, each reuse should use the same key. -/// If it is reused, `set_iv` will always be the very next call after `finish_encrypt`. -/// -/// Implementations of this trait do not have to be Send + Sync, -/// but if instances are wrapped in a `Mutex` they must satisfy the requirements of Send + Sync. -/// -/// Instances must securely delete their keys when dropped. -pub trait AesGcmEnc { - fn new(key: &[u8; AES_GCM_KEY_SIZE]) -> Self; - - fn set_iv(&mut self, iv: &[u8; AES_GCM_IV_SIZE]); - - fn set_aad(&mut self, aad: &[u8]); - - fn encrypt(&mut self, input: &[u8], output: &mut [u8]); - - fn encrypt_in_place(&mut self, data: &mut [u8]); - - fn finish_encrypt(&mut self, output: &mut [u8; AES_GCM_TAG_SIZE]); -} - -/// The order of calls to this trait is always -/// `set_iv` -> `set_aad` -> `decrypt` -> `finish_decrypt`. -/// `set_aad` and `decrypt` may not always be called. `decrypt_in_place` may be called instead of -/// `decrypt`. `decrypt` may be called multiple times, it should start decryption of the input at the point in -/// the keystream where decryption previously ended. -/// An instance of this trait may be reused multiple times, each reuse should use the same key. -/// If it is reused, `set_iv` will always be the very next call after `finish_decrypt`. -/// -/// Implementations of this trait do not have to be Send + Sync, -/// but if instances are wrapped in a `Mutex` they must satisfy the requirements of Send + Sync. -/// -/// Instances must securely delete their keys when dropped. -pub trait AesGcmDec { - fn new(key: &[u8; AES_GCM_KEY_SIZE]) -> Self; - - fn set_iv(&mut self, iv: &[u8; AES_GCM_IV_SIZE]); - - fn set_aad(&mut self, aad: &[u8]); - - fn decrypt(&mut self, input: &[u8], output: &mut [u8]); - - fn decrypt_in_place(&mut self, data: &mut [u8]); - - fn finish_decrypt(&mut self, expected_tag: &[u8; AES_GCM_TAG_SIZE]) -> bool; -} diff --git a/src/crypto/kyber1024.rs b/src/crypto/kyber1024.rs new file mode 100644 index 0000000..5cccc30 --- /dev/null +++ b/src/crypto/kyber1024.rs @@ -0,0 +1,36 @@ +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 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 + /// 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. + /// + /// **CRITICAL**: This must return `None` if the given `public_key` is invalid in any way + /// according to the Kyber1024 spec. + #[must_use] + fn encapsulate(rng: &mut Rng, public_key: &[u8; KYBER_PUBLIC_KEY_SIZE], plaintext_out: &mut [u8; KYBER_PLAINTEXT_SIZE]) -> Option<[u8; KYBER_CIPHERTEXT_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. + /// + /// **CRITICAL**: This must return `None` if the given `ciphertext` is invalid in any way + /// according to the Kyber1024 spec. + #[must_use] + fn decapsulate(&self, ciphertext: &[u8; KYBER_CIPHERTEXT_SIZE], plaintext_out: &mut [u8; KYBER_PLAINTEXT_SIZE]) -> bool; +} diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 0c7f601..22dcb9a 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -1,12 +1,25 @@ // (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. pub mod aes; -pub mod aes_gcm; pub mod p384; -pub mod secret; pub mod sha512; +pub mod 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 pqc_kyber; pub use rand_core; + +/// Constant time byte slice equality. +pub fn secure_eq + ?Sized, B: AsRef<[u8]> + ?Sized>(a: &A, b: &B) -> bool { + let (a, b) = (a.as_ref(), b.as_ref()); + if a.len() == b.len() { + let mut x = 0u8; + for (aa, bb) in a.iter().zip(b.iter()) { + x |= *aa ^ *bb; + } + x == 0 + } else { + false + } +} diff --git a/src/crypto/p384.rs b/src/crypto/p384.rs index d340356..647ac93 100644 --- a/src/crypto/p384.rs +++ b/src/crypto/p384.rs @@ -1,47 +1,46 @@ -// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. - -use super::rand_core::{CryptoRng, RngCore}; +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 P384PublicKey: 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 standard 49 byte NIST encoding of P384 public keys. - fn as_bytes(&self) -> &[u8; P384_PUBLIC_KEY_SIZE]; + /// This must output the compressed SEC1 NIST encoding of P-384 public keys. + fn to_bytes(&self) -> [u8; P384_PUBLIC_KEY_SIZE]; } /// A NIST P-384 ECDH/ECDSA public/private key pair. /// /// Instances must securely delete the private key when dropped. -pub trait P384KeyPair: Send + Sync { +pub trait P384KeyPair { + /// The `PublicKeyP384` implementation which matches this `KeyPairP384` implementation. type PublicKey: P384PublicKey; - type Rng: RngCore + CryptoRng; - /// 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. - fn generate(rng: &mut Self::Rng) -> Self; + /// 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 standard 49 byte NIST encoding of P384 public keys. - fn public_key_bytes(&self) -> &[u8; P384_PUBLIC_KEY_SIZE]; + /// 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 `false` if key agreement between this private key and - /// the input `other_public` 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 + /// **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 P-384 ECDH key agreement, or better yet use a peer reviewed /// library that has already implemented this correctly. - /// - /// If this function returns `false` then the contents of `output` will be discarded. - fn agree(&self, other_public: &Self::PublicKey, output: &mut [u8; P384_ECDH_SHARED_SECRET_SIZE]) -> bool; + #[must_use] + fn agree(&self, public_key: &Self::PublicKey, ecdh_out: &mut [u8; P384_ECDH_SHARED_SECRET_SIZE]) -> bool; } diff --git a/src/crypto/secret.rs b/src/crypto/secret.rs deleted file mode 100644 index 902eb6d..0000000 --- a/src/crypto/secret.rs +++ /dev/null @@ -1,127 +0,0 @@ -// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. -use std::{convert::TryInto, ptr::write_volatile}; - -/// Constant time byte slice equality. -pub fn secure_eq + ?Sized, B: AsRef<[u8]> + ?Sized>(a: &A, b: &B) -> bool { - let (a, b) = (a.as_ref(), b.as_ref()); - if a.len() == b.len() { - let mut x = 0u8; - for (aa, bb) in a.iter().zip(b.iter()) { - x |= *aa ^ *bb; - } - x == 0 - } else { - false - } -} - -/// Container for secrets that clears them on drop. -/// -/// We can't be totally sure that things like libraries are doing this and it's -/// hard to get every use of a secret anywhere, but using this in our code at -/// least reduces the number of secrets that are left lying around in memory. -/// -/// This is generally a low-risk thing since it's process memory that's protected, -/// but it's still not a bad idea due to things like swap or obscure side channel -/// attacks that allow memory to be read. -#[derive(Clone)] -#[repr(transparent)] -pub struct Secret(pub [u8; L]); - -impl Secret { - /// Create a new all-zero secret. - pub fn new() -> Self { - Self([0_u8; L]) - } - /// Copy bytes into secret, then delete the previous value, will panic if the slice does not match the size of this secret. - pub fn from_bytes_then_delete(b: &mut [u8]) -> Self { - let ret = Self(b.try_into().unwrap()); - b.fill(0); - ret - } - /// Moves bytes into secret, will panic if the slice does not match the size of this secret. - /// This is unsafe because it will not destroy the contents of its input. - /// # Safety - /// Make sure the contents of the input are securely deleted. - pub unsafe fn from_bytes(b: &[u8]) -> Self { - Self(b.try_into().unwrap()) - } - - pub fn as_ptr(&self) -> *const u8 { - self.0.as_ptr() - } - - pub fn as_bytes(&self) -> &[u8; L] { - &self.0 - } - - /// Get the first N bytes of this secret as a fixed length array. - pub fn first_n(&self) -> &[u8; N] { - assert!(N <= L); - unsafe { &*self.0.as_ptr().cast() } - } - - /// Clone the first N bytes of this secret as another secret. - pub fn first_n_clone(&self) -> Secret { - Secret::(*self.first_n()) - } - - pub fn overwrite(&mut self, src: &Self) { - self.0.copy_from_slice(&src.0); - } - pub fn overwrite_first_n(&mut self, src: &Secret) { - let amount = N.min(L); - self.0[..amount].copy_from_slice(&src.0[..amount]); - } - - pub fn eq_bytes(&self, other: &[u8]) -> bool { - secure_eq(&self.0, other) - } -} - -impl Drop for Secret { - fn drop(&mut self) { - unsafe { - for v in self.0.iter_mut() { - write_volatile(v, 0u8); - } - } - } -} - -impl Default for Secret { - fn default() -> Self { - Self([0u8; L]) - } -} - -impl AsRef<[u8]> for Secret { - fn as_ref(&self) -> &[u8] { - &self.0 - } -} - -impl AsRef<[u8; L]> for Secret { - fn as_ref(&self) -> &[u8; L] { - &self.0 - } -} - -impl AsMut<[u8]> for Secret { - fn as_mut(&mut self) -> &mut [u8] { - &mut self.0 - } -} - -impl AsMut<[u8; L]> for Secret { - fn as_mut(&mut self) -> &mut [u8; L] { - &mut self.0 - } -} - -impl PartialEq for Secret { - fn eq(&self, other: &Self) -> bool { - secure_eq(&self.0, &other.0) - } -} -impl Eq for Secret {} diff --git a/src/crypto/sha512.rs b/src/crypto/sha512.rs index c2161be..bfb4b0d 100644 --- a/src/crypto/sha512.rs +++ b/src/crypto/sha512.rs @@ -2,41 +2,24 @@ pub const SHA512_HASH_SIZE: usize = 64; -/// Opaque SHA-512 implementation. -/// Does not need to be threadsafe. -pub trait Sha512 { - /// Allocate memory on the stack or heap for Sha512. - /// An instance of Sha512 will only ever be held on the stack. +/// A SHA-512 implementation. +pub trait HashSha512 { + /// Create a new instance of SHA-512 for streaming data to. fn new() -> Self; - - /// Reinitialize the internal state of the hash function for a fresh input. - fn reset(&mut self); - - fn update(&mut self, input: &[u8]); - /// Finish hashing the input and write the final hash to output. - /// - /// After this function is called, this instance of Sha512 will either be dropped - /// or `reset` will be called. - fn finish(&mut self, output: &mut [u8; SHA512_HASH_SIZE]); + /// 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 streaming input and output the final hash. + fn finish_and_reset(&mut self, output: &mut [u8; SHA512_HASH_SIZE]); } + /// Opaque HMAC-SHA-512 implementation. /// Does not need to be threadsafe. pub trait HmacSha512 { - /// Allocate memory on the stack or heap for HmacSha512. - /// An instance of HmacSha512 will only ever be held on the stack. - /// - /// `reset` will always be called before `update` on a new instance of HmacSha512, - /// to make sure there is always a set key. + /// Allocate space on the stack or heap for repeated Hmac invocations. fn new() -> Self; - /// Reinitialize the internal state of the hash function for a fresh input. - /// The provided key should replace the previous Hmac key. - fn reset(&mut self, key: &[u8]); - - fn update(&mut self, input: &[u8]); - /// Finish hashing the input and write the final hash to output. - /// - /// After this function is called, this instance of HmacSha512 will either be dropped - /// or `reset` will be called. - fn finish(&mut self, output: &mut [u8; SHA512_HASH_SIZE]); + /// Pure function for computing a single HMAC Hash. Repeat invocations of this function should + /// have no effect on each other. + fn hash(&mut self, key: &[u8], full_input: &[u8], output: &mut [u8; SHA512_HASH_SIZE]); } diff --git a/src/lib.rs b/src/lib.rs index 46d0713..16c9ec0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,19 +8,24 @@ pub mod crypto; mod applicationlayer; -mod frag_cache; -mod fragged; -mod handshake_cache; +//mod frag_cache; +//mod fragged; +//mod handshake_cache; mod indexed_heap; -mod log_event; +//mod log_event; mod proto; mod ratchet_state; mod symmetric_state; -mod zssp; +mod antireplay; +mod challenge; +pub mod result; +//mod zssp; +mod zeta; +mod context; -pub mod error; +//pub mod error; pub use crate::applicationlayer::ApplicationLayer; -pub use crate::log_event::LogEvent; -pub use crate::proto::{MAX_IDENTITY_BLOB_SIZE, MIN_PACKET_SIZE, MIN_TRANSPORT_MTU, RATCHET_SIZE}; +//pub use crate::log_event::LogEvent; +//pub use crate::proto::{MAX_IDENTITY_BLOB_SIZE, MIN_PACKET_SIZE, MIN_TRANSPORT_MTU, RATCHET_SIZE}; pub use crate::ratchet_state::RatchetState; -pub use crate::zssp::{Context, ContextInner, IncomingSessionAction, ReceiveResult, Session, SessionEvent}; +//pub use crate::zssp::{Context, ContextInner, IncomingSessionAction, ReceiveResult, Session, SessionEvent}; diff --git a/src/log_event.rs b/src/log_event.rs index bdc94b5..be543aa 100644 --- a/src/log_event.rs +++ b/src/log_event.rs @@ -7,7 +7,7 @@ */ use std::sync::Arc; -use crate::{ApplicationLayer, Session}; +use crate::{ApplicationLayer, zssp::Session}; /// ZSSP events that might be interesting to log or aggregate into metrics. pub enum LogEvent<'a, Application: ApplicationLayer> { @@ -30,7 +30,7 @@ pub enum LogEvent<'a, Application: ApplicationLayer> { ReceiveUncheckedXK2, ReceiveValidXK2(&'a Arc>), ReceiveUncheckedXK3, - ReceiveValidXK3(&'a Application::Data), + ReceiveValidXK3(&'a Application::SessionData), ReceiveUncheckedKK1, ReceiveValidKK1(&'a Arc>), ReceiveUncheckedKK2, diff --git a/src/proto.rs b/src/proto.rs index 634e8d5..0d466a7 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -1,110 +1,55 @@ -/* 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::hash::Hasher; -use std::mem::size_of; +/* Common constants */ -use crate::crypto::aes_gcm::AES_GCM_TAG_SIZE; -use crate::crypto::p384::P384_PUBLIC_KEY_SIZE; -use crate::crypto::pqc_kyber::{KYBER_CIPHERTEXTBYTES, KYBER_PUBLICKEYBYTES}; -use crate::crypto::sha512::{Sha512, SHA512_HASH_SIZE}; -use hex_literal::hex; - -/// 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; +use crate::crypto::{sha512::SHA512_HASH_SIZE, p384::P384_PUBLIC_KEY_SIZE, kyber1024::{KYBER_PUBLIC_KEY_SIZE, KYBER_CIPHERTEXT_SIZE}, aes::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. pub const MIN_TRANSPORT_MTU: usize = 128; -pub const RATCHET_SIZE: usize = 32; +pub(crate) const KID_SIZE: usize = 4; -/// 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 = NoiseXKPattern3::MAX_SIZE - NoiseXKPattern3::MIN_SIZE; +/* Challenge protocol constants */ -/// Initial value of 'h'. -/// echo -n 'Noise_XKhfs+psk2_P384+Kyber1024_AESGCM_SHA512' | shasum -a 512 -pub(crate) const INITIAL_H: [u8; SHA512_HASH_SIZE] = - hex!("cd1f422196a5a614e24392cf34dcbf340ee61ad6ee6834274ff35fd42a7a5c44d04a045101555548a291778dd036b93ae21005a26c003213f57a5df9fb17f745"); -/// Initial value of 'ck' for rekeying. -/// echo -n 'Noise_KKpsk0_P384_AESGCM_SHA512' | shasum -a 512 -pub(crate) const INITIAL_H_REKEY: [u8; SHA512_HASH_SIZE] = - hex!("daeedd651ac9c5173f2eaaff996beebac6f3f1bfe9a70bb1cc54fa1fb2bf46260d71a3c4fb4d4ee36f654c31773a8a15e5d5be974a0668dc7db70f4e13ed172e"); +pub(crate) const SALT_SIZE: usize = 32; -pub(crate) const SESSION_ID_SIZE: usize = 4; +pub(crate) const COUNTER_SIZE: usize = 8; +pub(crate) const MAC_SIZE: usize = 16; +pub(crate) const POW_SIZE: usize = 8; +pub(crate) const POW_START: usize = COUNTER_SIZE + MAC_SIZE; -pub(crate) const PACKET_TYPE_NOISE_XK_PATTERN_1: u8 = 0; -pub(crate) const PACKET_TYPE_NOISE_XK_PATTERN_2: u8 = 1; -pub(crate) const PACKET_TYPE_NOISE_XK_PATTERN_3: u8 = 2; -pub(crate) const PACKET_TYPE_KEY_CONFIRM: u8 = 3; -pub(crate) const PACKET_TYPE_ACK: u8 = 4; -pub(crate) const PACKET_TYPE_NOISE_KK_PATTERN_1: u8 = 5; -pub(crate) const PACKET_TYPE_NOISE_KK_PATTERN_2: u8 = 6; -pub(crate) const PACKET_TYPE_SESSION_REJECTED: u8 = 7; -pub(crate) const PACKET_TYPE_DATA: u8 = 8; -pub(crate) const PACKET_TYPE_BOB_DOS_CHALLENGE: u8 = 9; -pub(crate) const PACKET_TYPE_RANGE_TRANSPORT: std::ops::Range = 3..9; - -/// Noise asks that the counter be initialized to 0 but for out of order reasons we have -/// to start it at 1. -/// Since with unreliable transport the first counter could always end up dropped this is -/// functionally equivalent to initializing to 0. -pub(crate) const INIT_COUNTER: u64 = 0; -pub(crate) const LABEL_RATCHET_STATE: u8 = b'R'; -pub(crate) const LABEL_HEADER_KEY: u8 = b'H'; -pub(crate) const LABEL_KEX_KEY: u8 = b'K'; - -/// Size of keys used during derivation, mixing, etc. -pub(crate) const NOISE_HASHLEN: usize = SHA512_HASH_SIZE; +pub(crate) const CHALLENGE_SIZE: usize = COUNTER_SIZE + MAC_SIZE + POW_SIZE; +pub(crate) const DIFFICULTY: u32 = 13; +/* Fragmentation constants */ +/* +Header: + [0..4] recipient key id +-- start AES(ck_es * h_e_e1_p) encrypted block -- + [5] fragment number (0..254) + [4] fragment count (1..255) +-- start packet nonce -- + [6] reserved zero + [7] packet type + [8..16] 64-bit counter +*/ pub(crate) const HEADER_SIZE: usize = 16; -pub(crate) const HEADER_PROTECT_ENC_START: usize = 4; -pub(crate) const HEADER_PROTECT_ENC_END: usize = 20; -pub(crate) const CHALLENGE_COUNTER_SIZE: usize = 8; -pub(crate) const CHALLENGE_MAC_SIZE: usize = 16; -pub(crate) const CHALLENGE_POW_SIZE: usize = 8; -pub(crate) const CHALLENGE_SALT_SIZE: usize = 32; +pub(crate) const PACKET_NONCE_SIZE: usize = 10; -pub(crate) const MAX_NOISE_HANDSHAKE_SIZE: usize = MAX_FRAGMENTS * MIN_TRANSPORT_MTU; -pub(crate) const CONTROL_PACKET_MAX_SIZE: usize = HEADER_SIZE + NoiseKKPattern1or2::SIZE + AES_GCM_TAG_SIZE; -pub(crate) const CONTROL_PACKET_MIN_SIZE: usize = HEADER_SIZE + AES_GCM_TAG_SIZE; +pub(crate) const HEADER_AUTH_START: usize = 4; +pub(crate) const HEADER_AUTH_END: usize = 20; +pub(crate) const PACKET_NONCE_START: usize = HEADER_SIZE - PACKET_NONCE_SIZE; -/// 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 = 16777216; -/// 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; -/// We hard-expire the Noise counter long before we reach u64::MAX because of the ABA problem. -/// Over (1<<16) threads would have to attempt to increment the counter at the same time -/// to overflow it. -/// Having (1<<16) threads active at the same time would crash basically any system. -pub(crate) const THREAD_SAFE_COUNTER_HARD_EXPIRE: u64 = u64::MAX - (1 << 16); +pub(crate) const FRAGMENT_NO_IDX: usize = 4; +pub(crate) const FRAGMENT_COUNT_IDX: usize = 5; + +pub(crate) const MAX_FRAGMENTS: usize = 48; -/// 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; // hard protocol max: 63 /// Maximum window over which session packets may be reordered to be defragmented and /// reassembled. Out of order fragments may be dropped in favor of newer fragments. /// Increasing this value makes a session consume more significantly more memory. pub(crate) const SESSION_MAX_FRAGMENTS_OOO: usize = 32; - /// The maximum number of unassociated packets that a receive context will cache. /// Additional packets will either be dropped or cause a different packet to be dropped /// from the cache. @@ -124,8 +69,10 @@ pub(crate) const MAX_UNASSOCIATED_HANDSHAKE_STATES: usize = 32; /// The maximum size a packet that is not associated to a session may be. /// Excludes the size of headers for fragmentation. -pub(crate) const MAX_UNASSOCIATED_PACKET_SIZE: usize = NoiseXKPattern1::MAX_SIZE - HEADER_SIZE; +pub(crate) const MAX_UNASSOCIATED_PACKET_SIZE: usize = HANDSHAKE_HELLO_MAX_SIZE - HEADER_SIZE; + +/* Key exchange constants */ /* XKhfs+psk2: <- s @@ -142,155 +89,53 @@ KKpsk0: -> psk, e, es, ss <- e, ee, se */ -/* -Header: - [0..4] recipient key id --- start AES(ck_es * h_e_e1_p) encrypted block -- - [4] fragment count (1..255) - [5] fragment number (0..254) - [6] reserved zero --- start AES-GCM Nonce -- - [7] packet type - [8..16] 64-bit counter or packet id -*/ -/// The first packet in Noise_XK exchange containing Alice's ephemeral keys, key id, -/// and a random symmetric key to protect header fragmentation fields for this session. -#[repr(C, packed)] -pub(crate) struct NoiseXKPattern1 { - pub header: [u8; HEADER_SIZE], - /// -- start prologue -- - pub alice_key_id: [u8; SESSION_ID_SIZE], - /// -- end prologue -- - pub noise_e: [u8; P384_PUBLIC_KEY_SIZE], - /// -- start AES-GCM(k_es) encrypted section - pub noise_e1: [u8; KYBER_PUBLICKEYBYTES], - /// -- end encrypted section - pub e1_gcm_tag: [u8; AES_GCM_TAG_SIZE], - pub payload: [u8; RATCHET_SIZE + RATCHET_SIZE + AES_GCM_TAG_SIZE + ChallengeResponse::SIZE], -} +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; -#[repr(C, packed)] -pub(crate) struct ChallengeResponse { - pub challenge_counter: [u8; CHALLENGE_COUNTER_SIZE], - pub challenge_mac: [u8; CHALLENGE_MAC_SIZE], - pub challenge_pow: [u8; CHALLENGE_POW_SIZE], -} +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"; -impl NoiseXKPattern1 { - pub const PROLOGUE_START: usize = HEADER_SIZE; - pub const PROLOGUE_END: usize = Self::PROLOGUE_START + SESSION_ID_SIZE; - pub const E1_ENC_START: usize = Self::PROLOGUE_END + P384_PUBLIC_KEY_SIZE; - pub const E1_AUTH_START: usize = Self::E1_ENC_START + KYBER_PUBLICKEYBYTES; - pub const P_ENC_START: usize = Self::E1_AUTH_START + AES_GCM_TAG_SIZE; +pub(crate) const LABEL_OTP_TO_RATCHET: &[u8; 19] = b"ZSSP_OTP_TO_RATCHET"; +pub(crate) const LABEL_KBKDF_CHAIN: &[u8; 4] = b"ZSSP"; +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 const MIN_SIZE: usize = Self::P_ENC_START + AES_GCM_TAG_SIZE + ChallengeResponse::SIZE; - pub const MAX_SIZE: usize = Self::MIN_SIZE + RATCHET_SIZE + RATCHET_SIZE; -} -impl ChallengeResponse { - pub const SIZE: usize = CHALLENGE_COUNTER_SIZE + CHALLENGE_MAC_SIZE + CHALLENGE_POW_SIZE; -} +pub(crate) const INIT_COUNTER: u64 = 0; +pub(crate) const EXPIRE_AFTER_USES: u64 = 4294967295; +pub(crate) const COUNTER_WINDOW_MAX_OOO: usize = 64; +pub(crate) const COUNTER_WINDOW_MAX_SKIP_AHEAD: u64 = 1 << 24; -#[repr(C, packed)] -pub(crate) struct BobDOSChallenge { - pub header: [u8; HEADER_SIZE], - pub alice_key_id: [u8; SESSION_ID_SIZE], - pub challenge_counter: [u8; CHALLENGE_COUNTER_SIZE], - pub challenge_mac: [u8; CHALLENGE_MAC_SIZE], - pub prior_challenge_pow: [u8; CHALLENGE_POW_SIZE], -} +/* Packet constants */ -impl BobDOSChallenge { - pub const SIZE: usize = HEADER_SIZE + SESSION_ID_SIZE + CHALLENGE_COUNTER_SIZE + CHALLENGE_MAC_SIZE + CHALLENGE_POW_SIZE; -} +pub(crate) const PACKET_TYPE_HANDSHAKE_HELLO: u8 = 0; +pub(crate) const PACKET_TYPE_HANDSHAKE_RESPONSE: u8 = 1; +pub(crate) const PACKET_TYPE_HANDSHAKE_COMPLETION: u8 = 2; +pub(crate) const PACKET_TYPE_KEY_CONFIRM: u8 = 3; +pub(crate) const PACKET_TYPE_ACK: u8 = 4; +pub(crate) const PACKET_TYPE_REKEY_INIT: u8 = 5; +pub(crate) const PACKET_TYPE_REKEY_COMPLETE: u8 = 6; +pub(crate) const PACKET_TYPE_SESSION_REJECTED: u8 = 7; +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; -/// The response to NoiseXKPattern1 containing Bob's ephemeral keys. -#[repr(C, packed)] -pub(crate) struct NoiseXKPattern2 { - pub header: [u8; HEADER_SIZE], - pub noise_e: [u8; P384_PUBLIC_KEY_SIZE], - /// -- start AES-GCM(k_es_ee) encrypted section - pub noise_ekem1: [u8; KYBER_CIPHERTEXTBYTES], - /// -- end encrypted section - pub ekem1_gcm_tag: [u8; AES_GCM_TAG_SIZE], - /// -- start AES-GCM(k_es_ee_ekem1_psk) encrypted section - pub bob_key_id: [u8; SESSION_ID_SIZE], - /// -- end encrypted section - pub p_gcm_tag: [u8; AES_GCM_TAG_SIZE], -} +pub(crate) const MAX_HANDSHAKE_SIZE: usize = MAX_FRAGMENTS * MIN_TRANSPORT_MTU; -impl NoiseXKPattern2 { - pub const EKEM1_ENC_START: usize = HEADER_SIZE + P384_PUBLIC_KEY_SIZE; - pub const EKEM1_AUTH_START: usize = Self::EKEM1_ENC_START + KYBER_CIPHERTEXTBYTES; - pub const P_ENC_START: usize = Self::EKEM1_AUTH_START + AES_GCM_TAG_SIZE; - pub const P_AUTH_START: usize = Self::P_ENC_START + SESSION_ID_SIZE; - pub const P_AUTH_END: usize = Self::P_AUTH_START + AES_GCM_TAG_SIZE; - pub const SIZE: usize = Self::P_AUTH_END; -} +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; -/// Alice's final response containing her identity (she already knows Bob's) and meta-data. -/// While Alice's response does match what is described in this struct, -/// this struct is unused because it would contain variable length fields. -/// It is present here for documentation purposes. -#[repr(C, packed)] -pub(crate) struct NoiseXKPattern3 { - pub header: [u8; HEADER_SIZE], - /// -- start AES-GCM(k_es_ee_ekem1_psk) encrypted section - pub noise_s: [u8; P384_PUBLIC_KEY_SIZE], - /// -- end encrypted section - pub s_gcm_tag: [u8; AES_GCM_TAG_SIZE], - /// -- start AES-GCM(k_es_ee_ekem1_psk_se) encrypted section - pub alice_blob: [u8; 0], - /// -- end encrypted section - pub p_gcm_tag: [u8; AES_GCM_TAG_SIZE], -} -impl NoiseXKPattern3 { - pub const MIN_SIZE: usize = HEADER_SIZE + P384_PUBLIC_KEY_SIZE + AES_GCM_TAG_SIZE + AES_GCM_TAG_SIZE; - pub const MAX_SIZE: usize = MAX_NOISE_HANDSHAKE_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; -#[repr(C, packed)] -pub(crate) struct NoiseKKPattern1or2 { - pub header: [u8; HEADER_SIZE], - pub noise_e: [u8; P384_PUBLIC_KEY_SIZE], - pub key_id: [u8; SESSION_ID_SIZE], - pub gcm_tag: [u8; AES_GCM_TAG_SIZE], - pub kek_tag: [u8; AES_GCM_TAG_SIZE], -} -impl NoiseKKPattern1or2 { - pub const ENC_START: usize = HEADER_SIZE + P384_PUBLIC_KEY_SIZE; - pub const AUTH_START: usize = Self::ENC_START + SESSION_ID_SIZE; - pub const AUTH_END: usize = Self::AUTH_START + AES_GCM_TAG_SIZE; - pub const SIZE: usize = Self::AUTH_END + 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 = MAX_HANDSHAKE_SIZE; -// Annotate only these structs as being compatible with byte_array_as_proto_buffer(). These structs -// are packed flat buffers containing only byte or byte array fields, making them safe to treat -// this way even on architectures that require type size aligned access. -pub(crate) trait ProtocolFlatBuffer {} -impl ProtocolFlatBuffer for NoiseXKPattern1 {} -impl ProtocolFlatBuffer for NoiseXKPattern2 {} -impl ProtocolFlatBuffer for NoiseKKPattern1or2 {} -impl ProtocolFlatBuffer for BobDOSChallenge {} -impl ProtocolFlatBuffer for ChallengeResponse {} +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; -#[inline(always)] -pub(crate) fn byte_array_as_proto_buffer(b: &[u8]) -> &B { - assert!(b.len() >= size_of::()); - unsafe { &*b.as_ptr().cast() } -} +pub(crate) const REKEY_SIZE: usize = P384_PUBLIC_KEY_SIZE + KID_SIZE + AES_GCM_TAG_SIZE + AES_GCM_TAG_SIZE; -#[inline(always)] -pub(crate) fn byte_array_as_proto_buffer_mut(b: &mut [u8]) -> &mut B { - assert!(b.len() >= size_of::()); - unsafe { &mut *b.as_mut_ptr().cast() } -} -/// Trick rust into letting us use a hasher that returns more than 64 bits. -pub(crate) struct ShaHasher<'a, ShaImpl: Sha512>(pub &'a mut ShaImpl); -impl<'a, ShaImpl: Sha512> Hasher for ShaHasher<'a, ShaImpl> { - fn finish(&self) -> u64 { - panic!() - } - fn write(&mut self, bytes: &[u8]) { - self.0.update(bytes) - } -} +pub(crate) const MAX_IDENTITY_SIZE: usize = MAX_HANDSHAKE_SIZE - HANDSHAKE_COMPLETION_MIN_SIZE; diff --git a/src/proto_old.rs b/src/proto_old.rs new file mode 100644 index 0000000..2f83f3e --- /dev/null +++ b/src/proto_old.rs @@ -0,0 +1,296 @@ +/* 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::hash::Hasher; +use std::mem::size_of; + +use crate::crypto::aes::AES_GCM_TAG_SIZE; +use crate::crypto::p384::P384_PUBLIC_KEY_SIZE; +use crate::crypto::pqc_kyber::{KYBER_CIPHERTEXTBYTES, KYBER_PUBLICKEYBYTES}; +use crate::crypto::sha512::{HashSha512, SHA512_HASH_SIZE}; +use hex_literal::hex; + +/// 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. +pub const MIN_TRANSPORT_MTU: usize = 128; + +pub const RATCHET_SIZE: usize = 32; + +/// 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 = NoiseXKPattern3::MAX_SIZE - NoiseXKPattern3::MIN_SIZE; + +/// Initial value of 'h'. +/// echo -n 'Noise_XKhfs+psk2_P384+Kyber1024_AESGCM_SHA512' | shasum -a 512 +pub(crate) const INITIAL_H: [u8; SHA512_HASH_SIZE] = + hex!("cd1f422196a5a614e24392cf34dcbf340ee61ad6ee6834274ff35fd42a7a5c44d04a045101555548a291778dd036b93ae21005a26c003213f57a5df9fb17f745"); +/// Initial value of 'ck' for rekeying. +/// echo -n 'Noise_KKpsk0_P384_AESGCM_SHA512' | shasum -a 512 +pub(crate) const INITIAL_H_REKEY: [u8; SHA512_HASH_SIZE] = + hex!("daeedd651ac9c5173f2eaaff996beebac6f3f1bfe9a70bb1cc54fa1fb2bf46260d71a3c4fb4d4ee36f654c31773a8a15e5d5be974a0668dc7db70f4e13ed172e"); + +pub(crate) const SESSION_ID_SIZE: usize = 4; + +pub(crate) const PACKET_TYPE_NOISE_XK_PATTERN_1: u8 = 0; +pub(crate) const PACKET_TYPE_NOISE_XK_PATTERN_2: u8 = 1; +pub(crate) const PACKET_TYPE_NOISE_XK_PATTERN_3: u8 = 2; +pub(crate) const PACKET_TYPE_KEY_CONFIRM: u8 = 3; +pub(crate) const PACKET_TYPE_ACK: u8 = 4; +pub(crate) const PACKET_TYPE_NOISE_KK_PATTERN_1: u8 = 5; +pub(crate) const PACKET_TYPE_NOISE_KK_PATTERN_2: u8 = 6; +pub(crate) const PACKET_TYPE_SESSION_REJECTED: u8 = 7; +pub(crate) const PACKET_TYPE_DATA: u8 = 8; +pub(crate) const PACKET_TYPE_BOB_DOS_CHALLENGE: u8 = 9; +pub(crate) const PACKET_TYPE_RANGE_TRANSPORT: std::ops::Range = 3..9; + +/// Noise asks that the counter be initialized to 0 but for out of order reasons we have +/// to start it at 1. +/// Since with unreliable transport the first counter could always end up dropped this is +/// functionally equivalent to initializing to 0. +pub(crate) const INIT_COUNTER: u64 = 0; +pub(crate) const LABEL_RATCHET_STATE: u8 = b'R'; +pub(crate) const LABEL_HEADER_KEY: u8 = b'H'; +pub(crate) const LABEL_KEX_KEY: u8 = b'K'; + +/// Size of keys used during derivation, mixing, etc. +pub(crate) const HASHLEN: usize = SHA512_HASH_SIZE; + +pub(crate) const HEADER_SIZE: usize = 16; +pub(crate) const HEADER_PROTECT_ENC_START: usize = 4; +pub(crate) const HEADER_PROTECT_ENC_END: usize = 20; +pub(crate) const CHALLENGE_COUNTER_SIZE: usize = 8; +pub(crate) const CHALLENGE_MAC_SIZE: usize = 16; +pub(crate) const CHALLENGE_POW_SIZE: usize = 8; +pub(crate) const CHALLENGE_SALT_SIZE: usize = 32; + +pub(crate) const MAX_NOISE_HANDSHAKE_SIZE: usize = MAX_FRAGMENTS * MIN_TRANSPORT_MTU; +pub(crate) const CONTROL_PACKET_MAX_SIZE: usize = HEADER_SIZE + NoiseKKPattern1or2::SIZE + AES_GCM_TAG_SIZE; +pub(crate) const CONTROL_PACKET_MIN_SIZE: usize = HEADER_SIZE + AES_GCM_TAG_SIZE; + +/// 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 = 16777216; +/// 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; +/// We hard-expire the Noise counter long before we reach u64::MAX because of the ABA problem. +/// Over (1<<16) threads would have to attempt to increment the counter at the same time +/// to overflow it. +/// Having (1<<16) threads active at the same time would crash basically any system. +pub(crate) const THREAD_SAFE_COUNTER_HARD_EXPIRE: u64 = u64::MAX - (1 << 16); + +/// 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; // hard protocol max: 63 +/// Maximum window over which session packets may be reordered to be defragmented and +/// reassembled. Out of order fragments may be dropped in favor of newer fragments. +/// Increasing this value makes a session consume more significantly more memory. +pub(crate) const SESSION_MAX_FRAGMENTS_OOO: usize = 32; + +/// The maximum number of unassociated packets that a receive context will cache. +/// Additional packets will either be dropped or cause a different packet to be dropped +/// from the cache. +/// Larger values consume more memory but provide better reliability and DDOS resistance. +pub(crate) const MAX_UNASSOCIATED_PACKETS: usize = 32; +/// The maximum number of fragments of unassociated packets that a receive context will +/// cache. +/// All unassociated fragments share the same buffer, when it fills up additional +/// fragments will be dropped or cause other fragments to be dropped from the cache. +/// Larger values consume more memory but provide better reliability and DDOS resistance. +pub(crate) const MAX_UNASSOCIATED_FRAGMENTS: usize = 32 * 32; +/// The maximum number of `NoiseXKBobHandshakeState` that a receive context will cache. +/// These are extremely large and since Alice has not been authenticated we put a hard +/// limit to how many we cache. +/// Larger values consume more memory but provide better reliability and DDOS resistance. +pub(crate) const MAX_UNASSOCIATED_HANDSHAKE_STATES: usize = 32; + +/// The maximum size a packet that is not associated to a session may be. +/// Excludes the size of headers for fragmentation. +pub(crate) const MAX_UNASSOCIATED_PACKET_SIZE: usize = NoiseXKPattern1::MAX_SIZE - HEADER_SIZE; + +/* +XKhfs+psk2: + <- s + ... + -> e, es, e1 + <- e, ee, ekem1, psk + -> s, se +*/ +/* +KKpsk0: + -> s + <- s + ... + -> psk, e, es, ss + <- e, ee, se +*/ +/* +Header: + [0..4] recipient key id +-- start AES(ck_es * h_e_e1_p) encrypted block -- + [4] fragment count (1..255) + [5] fragment number (0..254) + [6] reserved zero +-- start AES-GCM Nonce -- + [7] packet type + [8..16] 64-bit counter or packet id +*/ +/// The first packet in Noise_XK exchange containing Alice's ephemeral keys, key id, +/// and a random symmetric key to protect header fragmentation fields for this session. +#[repr(C, packed)] +pub(crate) struct NoiseXKPattern1 { + pub header: [u8; HEADER_SIZE], + /// -- start prologue -- + pub alice_key_id: [u8; SESSION_ID_SIZE], + /// -- end prologue -- + pub noise_e: [u8; P384_PUBLIC_KEY_SIZE], + /// -- start AES-GCM(k_es) encrypted section + pub noise_e1: [u8; KYBER_PUBLICKEYBYTES], + /// -- end encrypted section + pub e1_gcm_tag: [u8; AES_GCM_TAG_SIZE], + pub payload: [u8; RATCHET_SIZE + RATCHET_SIZE + AES_GCM_TAG_SIZE + ChallengeResponse::SIZE], +} + +#[repr(C, packed)] +pub(crate) struct ChallengeResponse { + pub challenge_counter: [u8; CHALLENGE_COUNTER_SIZE], + pub challenge_mac: [u8; CHALLENGE_MAC_SIZE], + pub challenge_pow: [u8; CHALLENGE_POW_SIZE], +} + +impl NoiseXKPattern1 { + pub const PROLOGUE_START: usize = HEADER_SIZE; + pub const PROLOGUE_END: usize = Self::PROLOGUE_START + SESSION_ID_SIZE; + pub const E1_ENC_START: usize = Self::PROLOGUE_END + P384_PUBLIC_KEY_SIZE; + pub const E1_AUTH_START: usize = Self::E1_ENC_START + KYBER_PUBLICKEYBYTES; + pub const P_ENC_START: usize = Self::E1_AUTH_START + AES_GCM_TAG_SIZE; + + pub const MIN_SIZE: usize = Self::P_ENC_START + AES_GCM_TAG_SIZE + ChallengeResponse::SIZE; + pub const MAX_SIZE: usize = Self::MIN_SIZE + RATCHET_SIZE + RATCHET_SIZE; +} +impl ChallengeResponse { + pub const SIZE: usize = CHALLENGE_COUNTER_SIZE + CHALLENGE_MAC_SIZE + CHALLENGE_POW_SIZE; +} + +#[repr(C, packed)] +pub(crate) struct BobDOSChallenge { + pub header: [u8; HEADER_SIZE], + pub alice_key_id: [u8; SESSION_ID_SIZE], + pub challenge_counter: [u8; CHALLENGE_COUNTER_SIZE], + pub challenge_mac: [u8; CHALLENGE_MAC_SIZE], + pub prior_challenge_pow: [u8; CHALLENGE_POW_SIZE], +} + +impl BobDOSChallenge { + pub const SIZE: usize = HEADER_SIZE + SESSION_ID_SIZE + CHALLENGE_COUNTER_SIZE + CHALLENGE_MAC_SIZE + CHALLENGE_POW_SIZE; +} + +/// The response to NoiseXKPattern1 containing Bob's ephemeral keys. +#[repr(C, packed)] +pub(crate) struct NoiseXKPattern2 { + pub header: [u8; HEADER_SIZE], + pub noise_e: [u8; P384_PUBLIC_KEY_SIZE], + /// -- start AES-GCM(k_es_ee) encrypted section + pub noise_ekem1: [u8; KYBER_CIPHERTEXTBYTES], + /// -- end encrypted section + pub ekem1_gcm_tag: [u8; AES_GCM_TAG_SIZE], + /// -- start AES-GCM(k_es_ee_ekem1_psk) encrypted section + pub bob_key_id: [u8; SESSION_ID_SIZE], + /// -- end encrypted section + pub p_gcm_tag: [u8; AES_GCM_TAG_SIZE], +} + +impl NoiseXKPattern2 { + pub const EKEM1_ENC_START: usize = HEADER_SIZE + P384_PUBLIC_KEY_SIZE; + pub const EKEM1_AUTH_START: usize = Self::EKEM1_ENC_START + KYBER_CIPHERTEXTBYTES; + pub const P_ENC_START: usize = Self::EKEM1_AUTH_START + AES_GCM_TAG_SIZE; + pub const P_AUTH_START: usize = Self::P_ENC_START + SESSION_ID_SIZE; + pub const P_AUTH_END: usize = Self::P_AUTH_START + AES_GCM_TAG_SIZE; + pub const SIZE: usize = Self::P_AUTH_END; +} + +/// Alice's final response containing her identity (she already knows Bob's) and meta-data. +/// While Alice's response does match what is described in this struct, +/// this struct is unused because it would contain variable length fields. +/// It is present here for documentation purposes. +#[repr(C, packed)] +pub(crate) struct NoiseXKPattern3 { + pub header: [u8; HEADER_SIZE], + /// -- start AES-GCM(k_es_ee_ekem1_psk) encrypted section + pub noise_s: [u8; P384_PUBLIC_KEY_SIZE], + /// -- end encrypted section + pub s_gcm_tag: [u8; AES_GCM_TAG_SIZE], + /// -- start AES-GCM(k_es_ee_ekem1_psk_se) encrypted section + pub alice_blob: [u8; 0], + /// -- end encrypted section + pub p_gcm_tag: [u8; AES_GCM_TAG_SIZE], +} +impl NoiseXKPattern3 { + pub const MIN_SIZE: usize = HEADER_SIZE + P384_PUBLIC_KEY_SIZE + AES_GCM_TAG_SIZE + AES_GCM_TAG_SIZE; + pub const MAX_SIZE: usize = MAX_NOISE_HANDSHAKE_SIZE; +} + +#[repr(C, packed)] +pub(crate) struct NoiseKKPattern1or2 { + pub header: [u8; HEADER_SIZE], + pub noise_e: [u8; P384_PUBLIC_KEY_SIZE], + pub key_id: [u8; SESSION_ID_SIZE], + pub gcm_tag: [u8; AES_GCM_TAG_SIZE], + pub kek_tag: [u8; AES_GCM_TAG_SIZE], +} +impl NoiseKKPattern1or2 { + pub const ENC_START: usize = HEADER_SIZE + P384_PUBLIC_KEY_SIZE; + pub const AUTH_START: usize = Self::ENC_START + SESSION_ID_SIZE; + pub const AUTH_END: usize = Self::AUTH_START + AES_GCM_TAG_SIZE; + pub const SIZE: usize = Self::AUTH_END + AES_GCM_TAG_SIZE; +} + +// Annotate only these structs as being compatible with byte_array_as_proto_buffer(). These structs +// are packed flat buffers containing only byte or byte array fields, making them safe to treat +// this way even on architectures that require type size aligned access. +pub(crate) trait ProtocolFlatBuffer {} +impl ProtocolFlatBuffer for NoiseXKPattern1 {} +impl ProtocolFlatBuffer for NoiseXKPattern2 {} +impl ProtocolFlatBuffer for NoiseKKPattern1or2 {} +impl ProtocolFlatBuffer for BobDOSChallenge {} +impl ProtocolFlatBuffer for ChallengeResponse {} + +#[inline(always)] +pub(crate) fn byte_array_as_proto_buffer(b: &[u8]) -> &B { + assert!(b.len() >= size_of::()); + unsafe { &*b.as_ptr().cast() } +} + +#[inline(always)] +pub(crate) fn byte_array_as_proto_buffer_mut(b: &mut [u8]) -> &mut B { + assert!(b.len() >= size_of::()); + unsafe { &mut *b.as_mut_ptr().cast() } +} +/// Trick rust into letting us use a hasher that returns more than 64 bits. +pub(crate) struct ShaHasher<'a, ShaImpl: HashSha512>(pub &'a mut ShaImpl); +impl<'a, ShaImpl: HashSha512> Hasher for ShaHasher<'a, ShaImpl> { + fn finish(&self) -> u64 { + panic!() + } + fn write(&mut self, bytes: &[u8]) { + self.0.update(bytes) + } +} diff --git a/src/ratchet_state.rs b/src/ratchet_state.rs index 273a358..378eb78 100644 --- a/src/ratchet_state.rs +++ b/src/ratchet_state.rs @@ -1,62 +1,76 @@ -/* 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 zeroize::Zeroizing; -use std::num::NonZeroU64; - -use crate::crypto::secret::Secret; -use crate::RATCHET_SIZE; - -#[derive(Clone, PartialEq, Eq)] -pub enum RatchetState { - Null, - Empty, - NonEmpty(NonEmptyRatchetState), -} -use RatchetState::*; -impl RatchetState { - pub fn new_nonempty(key: Secret, fingerprint: Secret, chain_len: NonZeroU64) -> Self { - NonEmpty(NonEmptyRatchetState { key, fingerprint, chain_len }) - } - pub fn new_initial_states() -> [RatchetState; 2] { - [RatchetState::Empty, RatchetState::Null] - } - pub fn is_null(&self) -> bool { - matches!(self, Null) - } - pub fn is_empty(&self) -> bool { - matches!(self, Empty) - } - pub fn nonempty(&self) -> Option<&NonEmptyRatchetState> { - match self { - NonEmpty(rs) => Some(rs), - _ => None, - } - } - pub fn chain_len(&self) -> u64 { - self.nonempty().map_or(0, |rs| rs.chain_len.get()) - } - pub fn fingerprint(&self) -> Option<&[u8; RATCHET_SIZE]> { - self.nonempty().map(|rs| rs.fingerprint.as_ref()) - } - 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.as_ref()), - } - } -} +use crate::crypto::secure_eq; +use crate::proto::*; /// A ratchet key and fingerprint, /// along with the length of the ratchet chain the keys were derived from. -#[derive(Clone, PartialEq, Eq)] -pub struct NonEmptyRatchetState { - pub key: Secret, - pub fingerprint: Secret, - pub chain_len: NonZeroU64, +/// +/// 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, +} +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, + } + } +} +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); + // buffer.extend(LABEL_OTP_TO_RATCHET); + // buffer.push(0x00); + // buffer.extend((2u16 * 512u16).to_be_bytes()); + // let r1 = Hmac::hmac(otp, &buffer); + // buffer[0] = 2; + // let r2 = Hmac::hmac(otp, &buffer); + // Self::new( + // Zeroizing::new(r1[..RATCHET_SIZE].try_into().unwrap()), + // Zeroizing::new(r2[..RATCHET_SIZE].try_into().unwrap()), + // 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/ratchet_state_old.rs b/src/ratchet_state_old.rs new file mode 100644 index 0000000..73519e6 --- /dev/null +++ b/src/ratchet_state_old.rs @@ -0,0 +1,62 @@ +/* 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::num::NonZeroU64; + +#[derive(Clone, PartialEq, Eq)] +pub enum RatchetState { + Null, + Empty, + NonEmpty(NonEmptyRatchetState), +} +use RatchetState::*; +use zeroize::Zeroizing; + +use crate::proto::RATCHET_SIZE; +impl RatchetState { + pub fn new_nonempty(key: Zeroizing<[u8; RATCHET_SIZE]>, fingerprint: Zeroizing<[u8; RATCHET_SIZE]>, chain_len: NonZeroU64) -> Self { + NonEmpty(NonEmptyRatchetState { key, fingerprint, chain_len }) + } + pub fn new_initial_states() -> [RatchetState; 2] { + [RatchetState::Empty, RatchetState::Null] + } + pub fn is_null(&self) -> bool { + matches!(self, Null) + } + pub fn is_empty(&self) -> bool { + matches!(self, Empty) + } + pub fn nonempty(&self) -> Option<&NonEmptyRatchetState> { + match self { + NonEmpty(rs) => Some(rs), + _ => None, + } + } + pub fn chain_len(&self) -> u64 { + self.nonempty().map_or(0, |rs| rs.chain_len.get()) + } + //pub fn fingerprint(&self) -> Option<&[u8; RATCHET_SIZE]> { + // self.nonempty().map(|rs| rs.fingerprint.as_ref()) + //} + //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.as_ref()), + // } + //} +} +/// A ratchet key and fingerprint, +/// along with the length of the ratchet chain the keys were derived from. +#[derive(Clone, PartialEq, Eq)] +pub struct NonEmptyRatchetState { + pub key: Zeroizing<[u8; RATCHET_SIZE]>, + pub fingerprint: Zeroizing<[u8; RATCHET_SIZE]>, + pub chain_len: NonZeroU64, +} diff --git a/src/result.rs b/src/result.rs new file mode 100644 index 0000000..d542227 --- /dev/null +++ b/src/result.rs @@ -0,0 +1,162 @@ +use std::sync::Arc; + +use crate::applicationlayer::ApplicationLayer; +use crate::zeta::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 { + /// An invalid parameter was supplied to the function. + InvalidPublicKey, + + RatchetIoError(IoError), +} + +/// An error that can occur when attempting to send data over a session. +/// 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. + /// + /// The associated session will no longer send or receive data and must be immediately dropped. + SessionExpired, + + /// Attempt to send using a session without a shared symmetric key. + /// The caller should wait until the handshake has completed. + SessionNotEstablished, + + /// Data object is too large to send, even with fragmentation. + DataTooLarge, +} + +/// A type of fault occurred because we received a bad packet. +/// +/// An unauthenticated attacker can intentionally trigger any of these, so it is best to +/// treat these as raw user input that needs to be sanitize. +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +pub enum FaultType { + /// The received packet was addressed to an unrecognized local session. + UnknownLocalKeyId, + + /// The received packet from the remote peer was not well formed. + InvalidPacket, + + /// Packet failed one or more authentication (MAC) checks. + FailedAuth, + + /// Packet counter was repeated or outside window of allowed counter values. + ExpiredCounter, + + /// Packet contained protocol control parameters that are disallowed at this point in + /// time by ZSSP. + OutOfSequence, +} + +/// An error that occurred during the receipt of a given packet. +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +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. + /// + /// Because an unauthenticated remote peer can force these to occur with specific + /// contained information, it is recommended in production to either drop these + /// immediately, or log them safely to a local output stream and then drop them. + ByzantineFault { + /// The type of fault that has occurred. Be cautious if you choose to read this + /// value, as an attacker has control over it. + error: FaultType, + /// Some byzantine faults within ZSSP are naturally occurring, i.e. they can occur + /// between two well behaved and trusted parties executing the protocol. + /// This boolean is false if this is one of these faults. If you go to the file and + /// line number specified by this error you will find a comment describing + /// how and why exactly this fault can occur naturally. + /// + /// Faults that can occur because the underlying communication medium is lossy and + /// sequentially inconsistent (as in UDP) are considered naturally occurring. + /// However ZSSP considers faults that occur because data integrity has not been + /// persevered (i.e. bits have been flipped) to be unnatural. + /// ZSSP also considers collisions of what are supposed to be uniform random + /// numbers to be unnatural. + unnatural: bool, + /// The file of this implementation of ZSSP from which this error was generated. + #[cfg(feature = "debug")] + file: &'static str, + /// The line number of this implementation of ZSSP from which this error was + /// generated. As such this number uniquely identifies each possible fault that + /// can occur during ZSSP. Advanced user can use this information to debug more + /// complicated usages of ZSSP. + #[cfg(feature = "debug")] + line: u32, + }, + + /// Rekeying failed and session secret has reached its hard usage count limit. + /// The associated session will no longer function and has to be dropped. + MaxKeyLifetimeExceeded, + + /// One of the ratchet saving or lookup functions returned an error, so the packet had to be + /// dropped. + RatchetIoError(IoError), +} + +macro_rules! byzantine_fault { + ($name:expr, $unnatural:ident) => { + ReceiveError::ByzantineFault { + #[cfg(feature = "debug")] + file: file!(), + #[cfg(feature = "debug")] + line: line!(), + error: $name, + unnatural: $unnatural, + } + }; +} +pub(crate) use byzantine_fault; + +/// Result generated by the context packet receive function, with possible payloads. +#[derive(Clone)] +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), +} +/// Something that can occur to an associated session when a packet is received successfully, +/// including receiving a payload of decrypted, authenticated data. +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +pub enum SessionEvent { + /// The received packet was valid, and it contained the necessary keys to fully establish a new + /// session with Alice, the handshake initiator. + /// + /// 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, + /// 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. + /// + /// Alice will receive this return value when the received packet confirms both parties + /// have completed the initial handshake and now have a shared session with each other. + /// If according to the upper protocol, Bob is the first party to send data, it is possible for + /// Alice to start receiving data from Bob before this value is returned. + /// + /// This return value can only occur once per session, only for session objects that were + /// created with `Context::open`. + Established, + /// Bob explicitly refused to establish a session with Alice, and sent us an error code. + /// The application should immediately drop this session as Bob will not allow us to connect. + /// + /// This return value cannot occur after a session is fully established. + Rejected, + /// The received packet was valid and a data payload was decoded and authenticated. + Data(Vec), + /// The received packet was some authentic protocol control packet. No action needs to be taken. + Control, +} diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index e6e5e52..a2caf2b 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -1,116 +1,34 @@ -/* 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 crate::crypto::aes::AES_256_KEY_SIZE; -use crate::crypto::secret::Secret; -use crate::crypto::sha512::HmacSha512; +use std::marker::PhantomData; -use crate::proto::NOISE_HASHLEN; +use arrayvec::ArrayVec; +use zeroize::Zeroizing; -#[derive(Clone)] -pub(crate) struct SymmetricState { - chaining_key: Secret, - token_counter: u8, +use crate::crypto::aes::{LowThroughputAesGcm, HighThroughputAesGcmPool, AES_GCM_IV_SIZE, AES_GCM_TAG_SIZE}; +use crate::crypto::sha512::{HashSha512, HmacSha512}; +use crate::{applicationlayer::ApplicationLayer, crypto::aes::AES_256_KEY_SIZE}; +use crate::proto::*; + +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>, +} +impl Clone for SymmetricState { + fn clone(&self) -> Self { + Self { + k: self.k.clone(), + ck: self.ck.clone(), + h: self.h.clone(), + _app: PhantomData, + } + } } -impl SymmetricState { - pub(crate) fn new(h: [u8; NOISE_HASHLEN]) -> Self { - Self { chaining_key: Secret(h), token_counter: b'P' } - } - /// Corresponds to Noise `MixKey`. - pub(crate) fn mix_key(&mut self, hm: &mut impl HmacSha512, input_key_material: &[u8]) { - let mut next_ck = Secret::new(); - self.kbkdf(hm, input_key_material, self.label(), 2, next_ck.as_mut(), None, None); - self.token_counter += 1; - - self.chaining_key.overwrite(&next_ck); - // We don't need a key at this step of Noise, so generating that key and calling - // `InitializeKey` would be completely pointless. - } - /// Corresponds to Noise `MixKey` followed by `InitializeKey`. - pub(crate) fn mix_key_initialize_key(&mut self, hm: &mut impl HmacSha512, input_key_material: &[u8]) -> Secret { - let mut next_ck = Secret::new(); - let mut temp_k = [0u8; NOISE_HASHLEN]; - - self.kbkdf(hm, input_key_material, self.label(), 2, next_ck.as_mut(), Some(&mut temp_k), None); - self.token_counter += 1; - - self.chaining_key.overwrite(&next_ck); - Secret::from_bytes_then_delete(&mut temp_k[..AES_256_KEY_SIZE]) - } - /// Corresponds to Noise `MixKeyAndHash`. - pub(crate) fn mix_key_and_hash(&mut self, hm: &mut impl HmacSha512, input_key_material: &[u8]) -> [u8; NOISE_HASHLEN] { - let mut next_ck = Secret::new(); - let mut temp_h = [0u8; NOISE_HASHLEN]; - - self.kbkdf(hm, input_key_material, self.label(), 3, next_ck.as_mut(), Some(&mut temp_h), None); - self.token_counter += 1; - - self.chaining_key.overwrite(&next_ck); - temp_h - } - /// Corresponds to Noise `MixKeyAndHash` followed by `InitializeKey`. - pub(crate) fn mix_key_and_hash_initialize_key( - &mut self, - hm: &mut impl HmacSha512, - input_key_material: &[u8], - ) -> ([u8; NOISE_HASHLEN], Secret) { - let mut next_ck = Secret::new(); - let mut temp_h = [0u8; NOISE_HASHLEN]; - let mut temp_k = [0u8; NOISE_HASHLEN]; - - self.kbkdf( - hm, - input_key_material, - self.label(), - 3, - next_ck.as_mut(), - Some(&mut temp_h), - Some(&mut temp_k), - ); - self.token_counter += 1; - - self.chaining_key.overwrite(&next_ck); - (temp_h, Secret::from_bytes_then_delete(&mut temp_k[..AES_256_KEY_SIZE])) - } - /// Get an additional symmetric key (ASK) that is a collision resistant hash of the transcript, - /// is forward secrect and is cryptographically independent from all other produced keys. - /// Based on Noise's unstable ASK mechanism, using KBKDF instead of HKDF. - /// https://github.com/noiseprotocol/noise_wiki/wiki/Additional-Symmetric-Keys. - pub(crate) fn get_ask2( - &self, - hm: &mut impl HmacSha512, - label: u8, - noise_h: &[u8; NOISE_HASHLEN], - ) -> (Secret, Secret) { - let mut temp_k1 = [0u8; NOISE_HASHLEN]; - let mut temp_k2 = [0u8; NOISE_HASHLEN]; - self.kbkdf(hm, noise_h, [b'A', b'S', b'K', label], 2, &mut temp_k1, Some(&mut temp_k2), None); - ( - Secret::from_bytes_then_delete(&mut temp_k1[..AES_256_KEY_SIZE]), - Secret::from_bytes_then_delete(&mut temp_k2[..AES_256_KEY_SIZE]), - ) - } - /// Corresponds to Noise `Split`. - pub(crate) fn split(self, hm: &mut impl HmacSha512) -> (Secret, Secret) { - let mut temp_k1 = [0u8; NOISE_HASHLEN]; - let mut temp_k2 = [0u8; NOISE_HASHLEN]; - self.kbkdf(hm, &[], self.label(), 2, &mut temp_k1, Some(&mut temp_k2), None); - // Normally KBKDF would not truncate to derive the correct length of AES keys, - // but Noise specifies that the AES keys be truncated from NOISE_HASHLEN to AES_256_KEY_SIZE. - ( - Secret::from_bytes_then_delete(&mut temp_k1[..AES_256_KEY_SIZE]), - Secret::from_bytes_then_delete(&mut temp_k2[..AES_256_KEY_SIZE]), - ) - } - fn label(&self) -> [u8; 4] { - [b'Z', b'S', b'S', self.token_counter] - } +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 @@ -122,36 +40,124 @@ 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, - hm: &mut impl HmacSha512, + hmac: &mut App::HmacHash, input_key_material: &[u8], - label: [u8; 4], + label: &[u8; 4], num_outputs: u16, - output1: &mut [u8; NOISE_HASHLEN], - output2: Option<&mut [u8; NOISE_HASHLEN]>, - output3: Option<&mut [u8; NOISE_HASHLEN]>, + output1: &mut [u8; HASHLEN], + output2: Option<&mut [u8; HASHLEN]>, + output3: Option<&mut [u8; HASHLEN]>, ) { - let l = &(num_outputs * 512u16).to_be_bytes(); + const LABEL_START: usize = 1; + const LABEL_END: usize = 5; + const CONTEXT_START: usize = 6; + const LEN_START: usize = 70; + const LEN_END: usize = 72; + let mut buffer = Zeroizing::new([0u8; LEN_END]); + buffer[0] = 1; + buffer[LABEL_START..LABEL_END].copy_from_slice(label); + buffer[LABEL_END] = 0x00; + buffer[CONTEXT_START..LEN_START].copy_from_slice(self.ck.as_ref()); + buffer[LEN_START..LEN_END].copy_from_slice(&(num_outputs * 8 * HASHLEN as u16).to_be_bytes()); + + debug_assert!(num_outputs >= 1); + hmac.hash(input_key_material, buffer.as_ref(), output1); - hm.reset(input_key_material); - hm.update(&[1, label[0], label[1], label[2], label[3], 0x00]); - hm.update(self.chaining_key.as_ref()); - hm.update(l); - hm.finish(output1); if let Some(output2) = output2 { - hm.reset(input_key_material); - hm.update(&[2, label[0], label[1], label[2], label[3], 0x00]); - hm.update(self.chaining_key.as_ref()); - hm.update(l); - hm.finish(output2); + debug_assert!(num_outputs >= 2); + buffer[0] = 2; + hmac.hash(input_key_material, buffer.as_ref(), output2); } + if let Some(output3) = output3 { - hm.reset(input_key_material); - hm.update(&[3, label[0], label[1], label[2], label[3], 0x00]); - hm.update(self.chaining_key.as_ref()); - hm.update(l); - hm.finish(output3); + debug_assert!(num_outputs >= 3); + buffer[0] = 3; + hmac.hash(input_key_material, buffer.as_ref(), output3); } } + + /// Corresponds to Noise `Initialize` on a SymmetricState. + pub fn initialize(h: [u8; HASHLEN]) -> Self { + Self { + k: Zeroizing::default(), + ck: Zeroizing::new(h), + h, + _app: PhantomData, + } + } + /// Corresponds to Noise `MixKey`. + pub fn mix_key(&mut self, hmac: &mut App::HmacHash, input_key_material: &[u8]) { + let mut next_ck = Zeroizing::new([0u8; HASHLEN]); + let mut temp_k = Zeroizing::new([0u8; HASHLEN]); + + self.kbkdf(hmac, 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]); + } + /// Corresponds to Noise `MixHash`. + pub fn mix_hash(&mut self, hash: &mut App::Hash, data: &[u8]) { + hash.update(&self.h); + hash.update(data); + hash.finish_and_reset(&mut self.h); + } + /// Corresponds to Noise `MixKeyAndHash`. + pub fn mix_key_and_hash(&mut self, hash: &mut App::Hash, hmac: &mut App::HmacHash, input_key_material: &[u8]) { + let mut next_ck = Zeroizing::new([0u8; HASHLEN]); + let mut temp_h = [0u8; HASHLEN]; + let mut temp_k = Zeroizing::new([0u8; HASHLEN]); + + self.kbkdf( + hmac, + input_key_material, + LABEL_KBKDF_CHAIN, + 3, + &mut next_ck, + Some(&mut temp_h), + Some(&mut temp_k), + ); + + *self.ck = *next_ck; + self.mix_hash(hash, &temp_h); + self.k.clone_from_slice(&temp_k[..AES_256_KEY_SIZE]); + } + /// Corresponds to Noise `EncryptAndHash`. + #[must_use] + pub fn encrypt_and_hash_in_place(&mut self, hash: &mut App::Hash, iv: [u8; AES_GCM_IV_SIZE], data: &mut [u8]) -> [u8; AES_GCM_TAG_SIZE] { + let tag = App::Aead::encrypt_in_place(&self.k, &iv, &self.h, data); + hash.update(&self.h); + hash.update(data); + hash.update(&tag); + hash.finish_and_reset(&mut self.h); + tag + } + /// Corresponds to Noise `DecryptAndHash`. + #[must_use] + pub fn decrypt_and_hash_in_place(&mut self, hash: &mut App::Hash, iv: [u8; AES_GCM_IV_SIZE], data: &mut [u8], tag: [u8; AES_GCM_TAG_SIZE]) -> bool { + hash.update(&self.h); + hash.update(data); + hash.update(&tag); + let is_auth = App::Aead::decrypt_in_place(&self.k, &iv, &self.h, data, tag.as_ref().try_into().unwrap()); + hash.finish_and_reset(&mut self.h); + is_auth + } + /// Corresponds to Noise `Split`. + pub fn split(self, hmac: &mut App::HmacHash, key1: &mut [u8; HASHLEN], key2: &mut [u8; HASHLEN]) { + self.kbkdf(hmac, &[], LABEL_KBKDF_CHAIN, 2, key1, Some(key2), None); + } + /// Get an additional symmetric key (ASK) that is a collision resistant hash of the transcript, + /// is forward secrect and is cryptographically independent from all other produced keys. + /// Based on Noise's unstable ASK mechanism, using KBKDF instead of HKDF. + /// https://github.com/noiseprotocol/noise_wiki/wiki/Additional-Symmetric-Keys. + pub fn get_ask(&self, hmac: &mut App::HmacHash, label: &[u8; 4], key1: &mut [u8; HASHLEN], key2: &mut [u8; HASHLEN]) { + self.kbkdf(hmac, &self.h, label, 2, key1, Some(key2), None); + } + /// Used for internally debugging a key exchange. + #[allow(unused)] + pub(crate) fn finger(&self) -> (u8, u8, u8) { + (self.k[0], self.ck[0], self.h[0]) + } } diff --git a/src/symmetric_state_old.rs b/src/symmetric_state_old.rs new file mode 100644 index 0000000..264fc45 --- /dev/null +++ b/src/symmetric_state_old.rs @@ -0,0 +1,156 @@ +/* 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 crate::crypto::aes::AES_256_KEY_SIZE; +use crate::crypto::sha512::HmacSha512; + +use crate::proto::NOISE_HASHLEN; + +#[derive(Clone)] +pub(crate) struct SymmetricState { + chaining_key: Secret, + token_counter: u8, +} + +impl SymmetricState { + pub(crate) fn new(h: [u8; NOISE_HASHLEN]) -> Self { + Self { chaining_key: Secret(h), token_counter: b'P' } + } + /// Corresponds to Noise `MixKey`. + pub(crate) fn mix_key(&mut self, hm: &mut impl HmacSha512, input_key_material: &[u8]) { + let mut next_ck = Secret::new(); + + self.kbkdf(hm, input_key_material, self.label(), 2, next_ck.as_mut(), None, None); + self.token_counter += 1; + + self.chaining_key.overwrite(&next_ck); + // We don't need a key at this step of Noise, so generating that key and calling + // `InitializeKey` would be completely pointless. + } + /// Corresponds to Noise `MixKey` followed by `InitializeKey`. + pub(crate) fn mix_key_initialize_key(&mut self, hm: &mut impl HmacSha512, input_key_material: &[u8]) -> Secret { + let mut next_ck = Secret::new(); + let mut temp_k = [0u8; NOISE_HASHLEN]; + + self.kbkdf(hm, input_key_material, self.label(), 2, next_ck.as_mut(), Some(&mut temp_k), None); + self.token_counter += 1; + + self.chaining_key.overwrite(&next_ck); + Secret::from_bytes_then_delete(&mut temp_k[..AES_256_KEY_SIZE]) + } + /// Corresponds to Noise `MixKeyAndHash`. + pub(crate) fn mix_key_and_hash(&mut self, hm: &mut impl HmacSha512, input_key_material: &[u8]) -> [u8; NOISE_HASHLEN] { + let mut next_ck = Secret::new(); + let mut temp_h = [0u8; NOISE_HASHLEN]; + + self.kbkdf(hm, input_key_material, self.label(), 3, next_ck.as_mut(), Some(&mut temp_h), None); + self.token_counter += 1; + + self.chaining_key.overwrite(&next_ck); + temp_h + } + /// Corresponds to Noise `MixKeyAndHash` followed by `InitializeKey`. + pub(crate) fn mix_key_and_hash_initialize_key( + &mut self, + hm: &mut impl HmacSha512, + input_key_material: &[u8], + ) -> ([u8; NOISE_HASHLEN], Secret) { + let mut next_ck = Secret::new(); + let mut temp_h = [0u8; NOISE_HASHLEN]; + let mut temp_k = [0u8; NOISE_HASHLEN]; + + self.kbkdf( + hm, + input_key_material, + self.label(), + 3, + next_ck.as_mut(), + Some(&mut temp_h), + Some(&mut temp_k), + ); + self.token_counter += 1; + + self.chaining_key.overwrite(&next_ck); + (temp_h, Secret::from_bytes_then_delete(&mut temp_k[..AES_256_KEY_SIZE])) + } + /// Get an additional symmetric key (ASK) that is a collision resistant hash of the transcript, + /// is forward secrect and is cryptographically independent from all other produced keys. + /// Based on Noise's unstable ASK mechanism, using KBKDF instead of HKDF. + /// https://github.com/noiseprotocol/noise_wiki/wiki/Additional-Symmetric-Keys. + pub(crate) fn get_ask2( + &self, + hm: &mut impl HmacSha512, + label: u8, + noise_h: &[u8; NOISE_HASHLEN], + ) -> (Secret, Secret) { + let mut temp_k1 = [0u8; NOISE_HASHLEN]; + let mut temp_k2 = [0u8; NOISE_HASHLEN]; + self.kbkdf(hm, noise_h, [b'A', b'S', b'K', label], 2, &mut temp_k1, Some(&mut temp_k2), None); + ( + Secret::from_bytes_then_delete(&mut temp_k1[..AES_256_KEY_SIZE]), + Secret::from_bytes_then_delete(&mut temp_k2[..AES_256_KEY_SIZE]), + ) + } + /// Corresponds to Noise `Split`. + pub(crate) fn split(self, hm: &mut impl HmacSha512) -> (Secret, Secret) { + let mut temp_k1 = [0u8; NOISE_HASHLEN]; + let mut temp_k2 = [0u8; NOISE_HASHLEN]; + self.kbkdf(hm, &[], self.label(), 2, &mut temp_k1, Some(&mut temp_k2), None); + // Normally KBKDF would not truncate to derive the correct length of AES keys, + // but Noise specifies that the AES keys be truncated from NOISE_HASHLEN to AES_256_KEY_SIZE. + ( + Secret::from_bytes_then_delete(&mut temp_k1[..AES_256_KEY_SIZE]), + Secret::from_bytes_then_delete(&mut temp_k2[..AES_256_KEY_SIZE]), + ) + } + fn label(&self) -> [u8; 4] { + [b'Z', b'S', b'S', self.token_counter] + } + /// 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 + /// `HKDF(self.chaining_key, input_key_material)` but this is how NIST rolls. + /// These are the values we have assigned to the 4 variables involved in their KDF: + /// * K_IN = `input_key_material` + /// * Label = `label` + /// * Context = `self.chaining_key` + /// * L = `num_outputs*512u16` + /// We have intentionally made every input small and fixed size to avoid unnecessary complexity + /// and data representation ambiguity. + fn kbkdf( + &self, + hm: &mut impl HmacSha512, + input_key_material: &[u8], + label: [u8; 4], + num_outputs: u16, + output1: &mut [u8; NOISE_HASHLEN], + output2: Option<&mut [u8; NOISE_HASHLEN]>, + output3: Option<&mut [u8; NOISE_HASHLEN]>, + ) { + let l = &(num_outputs * 512u16).to_be_bytes(); + + hm.reset(input_key_material); + hm.update(&[1, label[0], label[1], label[2], label[3], 0x00]); + hm.update(self.chaining_key.as_ref()); + hm.update(l); + hm.finish(output1); + if let Some(output2) = output2 { + hm.reset(input_key_material); + hm.update(&[2, label[0], label[1], label[2], label[3], 0x00]); + hm.update(self.chaining_key.as_ref()); + hm.update(l); + hm.finish(output2); + } + if let Some(output3) = output3 { + hm.reset(input_key_material); + hm.update(&[3, label[0], label[1], label[2], label[3], 0x00]); + hm.update(self.chaining_key.as_ref()); + hm.update(l); + hm.finish(output3); + } + } +} diff --git a/src/zeta.rs b/src/zeta.rs new file mode 100644 index 0000000..159df8b --- /dev/null +++ b/src/zeta.rs @@ -0,0 +1,1394 @@ +use arrayvec::ArrayVec; +use rand_core::RngCore; +use std::cmp::Reverse; +use std::collections::HashMap; +use std::num::NonZeroU32; +use std::ops::{Deref, DerefMut}; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::{Arc, Mutex, Weak, RwLock}; +use zeroize::Zeroizing; + +use crate::antireplay::Window; +use crate::applicationlayer::ApplicationLayer; +use crate::applicationlayer::RatchetUpdate; +use crate::challenge::{gen_null_response, respond_to_challenge_in_place}; +use crate::context::ContextInner; +//use crate::context::{log, ContextInner, SessionMap}; +use crate::crypto::aes::{HighThroughputAesGcmPool, LowThroughputAesGcm, AES_GCM_IV_SIZE, AES_256_KEY_SIZE, AES_GCM_TAG_SIZE}; +use crate::crypto::p384::{P384KeyPair, P384PublicKey, P384_ECDH_SHARED_SECRET_SIZE, P384_PUBLIC_KEY_SIZE}; +use crate::crypto::sha512::{HashSha512, HmacSha512}; +use crate::crypto::kyber1024::{Kyber1024PrivateKey, KYBER_PUBLIC_KEY_SIZE, KYBER_CIPHERTEXT_SIZE, KYBER_PLAINTEXT_SIZE}; +use crate::indexed_heap::BinaryHeapIndex; +//use crate::indexed_heap::BinaryHeapIndex; +//use crate::fragmentation::DefragBuffer; +use crate::proto::*; +use crate::ratchet_state::RatchetState; +use crate::result::{byzantine_fault, FaultType, OpenError, ReceiveError, SendError}; +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_IV_SIZE] { + let mut ret = [0u8; AES_GCM_IV_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())) +} +fn create_ratchet_state(hmac: &mut App::HmacHash, noise: &mut SymmetricState, pre_chain_len: u64) -> RatchetState { + let mut rk = Zeroizing::new([0u8; HASHLEN]); + let mut rf = Zeroizing::new([0u8; HASHLEN]); + noise.get_ask(hmac, LABEL_RATCHET_STATE, &mut rk, &mut rf); + RatchetState::new(Zeroizing::new(rk[..RATCHET_SIZE].try_into().unwrap()), Zeroizing::new(rf[..RATCHET_SIZE].try_into().unwrap()), pre_chain_len + 1) +} + +/// Corresponds to the Zeta State Machine found in Section 4.1. +pub(crate) struct Session { + //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. + pub was_bob: bool, + queue_idx: BinaryHeapIndex, + + s_remote: App::PublicKey, + send_counter: AtomicU64, + + pub window: Window, + //defrag: [Mutex>; SESSION_MAX_FRAGMENTS_OOO], + + state_machine_lock: Mutex<()>, + state: RwLock>, + + /// Pre-computed rekeying values. + noise_kk_ss: Zeroizing<[u8; P384_ECDH_SHARED_SECRET_SIZE]>, +} +pub(crate) struct MutableState { + ratchet_state1: RatchetState, + ratchet_state2: Option, + + key_creation_counter: u64, + key_index: bool, + keys: [DuplexKey; 2], + pub hk_send: Zeroizing<[u8; AES_256_KEY_SIZE]>, + + resend_timer: i64, + timeout_timer: i64, + pub beta: ZetaAutomata, +} + +/// 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, + pub kid_recv: NonZeroU32, + pub hk_send: Zeroizing<[u8; AES_256_KEY_SIZE]>, + e_secret: App::KeyPair, + noise: SymmetricState, + //pub defrag: DefragBuffer, +} + +#[derive(Default)] +pub(crate) struct DuplexKey { + send: Keys, + recv: Keys, + nk: Option, +} + +#[derive(Default)] +pub(crate) struct Keys { + kek: Option>, + 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, + e_secret: App::KeyPair, + e1_secret: App::Kem, + identity: ArrayVec, + kid_send: u32, + nonce: [u8; AES_GCM_IV_SIZE], + packet: ArrayVec, +} + +/// Corresponds to the ZKE Automata found in Section 4.1 - Definition 2. +pub(crate) enum ZetaAutomata { + Null, + A1(StateA1), + A3 { + identity: ArrayVec, + kid_send: u32, + nonce: [u8; AES_GCM_IV_SIZE], + packet: ArrayVec, + }, + S1, + S2, + R1 { + noise: SymmetricState, + e_secret: App::KeyPair, + k1: Vec, + }, + R2 { + k2: Vec, + }, +} + +impl SymmetricState { + fn write_e(&mut self, hash: &mut App::Hash, hmac: &mut App::HmacHash, rng: &Mutex, packet: &mut ArrayVec) -> App::KeyPair { + let e_secret = App::KeyPair::generate(rng.lock().unwrap().deref_mut()); + let pub_key = e_secret.public_key_bytes(); + packet.extend(pub_key); + self.mix_hash(hash, &pub_key); + self.mix_key(hmac, &pub_key); + e_secret + } + fn read_e(&mut self, hash: &mut App::Hash, hmac: &mut App::HmacHash, i: &mut usize, packet: &[u8]) -> Option { + let j = *i + P384_PUBLIC_KEY_SIZE; + let pub_key = &packet[*i..j]; + self.mix_hash(hash, pub_key); + self.mix_key(hmac, pub_key); + *i = j; + App::PublicKey::from_bytes((pub_key).try_into().unwrap()) + } + fn mix_dh(&mut self, hmac: &mut App::HmacHash, secret: &App::KeyPair, remote: &App::PublicKey) -> Option<()> { + let mut ecdh_secret = Zeroizing::new([0u8; P384_ECDH_SHARED_SECRET_SIZE]); + if secret.agree(&remote, &mut ecdh_secret) { + self.mix_key(hmac, ecdh_secret.as_ref()); + Some(()) + } else { + None + } + } +} + +/// Generate a random local key id that is currently unused. +fn gen_kid(session_map: &HashMap, rng: &mut impl RngCore) -> NonZeroU32 { + loop { + if let Some(kid) = NonZeroU32::new(rng.next_u32()) { + if !session_map.contains_key(&kid) { + return kid; + } + } + } +} + +impl MutableState { + fn key_ref(&self, is_next: bool) -> &DuplexKey { + &self.keys[(self.key_index ^ is_next) as usize] + } + fn key_mut(&mut self, is_next: bool) -> &mut DuplexKey { + &mut self.keys[(self.key_index ^ is_next) as usize] + } + pub(crate) fn next_timer(&self) -> i64 { + self.timeout_timer.min(self.resend_timer) + } +} + +fn create_a1_state( + hash: &mut App::Hash, hmac: &mut App::HmacHash, rng: &Mutex, + s_remote: &App::PublicKey, + kid_recv: NonZeroU32, + ratchet_state1: &RatchetState, + ratchet_state2: Option<&RatchetState>, + identity: &[u8], +) -> Option> { + // <- s + // ... + // -> e, es, e1 + let mut noise = SymmetricState::::initialize(PROTOCOL_NAME_NOISE_XK); + let mut x1 = ArrayVec::::new(); + // Noise process prologue. + let kid = kid_recv.get().to_be_bytes(); + x1.extend(kid); + noise.mix_hash(hash, &kid); + noise.mix_hash(hash, &s_remote.to_bytes()); + // Process message pattern 1 e token. + let e_secret = noise.write_e(hash, hmac, rng, &mut x1); + // Process message pattern 1 es token. + noise.mix_dh(hmac, &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()); + x1.extend(e1_public); + x1.extend([0u8; AES_GCM_IV_SIZE]); + x1.extend(noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, 0), &mut x1[i..])); + // Process message pattern 1 payload. + let i = x1.len(); + if let Some(rf) = ratchet_state1.fingerprint() { + x1.try_extend_from_slice(rf).unwrap(); + } + if let Some(Some(rf)) = ratchet_state2.map(|rs| rs.fingerprint()) { + x1.try_extend_from_slice(rf).unwrap(); + } + x1.extend(noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, 1), &mut x1[i..])); + + let c = u64::from_be_bytes(x1[x1.len() - 8..].try_into().unwrap()); + + x1.extend(gen_null_response(rng.lock().unwrap().deref_mut())); + Some(StateA1 { + noise, + e_secret, + e1_secret, + identity: identity.try_into().unwrap(), + kid_send: 0, + nonce: to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, c), + packet: x1, + }) +} +/// Corresponds to Transition Algorithm 1 found in Section 4.3. +pub(crate) fn trans_to_a1( + app: App, + ctx: &Arc>, + s_remote: App::PublicKey, + session_data: App::SessionData, + identity: &[u8], + //send: impl FnOnce(&Packet), +) -> Result>, OpenError> { + let (ratchet_state1, ratchet_state2) = app + .restore_by_identity(&s_remote, &session_data) + .map_err(|e| OpenError::RatchetIoError(e))?; + + let mut session_queue = ctx.session_queue.lock().unwrap(); + let mut session_map = ctx.session_map.write().unwrap(); + let kid_recv = gen_kid(session_map.deref(), ctx.rng.lock().unwrap().deref_mut()); + + let hash = &mut App::Hash::new(); + let hmac = &mut App::HmacHash::new(); + let a1 = create_a1_state(hash, hmac, &ctx.rng, &s_remote, kid_recv, &ratchet_state1, ratchet_state2.as_ref(), identity).ok_or(OpenError::InvalidPublicKey)?; + let packet = a1.packet.clone(); + + let mut hk_recv = Zeroizing::new([0u8; HASHLEN]); + let mut hk_send = Zeroizing::new([0u8; HASHLEN]); + a1.noise.get_ask(hmac, LABEL_HEADER_KEY, &mut hk_recv, &mut hk_send); + + let current_time = app.time(); + let queue_idx = session_queue.reserve_index(); + let mut session = Arc::new(Session { + session_data, + was_bob: false, + queue_idx, + s_remote, + send_counter: AtomicU64::new(0), + window: Window::new(), + state_machine_lock: Mutex::new(()), + state: RwLock::new(MutableState { + ratchet_state1, + ratchet_state2, + key_creation_counter: 0, + key_index: true, + keys: [DuplexKey::default(), DuplexKey::default()], + hk_send: Zeroizing::new(hk_send[..AES_256_KEY_SIZE].try_into().unwrap()), + resend_timer: current_time + App::SETTINGS.resend_time as i64, + timeout_timer: current_time + App::SETTINGS.initial_offer_timeout as i64, + beta: ZetaAutomata::A1(a1), + }), + noise_kk_ss: Zeroizing::new([0u8; P384_ECDH_SHARED_SECRET_SIZE]), + }); + let mut state = session.state.write().unwrap(); + state.key_mut(true).recv.kid = Some(kid_recv); + + session_map.insert(kid_recv, Arc::downgrade(&session)); + session_queue.push_reserved( + queue_idx, + Arc::downgrade(&session), + Reverse(state.next_timer()), + ); + + //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]) { +// 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(), +// challenge, +// (&mut x1[response_start..]).try_into().unwrap(), +// ); +// } +//} +/// Corresponds to Transition Algorithm 2 found in Section 4.3. +pub(crate) fn received_x1_trans( + app: &App, + ctx: &ContextInner, + n: [u8; AES_GCM_IV_SIZE], + mut x1: Vec, + //send: impl FnOnce(&Packet, &[u8; AES_256_KEY_SIZE]), +) -> Result<(), ReceiveError> { + use FaultType::*; + // <- s + // ... + // -> e, es, e1 + // <- e, ee, ekem1, psk + 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..] { + return Err(byzantine_fault!(FailedAuth, true)); + } + let hash = &mut App::Hash::new(); + let hmac = &mut App::HmacHash::new(); + let mut noise = SymmetricState::::initialize(PROTOCOL_NAME_NOISE_XK); + let mut i = 0; + // Noise process prologue. + let j = i + KID_SIZE; + noise.mix_hash(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))?; + noise.mix_hash(hash, &ctx.s_secret.public_key_bytes()); + i = j; + // Process message pattern 1 e token. + let e_remote = noise.read_e(hash, hmac, &mut i, &x1).ok_or(byzantine_fault!(FailedAuth, true))?; + // Process message pattern 1 es token. + noise.mix_dh(hmac, &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 = i + AES_GCM_TAG_SIZE; + let tag = x1[j..k].try_into().unwrap(); + if !noise.decrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, 0), &mut x1[i..j], tag) { + return Err(byzantine_fault!(FailedAuth, true)); + } + let e1_start = i; + let e1_end = j; + i = j; + // Process message pattern 1 payload. + let k = x1.len(); + let j = k - AES_GCM_TAG_SIZE; + let tag = x1[j..k].try_into().unwrap(); + if !noise.decrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, 1), &mut x1[i..j], tag) { + return Err(byzantine_fault!(FailedAuth, true)); + } + + let mut ratchet_state = None; + while i + RATCHET_SIZE <= j { + match app.restore_by_fingerprint((&x1[i..i + RATCHET_SIZE]).try_into().unwrap()) { + Ok(None) => {} + Ok(Some(rs)) => { + ratchet_state = Some(rs); + break; + } + Err(e) => return Err(ReceiveError::RatchetIoError(e)), + } + i += RATCHET_SIZE; + } + let ratchet_state = if let Some(rs) = ratchet_state { + rs + } else { + if app.hello_requires_recognized_ratchet() { + return Err(byzantine_fault!(FailedAuth, true)); + } + RatchetState::empty() + }; + let mut hk_recv = Zeroizing::new([0u8; HASHLEN]); + let mut hk_send = Zeroizing::new([0u8; HASHLEN]); + noise.get_ask(hmac, LABEL_HEADER_KEY, &mut hk_recv, &mut hk_send); + + let mut x2 = ArrayVec::new(); + // Process message pattern 2 e token. + let e_secret = noise.write_e(hash, hmac, &ctx.rng, &mut x2); + // Process message pattern 2 ee token. + noise.mix_dh(hmac, &e_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + // Process message pattern 2 ekem1 token. + { + let i = x2.len(); + let mut ekem1_secret = Zeroizing::new([0u8; KYBER_PLAINTEXT_SIZE]); + let ekem1 = App::Kem::encapsulate(ctx.rng.lock().unwrap().deref_mut(), (&x1[e1_start..e1_end]).try_into().unwrap(), &mut ekem1_secret).ok_or(byzantine_fault!(FailedAuth, true))?; + x2.extend(ekem1); + x2.extend(noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), &mut x2[i..])); + noise.mix_key(hmac, ekem1_secret.as_ref()); + } + // Process message pattern 2 psk2 token. + noise.mix_key_and_hash(hash, hmac, ratchet_state.key.as_ref()); + // Process message pattern 2 payload. + let kid_recv = gen_kid(ctx.session_map.read().unwrap().deref(), ctx.rng.lock().unwrap().deref_mut()); + + let i = x2.len(); + x2.extend(kid_recv.get().to_be_bytes()); + x2.extend(noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), &mut x2[i..])); + + let i = x2.len(); + let mut c = 0u64.to_be_bytes(); + c[5] = x2[i - 3]; + c[6] = x2[i - 2]; + c[7] = x2[i - 1]; + let c = u64::from_be_bytes(c); + + /// + //ctx.b2_map.lock().unwrap().insert( + // kid_recv, + // StateB2 { + // ratchet_state, + // kid_send, + // kid_recv, + // hk_send: hk_send.clone(), + // e_secret, + // noise, + // defrag: DefragBuffer::new(Some(hk_recv)), + // }, + //); + + //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( + app: &App, + ctx: &Arc>, + session: &Arc>, + kid: NonZeroU32, + n: [u8; AES_GCM_IV_SIZE], + mut x2: &[u8], + //send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), +) -> Result<(), ReceiveError> { + use FaultType::*; + // <- e, ee, ekem1, psk + // -> s, se + if HANDSHAKE_RESPONSE_SIZE != x2.len() { + return Err(byzantine_fault!(InvalidPacket, true)); + } + + let kex_lock = session.state_machine_lock.lock().unwrap(); + let state = session.state.read().unwrap(); + let hash = &mut App::Hash::new(); + let hmac = &mut App::HmacHash::new(); + + if Some(kid) != state.key_ref(true).recv.kid { + 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..] { + return Err(byzantine_fault!(FailedAuth, true)); + } + let result = (|| { + if let ZetaAutomata::A1(StateA1 { noise, e_secret, e1_secret, identity, .. }) = &state.beta { + let mut noise = noise.clone(); + let mut i = 0; + // Process message pattern 2 e token. + let e_remote = noise.read_e(hash, hmac, &mut i, &x2).ok_or(byzantine_fault!(FailedAuth, true))?; + // Process message pattern 2 ee token. + noise.mix_dh(hmac, 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; + let tag = x2[j..k].try_into().unwrap(); + if !noise.decrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), &mut x2[i..j], tag) { + return Err(byzantine_fault!(FailedAuth, true)); + } + let mut ekem1_secret = Zeroizing::new([0u8; KYBER_PLAINTEXT_SIZE]); + if !e1_secret.decapsulate((&x2[i..j]).try_into().unwrap(), &mut ekem1_secret) { + return Err(byzantine_fault!(FailedAuth, true)); + } + noise.mix_key(hmac, ekem1_secret.as_ref()); + drop(ekem1_secret); + i = j; + // We attempt to decrypt the payload at most three times. First two times with + // the ratchet key Alice remembers, and final time with a ratchet + // key of zero if Alice allows ratchet downgrades. + // The following code is not constant time, meaning we leak to an + // attacker whether or not we downgraded. + // We don't currently consider this sensitive enough information to hide. + let j = i + KID_SIZE; + let k = j + AES_GCM_TAG_SIZE; + 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 mut noise = noise.clone(); + let mut payload = payload.clone(); + // Process message pattern 2 psk token. + noise.mix_key_and_hash(hash, hmac, ratchet_key); + // Process message pattern 2 payload. + if !noise.decrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), &mut payload, tag) { + return None; + } + NonZeroU32::new(u32::from_be_bytes(payload)).map(|kid2| (kid2, noise)) + }; + // Check first key. + let mut ratchet_i = 1; + let mut chain_len = state.ratchet_state1.chain_len; + let mut result = test_ratchet_key(state.ratchet_state1.key.as_ref()); + // Check second key. + if result.is_none() { + ratchet_i = 2; + if let Some(rs) = state.ratchet_state2.as_ref() { + chain_len = rs.chain_len; + result = test_ratchet_key(rs.key.as_ref()); + } + } + // Check zero key. + if result.is_none() && !app.initiator_disallows_downgrade(session) { + chain_len = 0; + result = test_ratchet_key(&[0u8; RATCHET_SIZE]); + if result.is_some() { + // TODO: add some kind of warning callback or signal. + } + } + + let (kid_send, mut noise) = result.ok_or(byzantine_fault!(FailedAuth, true))?; + let mut x3 = ArrayVec::new(); + + // Process message pattern 3 s token. + let i = x3.len(); + x3.extend(ctx.s_secret.public_key_bytes()); + x3.extend(noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 1), &mut x3[i..])); + // Process message pattern 3 se token. + noise.mix_dh(hmac, &ctx.s_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + // Process message pattern 3 payload. + let i = x3.len(); + x3.try_extend_from_slice(identity).unwrap(); + x3.extend(noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 0), &mut x3[i..])); + + let new_ratchet_state = create_ratchet_state(hmac, &mut noise, chain_len); + + let (ratchet_to_preserve, ratchet_to_delete) = if ratchet_i == 1 { + (Some(&state.ratchet_state1), state.ratchet_state2.as_ref()) + } else { + (state.ratchet_state2.as_ref(), Some(&state.ratchet_state1)) + }; + let result = app.save_ratchet_state( + &session.s_remote, + &session.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)); + } + + let kek_recv = Zeroizing::new([0u8; HASHLEN]); + let kek_send = Zeroizing::new([0u8; HASHLEN]); + let nk_recv = Zeroizing::new([0u8; HASHLEN]); + let nk_send = Zeroizing::new([0u8; HASHLEN]); + noise.get_ask(hmac, LABEL_KEX_KEY, &mut kek_recv, &mut kek_send); + noise.split(hmac, &mut nk_recv, &mut nk_send); + let nonce = to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 0); + + //let identity = identity.clone(); + drop(state); + let mut state = session.state.write().unwrap(); + + state.key_mut(true).send.kid = Some(kid_send); + state.key_mut(true).send.kek = Some(Zeroizing::new(kek_send[..AES_256_KEY_SIZE].try_into().unwrap())); + state.key_mut(true).recv.kek = Some(Zeroizing::new(kek_recv[..AES_256_KEY_SIZE].try_into().unwrap())); + state.key_mut(true).nk = Some(App::AeadPool::new((&nk_send[..AES_256_KEY_SIZE]).try_into().unwrap(), (&nk_recv[..AES_256_KEY_SIZE]).try_into().unwrap())); + state.ratchet_state2 = Some(state.ratchet_state1.clone()); + state.ratchet_state1 = new_ratchet_state.clone(); + let current_time = app.time(); + state.key_creation_counter = session.send_counter.load(Ordering::Relaxed); + state.resend_timer = current_time + App::SETTINGS.resend_time as i64; + state.timeout_timer = current_time + App::SETTINGS.initial_offer_timeout as i64; + state.beta = ZetaAutomata::A3 { identity: identity.clone(), packet: x3, kid_send: kid_send.get(), nonce }; + + Ok(()) + } else { + Err(byzantine_fault!(FailedAuth, true)) + } + })(); + match &result { + Err(ReceiveError::ByzantineFault { .. }) => timeout_trans(state, session, app, ctx, app.time(), send), + Ok(packet) => send(packet, Some(&state.hk_send)), + _ => {} + } + result.map(|_| ()) +} +/// Corresponds to Transition Algorithm 4 found in Section 4.3. +pub(crate) fn received_x3_trans( + zeta: StateB2, + app: &App, + ctx: &Arc>, + kid: NonZeroU32, + mut x3: Vec, + send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), +) -> Result>, ReceiveError> { + use FaultType::*; + // -> s, se + if x3.len() < HANDSHAKE_COMPLETION_MIN_SIZE { + return Err(byzantine_fault!(InvalidPacket, true)); + } + if kid != zeta.kid_recv { + return Err(byzantine_fault!(UnknownLocalKeyId, true)); + } + + let mut noise = zeta.noise.clone(); + let mut i = 0; + // Process message pattern 3 s token. + let j = i + P384_PUBLIC_KEY_SIZE; + let k = j + AES_GCM_TAG_SIZE; + let tag = x3[j..k].try_into().unwrap(); + 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))?; + i = k; + // Process message pattern 3 se token. + 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; + let tag = x3[j..k].try_into().unwrap(); + if !noise.decrypt_and_hash_in_place(to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 0), &mut x3[i..j], tag) { + return Err(byzantine_fault!(FailedAuth, true)); + } + let identity_start = i; + let identity_end = j; + + let (kek_send, kek_recv) = noise.get_ask(LABEL_KEX_KEY); + let c = INIT_COUNTER; + + 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); + 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 + // handshake is being dropped, so nonce reuse can't happen. + Packet(zeta.kid_send.get(), n, d) + }; + 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()) { + if !responder_disallows_downgrade && zeta.ratchet_state.fingerprint().is_none() { + // TODO: add some kind of warning callback or signal. + } else { + if !responder_silently_rejects { + send(&create_reject(), Some(&zeta.hk_send)) + } + return Err(byzantine_fault!(FailedAuth, true)); + } + } + + 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 result = app.save_ratchet_state( + &s_remote, + &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)); + } + + 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 []); + c1.extend(&tag); + + let (nk1, nk2) = noise.split(); + let keys = DuplexKey { + send: Keys { kek: Some(kek_send), nk: Some(nk1), kid: Some(zeta.kid_send) }, + recv: Keys { kek: Some(kek_recv), nk: Some(nk2), kid: Some(zeta.kid_recv) }, + }; + let current_time = app.time(); + + let mut session_map = ctx.session_map.lock().unwrap(); + 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 + // before Alice was able to respond. It is unlikely but possible. + Occupied(_) => return Err(byzantine_fault!(OutOfSequence, false)), + Vacant(entry) => entry, + }; + let session = Arc::new(Session(Mutex::new(Zeta { + ctx: Arc::downgrade(ctx), + session_data, + was_bob: true, + s_remote, + send_counter: INIT_COUNTER + 1, + key_creation_counter: INIT_COUNTER + 1, + key_index: false, + keys: [keys, DuplexKey::default()], + 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, + beta: ZetaAutomata::S1, + counter_antireplay_window: std::array::from_fn(|_| 0), + defrag: zeta.defrag, + }))); + entry.insert(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) + } + Err(e) => Err(ReceiveError::RatchetIoError(e)), + } + } else { + if !responder_silently_rejects { + send(&create_reject(), Some(&zeta.hk_send)) + } + 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, + rng: &Mutex, + kid: NonZeroU32, + n: [u8; AES_GCM_IV_SIZE], + c1: Vec, + send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), +) -> Result> { + use FaultType::*; + + if c1.len() != KEY_CONFIRMATION_SIZE { + return Err(byzantine_fault!(InvalidPacket, true)); + } + let is_other = if Some(kid) == zeta.key_ref(true).recv.kid { + true + } else if Some(kid) == zeta.key_ref(false).recv.kid { + false + } else { + // Some key confirmation may have arrived extremely delayed. + // It is unlikely but possible. + 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 tag = c1[..].try_into().unwrap(); + if !App::Aead::decrypt_in_place(specified_key, n, None, &mut [], tag) { + return Err(byzantine_fault!(FailedAuth, true)); + } + let (_, c) = from_nonce(&n); + if !zeta.update_counter_window(c) { + return Err(byzantine_fault!(ExpiredCounter, true)); + } + + let just_establised = is_other && matches!(&zeta.beta, ZetaAutomata::A3 { .. }); + if is_other { + if let ZetaAutomata::A3 { .. } | ZetaAutomata::R2 { .. } = &zeta.beta { + if zeta.ratchet_state2.is_some() { + let result = app.save_ratchet_state( + &zeta.s_remote, + &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)); + } + } + + 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; + zeta.resend_timer = i64::MAX; + zeta.beta = ZetaAutomata::S2; + } + } + let mut c2 = Vec::new(); + + 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 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 +/// Section 4.3. +pub(crate) fn received_c2_trans( + zeta: &mut Zeta, + app: &App, + rng: &Mutex, + kid: NonZeroU32, + n: [u8; AES_GCM_IV_SIZE], + c2: Vec, +) -> Result<(), ReceiveError> { + use FaultType::*; + + if c2.len() != ACKNOWLEDGEMENT_SIZE { + return Err(byzantine_fault!(InvalidPacket, true)); + } + if Some(kid) != zeta.key_ref(false).recv.kid { + // Some acknowledgement may have arrived extremely delayed. + return Err(byzantine_fault!(UnknownLocalKeyId, false)); + } + if !matches!(&zeta.beta, ZetaAutomata::S1) { + // Some acknowledgement may have arrived extremely delayed. + return Err(byzantine_fault!(OutOfSequence, false)); + } + + 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) { + return Err(byzantine_fault!(FailedAuth, true)); + } + let (_, c) = from_nonce(&n); + if !zeta.update_counter_window(c) { + 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; + zeta.resend_timer = i64::MAX; + 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, + n: [u8; AES_GCM_IV_SIZE], + d: Vec, +) -> Result<(), ReceiveError> { + use FaultType::*; + + if d.len() != SESSION_REJECTED_SIZE { + return Err(byzantine_fault!(InvalidPacket, true)); + } + if Some(kid) != zeta.key_ref(true).recv.kid || !matches!(&zeta.beta, ZetaAutomata::A3 { .. }) { + return Err(byzantine_fault!(OutOfSequence, true)); + } + + 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) { + return Err(byzantine_fault!(FailedAuth, true)); + } + let (_, c) = from_nonce(&n); + if !zeta.update_counter_window(c) { + return Err(byzantine_fault!(ExpiredCounter, true)); + } + + 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>, + ctx: &Arc>, + app: &App, + current_time: i64, + send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), +) { + 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 { + ZetaAutomata::Null => return, + ZetaAutomata::A1(StateA1 { packet, .. }) => { + log!(app, ResentX1(session)); + return send(packet, None); + } + ZetaAutomata::A3 { packet, .. } => { + log!(app, ResentX3(session)); + return send(packet, Some(&zeta.hk_send)); + } + ZetaAutomata::S1 => { + log!(app, ResentKeyConfirm(session)); + (PACKET_TYPE_KEY_CONFIRM, Vec::new()) + } + ZetaAutomata::S2 => return, + ZetaAutomata::R1 { k1, .. } => { + log!(app, ResentK1(session)); + (PACKET_TYPE_REKEY_INIT, k1.clone()) + } + ZetaAutomata::R2 { k2, .. } => { + log!(app, ResentK2(session)); + (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), + ); + } +} +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 + } else { + Arc::downgrade(&session) + }; + let new_kid_recv = gen_kid(session_map.deref(), rng.lock().unwrap().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, + session: &Arc>, + app: &App, + ctx: &Arc>, + current_time: i64, + send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), +) { + match &zeta.beta { + ZetaAutomata::Null => {} + ZetaAutomata::A1(StateA1 { identity, .. }) | ZetaAutomata::A3 { identity, .. } => { + if matches!(&zeta.beta, ZetaAutomata::A1(_)) { + log!(app, TimeoutX1(session)); + } else { + log!(app, TimeoutX3(session)); + } + 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(), + ) { + let (hk_recv, hk_send) = a1.noise.get_ask(LABEL_HEADER_KEY); + let packet = a1.packet.clone(); + + 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.beta = ZetaAutomata::A1(a1); + zeta.defrag = DefragBuffer::new(Some(hk_recv)); + + send(&packet, None); + } else { + zeta.expire(); + } + } + 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 + // <- s + // ... + // -> psk, e, es, ss + let mut k1 = Vec::new(); + let mut noise = SymmetricState::initialize(PROTOCOL_NAME_NOISE_KK); + // Noise process prologue. + 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()); + // Process message pattern 1 e token. + let e_secret = noise.write_e(&ctx.rng, &mut k1); + // Process message pattern 1 es token. + if noise.mix_dh(&e_secret, &zeta.s_remote).is_none() { + zeta.expire(); + return; + } + // Process message pattern 1 ss token. + if noise.mix_dh(&ctx.s_secret, &zeta.s_remote).is_none() { + zeta.expire(); + return; + } + // 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); + + 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 = 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); + + send(&Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, k1), Some(&zeta.hk_send)); + } + ZetaAutomata::S1 { .. } => { + log!(app, TimeoutKeyConfirm(session)); + zeta.expire(); + } + ZetaAutomata::R1 { .. } => { + log!(app, TimeoutK1(session)); + zeta.expire(); + } + 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>, + app: &App, + rng: &Mutex, + session_map: &SessionMap, + s_secret: &App::KeyPair, + kid: NonZeroU32, + n: [u8; AES_GCM_IV_SIZE], + mut k1: Vec, + send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), +) -> Result<(), ReceiveError> { + use FaultType::*; + // -> s + // <- s + // ... + // -> psk, e, es, ss + // <- e, ee, se + if k1.len() != REKEY_SIZE { + return Err(byzantine_fault!(InvalidPacket, true)); + } + if Some(kid) != zeta.key_ref(false).recv.kid { + // Some rekey packet may have arrived extremely delayed. + return Err(byzantine_fault!(UnknownLocalKeyId, false)); + } + let should_rekey_as_bob = match &zeta.beta { + ZetaAutomata::S2 { .. } => true, + ZetaAutomata::R1 { .. } => zeta.was_bob, + _ => false, + }; + if !should_rekey_as_bob { + // Some rekey packet may have arrived extremely delayed. + return Err(byzantine_fault!(OutOfSequence, false)); + } + + 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) { + return Err(byzantine_fault!(FailedAuth, true)); + } + let (_, c) = from_nonce(&n); + if !zeta.update_counter_window(c) { + return Err(byzantine_fault!(ExpiredCounter, true)); + } + k1.truncate(i); + + let result = (|| { + let mut i = 0; + 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()); + // Process message pattern 1 psk0 token. + 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. + 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))?; + // Process message pattern 1 payload. + let j = i + KID_SIZE; + let k = j + AES_GCM_TAG_SIZE; + let tag = k1[j..k].try_into().unwrap(); + 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 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))?; + // Process message pattern 2 se token. + 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); + k2.extend(&new_kid_recv.get().to_be_bytes()); + 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 result = app.save_ratchet_state( + &zeta.s_remote, + &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)); + } + let (kek_send, kek_recv) = noise.get_ask(LABEL_KEX_KEY); + let (nk_send, nk_recv) = noise.split(); + + zeta.key_mut(true).send.kid = Some(kid_send); + zeta.key_mut(true).send.kek = Some(kek_send); + zeta.key_mut(true).send.nk = Some(nk_send); + 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_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; + 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 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)); + Ok(()) + })(); + if matches!(result, Err(ReceiveError::ByzantineFault { .. })) { + zeta.expire(); + } + result +} +/// Corresponds to Transition Algorithm 8 found in Section 4.3. +pub(crate) fn received_k2_trans( + zeta: &mut Zeta, + app: &App, + kid: NonZeroU32, + n: [u8; AES_GCM_IV_SIZE], + mut k2: Vec, + send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), +) -> Result<(), ReceiveError> { + use FaultType::*; + // <- e, ee, se + if k2.len() != REKEY_SIZE { + return Err(byzantine_fault!(InvalidPacket, true)); + } + if Some(kid) != zeta.key_ref(false).recv.kid { + // Some rekey packet may have arrived extremely delayed. + return Err(byzantine_fault!(UnknownLocalKeyId, false)); + } + if !matches!(&zeta.beta, ZetaAutomata::R1 { .. }) { + // Some rekey packet may have arrived extremely delayed. + return Err(byzantine_fault!(OutOfSequence, false)); + } + + 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) { + return Err(byzantine_fault!(FailedAuth, true)); + } + let (_, c) = from_nonce(&n); + if !zeta.update_counter_window(c) { + return Err(byzantine_fault!(ExpiredCounter, true)); + } + k2.truncate(i); + let result = (|| { + if let ZetaAutomata::R1 { noise, e_secret, .. } = &zeta.beta { + let mut noise = noise.clone(); + let mut i = 0; + // 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))?; + // Process message pattern 2 se token. + 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; + let tag = k2[j..k].try_into().unwrap(); + 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 (rk, rf) = noise.get_ask(LABEL_RATCHET_STATE); + 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, + 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)); + } + let (kek_recv, kek_send) = noise.get_ask(LABEL_KEX_KEY); + let (nk_recv, nk_send) = noise.split(); + + zeta.key_mut(true).send.kid = Some(kid_send); + zeta.key_mut(true).send.kek = Some(kek_send); + 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_state1 = new_ratchet_state; + 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.beta = ZetaAutomata::S1; + + let mut c1 = Vec::new(); + let c = zeta.send_counter; + zeta.send_counter += 1; + 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)); + Ok(()) + } else { + unreachable!() + } + })(); + if matches!(result, Err(ReceiveError::ByzantineFault { .. })) { + zeta.expire(); + } + result +} +/// Corresponds to Algorithm 9 found in Section 4.3. +pub(crate) fn send_payload( + zeta: &mut Zeta, + mut payload: Vec, + send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), +) -> Result<(), SendError> { + use SendError::*; + + if matches!(&zeta.beta, ZetaAutomata::Null) { + return Err(SessionExpired); + } + if !matches!( + &zeta.beta, + ZetaAutomata::S1 | ZetaAutomata::S2 | ZetaAutomata::R1 { .. } | ZetaAutomata::R2 { .. } + ) { + 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 { + // 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(()) +} +/// Corresponds to Algorithm 10 found in Section 4.3. +pub(crate) fn received_payload_in_place( + zeta: &mut Zeta, + kid: NonZeroU32, + n: [u8; AES_GCM_IV_SIZE], + payload: &mut Vec, +) -> Result<(), ReceiveError> { + use FaultType::*; + + if payload.len() < AES_GCM_TAG_SIZE { + return Err(byzantine_fault!(FailedAuth, true)); + } + let is_other = if Some(kid) == zeta.key_ref(true).recv.kid { + true + } else if Some(kid) == zeta.key_ref(false).recv.kid { + false + } else { + // A packet would have to be delayed by around an hour for this error to occur, but it can + // occur naturally due to just out-of-order transport. + return Err(byzantine_fault!(OutOfSequence, false)); + }; + + 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 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)); + } + let (_, c) = from_nonce(&n); + if !zeta.update_counter_window(c) { + // This error is marked as not happening naturally, but it could occur if something about + // the transport protocol is duplicating packets. + return Err(byzantine_fault!(ExpiredCounter, true)); + } + payload.truncate(i); + + Ok(()) +} + +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. + pub fn expire(&mut self) { + self.0.lock().unwrap().expire(); + } +} + +impl Drop for Session { + fn drop(&mut self) { + self.expire(); + } +} diff --git a/src/zssp copy.rs b/src/zssp copy.rs new file mode 100644 index 0000000..413c38d --- /dev/null +++ b/src/zssp copy.rs @@ -0,0 +1,2704 @@ +/* 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/ +*/ +// ZSSP: ZeroTier Secure Session Protocol +// FIPS compliant Noise_XK with Jedi powers (Kyber1024) and built-in attack-resistant large payload (fragmentation) support. + +use std::cmp::Reverse; +use std::collections::HashMap; +use std::hash::Hash; +use std::num::{NonZeroU32, NonZeroU64}; +use std::ops::DerefMut; +use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering}; +use std::sync::{Arc, Mutex, MutexGuard, RwLock, Weak}; + +use crate::crypto::aes::{AesDec, AesEnc}; +use crate::crypto::p384::{P384KeyPair, P384PublicKey, P384_ECDH_SHARED_SECRET_SIZE, P384_PUBLIC_KEY_SIZE}; +use crate::crypto::pqc_kyber::KYBER_SECRETKEYBYTES; +use crate::crypto::rand_core::RngCore; +use crate::crypto::sha512::{HmacSha512, HashSha512}; + +use crate::error::{FaultType, OpenError, ReceiveError, SendError}; +use crate::frag_cache::UnassociatedFragCache; +use crate::fragged::{Assembled, Fragged}; +use crate::handshake_cache::UnassociatedHandshakeCache; +use crate::indexed_heap::{BinaryHeapIndex, IndexedBinaryHeap}; +use crate::log_event::LogEvent; +use crate::proto::*; +use crate::symmetric_state::SymmetricState; +use crate::{applicationlayer::*, RatchetState}; + +/// Session context for local application. +/// +/// Each application using ZSSP must create an instance of this to own sessions and +/// 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(pub Arc>); +impl Clone for Context { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} +pub struct ContextInner { + static_keypair: Application::KeyPair, + unassociated_defrag_cache: Mutex>, + unassociated_handshake_states: UnassociatedHandshakeCache, + /// `session_queue -> state_machine_lock -> state -> session_map` + session_queue: Mutex>, Reverse>>, + session_map: RwLock>, bool)>>, + challenge_counter: AtomicU64, + challenge_antireplay_window: [AtomicU64; CHALLENGE_COUNTER_WINDOW_MAX_OOO], + challenge_salt: [u8; CHALLENGE_SALT_SIZE], + rng: Mutex, +} + +/// Result generated by the context packet receive function, with possible payloads. +pub enum ReceiveResult<'b, Application: ApplicationLayer> { + /// 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<'b>), + /// Packet was a part of a handshake, and while it superficially appeared valid the application + /// explicitly rejected it. + /// Relates to callbacks `check_allow_incoming_session`, `hello_requires_recognized_ratchet` + /// and `check_accept_session`. + Rejected, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum SessionEvent<'b> { + /// The received packet was valid, and it contained the necessary keys to fully establish a new + /// session with Alice, the handshake initiator. + /// + /// 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, + /// 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. + /// + /// Alice will receive this return value when the received packet confirms both parties + /// have completed the initial handshake and now have a shared session with each other. + /// If according to the upper protocol, Bob is the first party to send data, it is possible for + /// Alice to start receiving data from Bob before this value is returned. + /// + /// This return value can only occur once per session, only for session objects that were + /// created with `Context::open`. + Established, + /// Bob explicitly refused to establish a session with Alice, and sent us an error code. + /// The application should immediately drop this session as Bob will not allow us to connect. + /// + /// This return value cannot occur after a session is fully established. + Rejected, + /// The received packet was valid and a data payload was decoded and authenticated. + Data(&'b mut [u8]), + /// The received packet was some authentic protocol control packet. No action needs to be taken. + Control, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum IncomingSessionAction { + Allow, + Challenge, + Drop, +} + +/// ZeroTier Secure Session Protocol (ZSSP) Session +/// +/// A FIPS/NIST compliant variant of Noise_XK with hybrid Kyber1024 PQ data forward secrecy. +pub struct Session { + /// An arbitrary application defined object associated with each session. + pub application_data: Application::SessionData, + /// Is true if the local peer acted as Bob, the responder in the initial key exchange. + pub was_bob: bool, + /// The receive context associated with this session, + /// only this context can receive messages from the remote peer. + context: Weak>, + /// Handle into the session queue for changing the update timer. + queue_idx: BinaryHeapIndex, + + remote_static_key: Application::PublicKey, + send_counter: AtomicU64, + /// This bool signals to all threads to stop incrementing the counter and instead error out. + session_has_expired: AtomicBool, + /// The following is a ring buffer of previously seen counter values, where we use the counter's + /// value as the index of the head of the ring buffer. + counter_antireplay_window: [AtomicU64; COUNTER_WINDOW_MAX_OOO], + /// Enforces atomicity of state machine transitions. + /// There is a standard locking sequence, + /// it goes `session_queue -> state_machine_lock -> state -> session_map`. + /// Any lock can be skipped but they must be locked in that order. + state_machine_lock: Mutex<()>, + state: RwLock>, + defrag: [Mutex>; SESSION_MAX_FRAGMENTS_OOO], + header_send_cipher: Application::PrpEnc, + header_receive_cipher: Application::PrpDec, + kex_send_cipher: Mutex>, + kex_receive_cipher: Mutex>, + /// Pre-computed rekeying values. + noise_kk_ss: Secret, + noise_kk_local_init_h: [u8; HASHLEN], + noise_kk_remote_init_h: [u8; HASHLEN], +} +/// `AesGcm` is not threadsafe, but it is threadsafe when inside a `Mutex`. +unsafe impl Send for Session {} +unsafe impl Sync for Session {} + +/// Session state may only be mutated during atomic transitions of the offer state machine. +struct SessionMutableState { + ratchet_states: [RatchetState; 2], + /// For OOO rekeying reliability we allow our version of Noise CipherState to hold the last two + /// session keys, instead of just the most recent one. + cipher_states: [Option>; 2], + /// This is the index of `noise_cipher_state` that contains the most recent key. + /// It will be attached to fragment headers to help with OOO transport. + current_key: usize, + /// This defines the exact state of the offer state machine we are in. + outgoing_offer: OfferStateMachine, +} + +/// These offer enums form a state machine. +/// Documented below are the only legal transitions for this state machine. +/// A session is initialized with an `outgoing_offer` of either NoiseXKPattern1 or Normal. +enum OfferStateMachine { + Normal { + timeout: i64, + }, // -> NoiseKKPattern1, NoiseKKPattern2 + /// This state uses a lot of memory so we put it on the heap. + NoiseXKPattern1or3(Box>), // -> Normal + NoiseKKPattern1 { + next_retry_time: AtomicI64, + timeout: i64, + new_key_id: NonZeroU32, + noise_e_secret: Application::KeyPair, + noise_message: [u8; NoiseKKPattern1or2::SIZE], + noise_ck: SymmetricState, + noise_h_pskep: [u8; HASHLEN], + }, // -> NoiseKKPattern2, KeyConfirm + NoiseKKPattern2 { + next_retry_time: AtomicI64, + timeout: i64, + noise_message: [u8; NoiseKKPattern1or2::SIZE], + kex_send_key: Secret, + }, // -> Normal + KeyConfirm { + next_retry_time: AtomicI64, + timeout: i64, + }, // -> Normal + Null, +} + +pub(crate) struct NoiseXKBobHandshakeState { + /// Can never be Null. + ratchet_state: RatchetState, + remote_key_id: NonZeroU32, + local_key_id: NonZeroU32, + header_receive_key: Secret, + header_send_key: Secret, + noise_h_ee1peekem1pskp: [u8; HASHLEN], + noise_e_secret: Application::KeyPair, + noise_ck_eseeekem1psk: SymmetricState, + noise_k_eseeekem1psk: Secret, + noise_pattern3_defrag: Mutex>, +} + +struct NoiseXKAliceHandshake { + next_retry_time: AtomicI64, + timeout: i64, + /// A secure random number put in the header of Alice's fragments to identify them. + /// If a DDOS attacker could guess this they could block Alice starting the handshake. + local_key_id: NonZeroU32, + alice_identity_blob: Application::LocalIdentityBlob, + offer: NoiseXKAliceHandshakeState, +} + +enum NoiseXKAliceHandshakeState { + NoiseXKPattern1 { + noise_h_ee1p: [u8; HASHLEN], + noise_e_secret: Application::KeyPair, + noise_e1_secret: Secret, + noise_ck_es: SymmetricState, + /// ZSSP assumes an unreliable, out-of-order physical transport environment, so for that + /// reason we have to resend key offers. + noise_message: [u8; NoiseXKPattern1::MAX_SIZE], + noise_message_len: usize, + message_id: u64, + }, + NoiseXKPattern3 { + noise_message: [u8; NoiseXKPattern3::MAX_SIZE], + noise_message_len: usize, + }, +} + +struct SessionKey { + remote_key_id: NonZeroU32, + local_key_id: NonZeroU32, + /// Pool of reusable sending ciphers. + receive_cipher_pool: [Mutex; 8], + /// Pool of reusable receiving ciphers. + send_cipher_pool: [Mutex; 8], + /// Rekey at or after this counter. + rekey_at_counter: u64, + /// Hard error when this counter value is reached or exceeded. + expire_at_counter: u64, +} + +macro_rules! byzantine_fault { + ($name:expr, $is_natural:ident) => { + ReceiveError::ByzantineFault { + file: file!(), + line: line!(), + error: $name, + is_naturally_occurring: $is_natural, + } + }; +} + +impl Context { + /// Create a new session context. + pub fn new(static_keypair: Application::KeyPair, mut rng: Application::Rng) -> Self { + debug_assert!(Application::REKEY_AFTER_TIME_MAX_JITTER_MS > 0, "Invalid protocol constant"); + let mut challenge_salt = [0u8; CHALLENGE_SALT_SIZE]; + rng.fill_bytes(&mut challenge_salt); + Self(Arc::new(ContextInner { + static_keypair, + unassociated_defrag_cache: Mutex::new(UnassociatedFragCache::new()), + unassociated_handshake_states: UnassociatedHandshakeCache::new(), + session_map: RwLock::new(HashMap::new()), + session_queue: Mutex::new(IndexedBinaryHeap::new()), + challenge_counter: AtomicU64::new(INIT_COUNTER), + challenge_antireplay_window: std::array::from_fn(|_| AtomicU64::new(0)), + challenge_salt, + rng: Mutex::new(rng), + })) + } + + /// Perform periodic background service and cleanup tasks. + /// + /// 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 second or two are not + /// a problem. + /// + /// * `send_to` - Function to get a sender and an MTU to send something over an active session + /// * `current_time` - 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 + /// should rekey. + pub fn service bool>( + &self, + app: &Application, + mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, + current_time: i64, + ) -> i64 { + let retry_next = current_time.saturating_add(Application::RETRY_INTERVAL_MS); + let mut next_service_time = 2 * Application::RETRY_INTERVAL_MS; + + let mut session_queue = self.0.session_queue.lock().unwrap(); + // This update system takes heavy advantage of the fact that sessions only need to be updated + // either roughly every second or roughly every hour. That big gap allows for minor optimizations. + // If the gap changes (unlikely) this code may need to be rewritten. + while let Some((session, timer, queue_idx)) = session_queue.peek() { + if timer.0 >= current_time { + next_service_time = next_service_time.min(timer.0 - current_time); + break; + } + let session = match session.upgrade() { + Some(s) => s, + _ => { + session_queue.remove(queue_idx); + continue; + } + }; + let state = session.state.read().unwrap(); + use OfferStateMachine::*; + let next_timer = match &state.outgoing_offer { + Normal { timeout, .. } => { + if *timeout <= current_time { + drop(state); + if let Some((send, _)) = send_to(&session) { + let result = initiate_rekey(&self.0, &session, send, current_time); + if result.is_ok() { + app.event_log(LogEvent::ServiceKKStart(&session), current_time); + } + result.unwrap_or(retry_next) + } else { + retry_next + } + } else { + *timeout + } + } + // If there's an outstanding attempt to open a session, retransmit this + // periodically in case the initial packet doesn't make it. + NoiseXKPattern1or3(handshake_state) => { + if let Some(ts) = process_timer(&handshake_state.next_retry_time, Application::RETRY_INTERVAL_MS, current_time) { + ts + } else { + // We have to eventually time out NoiseXKPattern3 because of unreliable network conditions. + if handshake_state.timeout <= current_time { + drop(state); + let _kex_lock = session.state_machine_lock.lock().unwrap(); + let mut state = session.state.write().unwrap(); + let ratchet_state = state.ratchet_states.clone(); + // Since we dropped the lock we must re-check if we are in the correct state. + if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { + if handshake_state.timeout <= current_time { + app.event_log(LogEvent::ServiceXKTimeout(&session), current_time); + handshake_state.reinitialize( + &session, + &ratchet_state, + &mut self.0.session_map.write().unwrap(), + &mut self.0.rng.lock().unwrap(), + current_time, + ); + } + } + } else if let Some((mut send, mut mtu)) = send_to(&session) { + mtu = mtu.max(MIN_TRANSPORT_MTU); + match &handshake_state.offer { + NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_message, noise_message_len, message_id, .. } => { + app.event_log(LogEvent::ServiceXK1Resend(&session), current_time); + // We are in state NoiseXKPattern1 so resend noise_pattern1. + send_with_fragmentation( + &mut send, + mtu, + &mut noise_message.clone()[..*noise_message_len], + PACKET_TYPE_NOISE_XK_PATTERN_1, + None, + *message_id, + None::<&Application::PrpEnc>, + ); + } + NoiseXKAliceHandshakeState::NoiseXKPattern3 { noise_message, noise_message_len, .. } => { + app.event_log(LogEvent::ServiceXK3Resend(&session), current_time); + send_with_fragmentation( + &mut send, + mtu, + &mut noise_message.clone()[..*noise_message_len], + PACKET_TYPE_NOISE_XK_PATTERN_3, + state.cipher_states[0].as_ref().map(|k| k.remote_key_id), + 0, + Some(&session.header_send_cipher), + ); + } + } + } + retry_next + } + } + NoiseKKPattern1 { next_retry_time, timeout, noise_message, .. } | NoiseKKPattern2 { next_retry_time, timeout, noise_message, .. } => { + if let Some(ts) = process_timer(next_retry_time, Application::RETRY_INTERVAL_MS, current_time) { + ts + } else { + if *timeout <= current_time { + app.event_log(LogEvent::ServiceKKTimeout(&session), current_time); + next_retry_time.store(i64::MAX, Ordering::Relaxed); + drop(state); + session.expire_inner(&self.0, &mut session_queue); + } else { + let packet_type = if let NoiseKKPattern1 { .. } = &state.outgoing_offer { + app.event_log(LogEvent::ServiceKK1Resend(&session), current_time); + PACKET_TYPE_NOISE_KK_PATTERN_1 + } else { + app.event_log(LogEvent::ServiceKK2Resend(&session), current_time); + PACKET_TYPE_NOISE_KK_PATTERN_2 + }; + if let Some((send, _)) = send_to(&session) { + let _ = session.send_control(&state, send, packet_type, noise_message); + } + } + retry_next + } + } + KeyConfirm { next_retry_time, timeout, .. } => { + if let Some(ts) = process_timer(next_retry_time, Application::RETRY_INTERVAL_MS, current_time) { + ts + } else { + if *timeout <= current_time { + app.event_log(LogEvent::ServiceKeyConfirmTimeout(&session), current_time); + next_retry_time.store(i64::MAX, Ordering::Relaxed); + drop(state); + session.expire_inner(&self.0, &mut session_queue); + } else { + app.event_log(LogEvent::ServiceKeyConfirmResend(&session), current_time); + if let Some((send, _)) = send_to(&session) { + let _ = session.send_control(&state, send, PACKET_TYPE_KEY_CONFIRM, &[]); + } + } + retry_next + } + } + Null => retry_next, + }; + session_queue.change_priority(queue_idx, Reverse(next_timer)); + } + drop(session_queue); + + self.0 + .unassociated_defrag_cache + .lock() + .unwrap() + .check_for_expiry(Application::INITIAL_OFFER_TIMEOUT_MS, current_time); + self.0.unassociated_handshake_states.service(current_time); + + next_service_time + } + + /// 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 + /// * `mtu` - MTU for initial packets + /// * `remote_static_key` - Remote side's static public NIST P-384 key + /// * `application_data` - Arbitrary data meaningful to the application to include with session + /// object + /// * `ratchet_state` - The last saved and confirmed ratchet state associated with this remote + /// peer, or None if we do not have one. + /// * `local_identity_blob` - Payload to be sent to Bob that contains the information necessary + /// for the upper protocol to authenticate and approve of Alice's identity. + /// * `current_time` - Current time in milliseconds. Does not have to be monotonic, nor synced + /// with the remote peer. Used to determine when this offer should be resent. + pub fn open( + &self, + app: &Application, + mut send: impl FnMut(&mut [u8]) -> bool, + mut mtu: usize, + remote_static_key: Application::PublicKey, + application_data: Application::SessionData, + local_identity_blob: Application::LocalIdentityBlob, + current_time: i64, + ) -> Result>, OpenError> { + mtu = mtu.max(MIN_TRANSPORT_MTU); + if local_identity_blob.as_ref().len() > MAX_IDENTITY_BLOB_SIZE { + return Err(OpenError::DataTooLarge); + } + let result = app.restore_by_identity(&remote_static_key, &application_data, current_time); + match result { + Ok(ratchet_states) => { + let sha512 = &mut Application::Hash::new(); + + let mut noise_kk_ss = Secret::new(); + if !self.0.static_keypair.agree(&remote_static_key, noise_kk_ss.as_mut()) { + return Err(OpenError::InvalidPublicKey); + } + let noise_kk_local_init_h = mix_hash(sha512, &INITIAL_H_REKEY, self.0.static_keypair.public_key_bytes()); + let noise_kk_local_init_h = mix_hash(sha512, &noise_kk_local_init_h, remote_static_key.as_bytes()); + let noise_kk_remote_init_h = mix_hash(sha512, &INITIAL_H_REKEY, remote_static_key.as_bytes()); + let noise_kk_remote_init_h = mix_hash(sha512, &noise_kk_remote_init_h, self.0.static_keypair.public_key_bytes()); + + let mut session_queue = self.0.session_queue.lock().unwrap(); + let mut session_map = self.0.session_map.write().unwrap(); + let local_key_id = generate_key_id(&session_map, &mut self.0.rng.lock().unwrap()); + // Begin Noise XKhfs+psk2. + let (offer, a2b_header_key, b2a_header_key) = NoiseXKAliceHandshake::::initialize( + local_key_id, + &remote_static_key, + &ratchet_states, + &mut self.0.rng.lock().unwrap(), + )?; + let handshake_state = Box::new(NoiseXKAliceHandshake { + next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), + timeout: current_time.saturating_add(Application::INITIAL_OFFER_TIMEOUT_MS), + local_key_id, + alice_identity_blob: local_identity_blob, + offer, + }); + if let NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_message, noise_message_len, message_id, .. } = &handshake_state.offer { + send_with_fragmentation( + &mut send, + mtu, + &mut noise_message.clone()[..*noise_message_len], + PACKET_TYPE_NOISE_XK_PATTERN_1, + None, + *message_id, + None::<&Application::PrpEnc>, + ); + } + + let queue_idx = session_queue.reserve_index(); + let session = Arc::new(Session { + context: Arc::downgrade(&self.0), + queue_idx, + application_data, + remote_static_key, + send_counter: AtomicU64::new(INIT_COUNTER), + session_has_expired: AtomicBool::new(false), + counter_antireplay_window: std::array::from_fn(|_| AtomicU64::new(0)), + state_machine_lock: Mutex::new(()), + state: RwLock::new(SessionMutableState { + ratchet_states: ratchet_states.clone(), + cipher_states: [None, None], + // Points at 1 until the first key is confirmed. + current_key: 1, + outgoing_offer: OfferStateMachine::NoiseXKPattern1or3(handshake_state), + }), + header_send_cipher: Application::PrpEnc::new(a2b_header_key.as_ref()), + header_receive_cipher: Application::PrpDec::new(b2a_header_key.as_ref()), + kex_receive_cipher: Mutex::new(None), + kex_send_cipher: Mutex::new(None), + noise_kk_ss: noise_kk_ss.clone(), + noise_kk_local_init_h, + noise_kk_remote_init_h, + defrag: std::array::from_fn(|_| Mutex::new(Fragged::new())), + was_bob: false, + }); + session_map.insert(local_key_id, (Arc::downgrade(&session), false)); + session_queue.push_reserved( + queue_idx, + Arc::downgrade(&session), + Reverse(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), + ); + + Ok(session) + } + Err(e) => Err(OpenError::RatchetIoError(e)), + } + } + + /// Receive, authenticate, decrypt, and process a physical wire packet. + /// + /// The check_allow_incoming_session function is called when an initial Noise_XK init message is + /// received. This is before anything is known about the caller. A return value of true proceeds + /// with negotiation. False drops the packet and ignores the inbound attempt. + /// + /// The check_accept_session function is called at the end of negotiation for an incoming + /// session with the caller's static public blob. It must return the P-384 static public key + /// extracted from the supplied blob and application data. A return of Some() accepts the + /// session and will always result in a new session ReceiveResult being returned. + /// + /// * `app` - Interface to application using ZSSP + /// * `check_allow_incoming_session` - Function to call to check whether an unidentified new + /// session should be accepted + /// * `check_accept_session` - Function to accept sessions after final negotiation. + /// The second argument is the identity blob that the remote peer sent us. The application + /// must verify this identity is associated with the remote peer's static key. + /// The third argument is the ratchet chain length, or ratchet count. + /// To prevent desync, if this function returns (Some(_), _), no other open session with the + /// same remote peer must exist. + /// * `send_unassociated_reply` - Function to send reply packets directly when no session exists + /// * `send_unassociated_mtu` - MTU for unassociated replies + /// * `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 + /// * `data_buf` - Buffer to receive decrypted and authenticated object data (an error is + /// returned if too small) + /// * `incoming_physical_packet_buf` - Buffer containing incoming wire packet + /// (receive() takes ownership) + /// * `current_time` - Current time in milliseconds. Does not have to be monotonic, nor synced + /// with the remote peer. Used to check the state of local offers we may currently have or want + /// to put in-flight. + pub fn receive<'a, SendFn: FnMut(&mut [u8]) -> bool>( + &self, + app: &Application, + check_allow_incoming_session: impl FnOnce() -> IncomingSessionAction, + check_accept_session: impl FnOnce(&Application::PublicKey, &[u8], u64) -> (Option<(bool, Application::SessionData)>, bool), + mut send_unassociated_reply: impl FnMut(&mut [u8]) -> bool, + mut send_unassociated_mtu: usize, + mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, + remote_address: &impl Hash, + data_buf: &'a mut [u8], + mut incoming_physical_packet_buf: Application::IncomingPacketBuffer, + current_time: i64, + ) -> Result, ReceiveError> { + send_unassociated_mtu = send_unassociated_mtu.max(MIN_TRANSPORT_MTU); + let incoming_physical_packet: &mut [u8] = incoming_physical_packet_buf.as_mut(); + let incoming_physical_packet_len = incoming_physical_packet.len(); + if incoming_physical_packet_len < MIN_PACKET_SIZE { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + + // The first section parses the header and looks up relevant state information. If it's a DATA + // or NOP packet it gets handled right here, otherwise we pull out a set of variables and + // continue to the logic that handles KEX and session control packets. + + let mut assembled_packet = Assembled::new(); // needs to outlive the block below + let mut incoming = None; + let (session, packet_type, fragments) = { + let local_key_id = incoming_physical_packet[0..SESSION_ID_SIZE].try_into().unwrap(); + // `from_ne_bytes` because this id was generated locally. + if let Some(local_key_id) = NonZeroU32::new(u32::from_ne_bytes(local_key_id)) { + let session_map = self.0.session_map.read().unwrap(); + if let Some((Some(session), key_index)) = session_map.get(&local_key_id).map(|r| (r.0.upgrade(), r.1 as usize)) { + drop(session_map); + session.header_receive_cipher.decrypt_in_place( + (&mut incoming_physical_packet[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]) + .try_into() + .unwrap(), + ); + let (fragment_count, fragment_no, packet_type, incoming_counter, header_nonce) = parse_packet_header(incoming_physical_packet); + // Handle replay protection. + if PACKET_TYPE_RANGE_TRANSPORT.contains(&packet_type) { + // For DOS resistant reply-protection we need to check that the given counter is + // in the window of valid counters immediately. + // But for packets larger than 1 fragment we can't actually record the + // counter as received until we've authenticated the packet. + // So we check the counter window twice, and only update it the second time + // after the packet has been authenticated. + if !session.check_receive_window(incoming_counter) { + // This can occur naturally if packets arrive way out of order, or + // if they are duplicates. + // This can also be naturally triggered if Bob has just successfully + // received the first session key and is reject all of Alice's resends. + // This can also occur if a session was manually expired, but not + // dropped, and the remote party is still sending us data. + return Err(byzantine_fault!(FaultType::ExpiredCounter, true)); + } + if packet_type != PACKET_TYPE_DATA { + // This is a control packet. + if fragment_count != 1 || fragment_no > 0 { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + return receive_control_fragment( + self, + session, + app, + send_to, + packet_type, + incoming_counter, + incoming_physical_packet_buf.as_mut(), + current_time, + ); + } + } else if packet_type == PACKET_TYPE_NOISE_XK_PATTERN_2 { + // We need to reject fragments marked with this type if they are sent out + // of sequence, since an attacker is able to replay them. + match &session.state.read().unwrap().outgoing_offer { + OfferStateMachine::NoiseXKPattern1or3(handshake_state) => match &handshake_state.offer { + NoiseXKAliceHandshakeState::NoiseXKPattern1 { .. } => { + if incoming_counter >= COUNTER_WINDOW_MAX_SKIP_AHEAD { + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } + } + // This error can occur naturally if Bob's initial reply to Alice had a + // resend that was delayed massively and arrived out of order. + _ => return Err(byzantine_fault!(FaultType::OutOfSequence, true)), + }, + _ => return Err(byzantine_fault!(FaultType::OutOfSequence, false)), + }; + } else if packet_type == PACKET_TYPE_NOISE_XK_PATTERN_3 { + // This can be triggered if Bob successfully received a session key and + // needs to reject all of Alice's resends of PACKET_TYPE_NOISE_XK_PATTERN_3. + return Err(byzantine_fault!(FaultType::OutOfSequence, true)); + } else { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + // Handle defragmentation. + let fragments = if fragment_count > 1 { + let idx = incoming_counter as usize % session.defrag.len(); + session.defrag[idx].lock().unwrap().assemble( + header_nonce, + incoming_physical_packet_buf, + fragment_no, + fragment_count, + &mut assembled_packet, + ); + if assembled_packet.is_empty() { + // We have not yet authenticated the sender so we do not report + // receiving a packet from them. + return Ok(ReceiveResult::Unassociated); + } else { + assembled_packet.as_ref() + } + } else { + std::array::from_ref(&incoming_physical_packet_buf) + }; + // Handle DATA in the fastest path when we have a session. + if packet_type == PACKET_TYPE_DATA { + let state = session.state.read().unwrap(); + // The error here can occur because the other party is using a brand new + // session key that we have not received yet. + let key = state.cipher_states[key_index] + .as_ref() + .ok_or(byzantine_fault!(FaultType::OutOfSequence, true))?; + let mut c = key.get_receive_cipher(incoming_counter); + c.set_iv(&create_message_nonce(packet_type, incoming_counter)); + + let mut data_len = 0; + + // Decrypt fragments 0..N-1 where N is the number of fragments. + for f in fragments[..(fragments.len() - 1)].iter() { + let f: &[u8] = f.as_ref(); + debug_assert!(f.len() >= HEADER_SIZE); + let current_frag_data_start = data_len; + data_len += f.len() - HEADER_SIZE; + if data_len > data_buf.len() { + return Err(ReceiveError::DataBufferTooSmall); + } + c.decrypt(&f[HEADER_SIZE..], &mut data_buf[current_frag_data_start..data_len]); + } + + // Decrypt final fragment (or only fragment if not fragmented) + let current_frag_data_start = data_len; + let last_fragment = fragments.last().unwrap().as_ref(); + if last_fragment.len() < (HEADER_SIZE + AES_GCM_TAG_SIZE) { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + data_len += last_fragment.len() - (HEADER_SIZE + AES_GCM_TAG_SIZE); + if data_len > data_buf.len() { + return Err(ReceiveError::DataBufferTooSmall); + } + let payload_end = last_fragment.len() - AES_GCM_TAG_SIZE; + c.decrypt(&last_fragment[HEADER_SIZE..payload_end], &mut data_buf[current_frag_data_start..data_len]); + + let aead_authentication_ok = c.finish_decrypt(&last_fragment[payload_end..].try_into().unwrap()); + drop(c); + drop(state); + + if !aead_authentication_ok { + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } + if !session.update_receive_window(incoming_counter) { + // This can be naturally triggered because Bob has just + // successfully received a session key and needs to reject + // all of Alice's resends. + // This can also occur naturally if some part of the outer + // system is duplicating the packets being sent to us. + // We are safely deduplicating them here. + return Err(byzantine_fault!(FaultType::ExpiredCounter, true)); + } + // Packet fully authenticated + return Ok(ReceiveResult::Session(session, SessionEvent::Data(&mut data_buf[..data_len]))); + } else if packet_type == PACKET_TYPE_NOISE_XK_PATTERN_2 { + (Some(session), packet_type, fragments) + } else { + unreachable!() + } + } else { + drop(session_map); + // Check for and handle PACKET_TYPE_ALICE_NOISE_XK_PATTERN_3 + incoming = self.0.unassociated_handshake_states.get(local_key_id); + if let Some(incoming) = incoming.as_ref() { + Application::PrpDec::new(incoming.header_receive_key.as_ref()).decrypt_in_place( + (&mut incoming_physical_packet[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]) + .try_into() + .unwrap(), + ); + let (fragment_count, fragment_no, packet_type, _, header_nonce) = parse_packet_header(incoming_physical_packet); + app.event_log( + LogEvent::ReceiveUnassociatedFragment(fragment_count, fragment_no, packet_type), + current_time, + ); + if packet_type != PACKET_TYPE_NOISE_XK_PATTERN_3 { + return Err(byzantine_fault!(FaultType::OutOfSequence, false)); + } + let fragments = if fragment_count > 1 { + incoming.noise_pattern3_defrag.lock().unwrap().assemble( + header_nonce, + incoming_physical_packet_buf, + fragment_no, + fragment_count, + &mut assembled_packet, + ); + if !assembled_packet.is_empty() { + assembled_packet.as_ref() + } else { + return Ok(ReceiveResult::Unassociated); + } + } else { + std::array::from_ref(&incoming_physical_packet_buf) + }; + // We must guarantee that this incoming handshake is processed once and only + // once. This prevents catastrophic nonce reuse caused by multithreading. + if self.0.unassociated_handshake_states.remove(local_key_id) { + (None, PACKET_TYPE_NOISE_XK_PATTERN_3, fragments) + } else { + return Ok(ReceiveResult::Unassociated); + } + } else { + // This can occur naturally because either Bob's incoming_sessions cache got + // full so Alice's incoming session was dropped, or the session this packet + // was for was dropped by the application. + return Err(byzantine_fault!(FaultType::UnknownLocalKeyId, true)); + } + } + } else { + let (fragment_count, fragment_no, packet_type, _, header_nonce) = parse_packet_header(incoming_physical_packet); + app.event_log( + LogEvent::ReceiveUnassociatedFragment(fragment_count, fragment_no, packet_type), + current_time, + ); + if packet_type != PACKET_TYPE_NOISE_XK_PATTERN_1 && packet_type != PACKET_TYPE_BOB_DOS_CHALLENGE { + return Err(byzantine_fault!(FaultType::OutOfSequence, false)); + } + let fragments = if fragment_count > 1 { + self.0.unassociated_defrag_cache.lock().unwrap().assemble( + header_nonce, + remote_address, + incoming_physical_packet_len - HEADER_SIZE, + incoming_physical_packet_buf, + fragment_no, + fragment_count, + Application::RETRY_INTERVAL_MS, + current_time, + &mut assembled_packet, + ); + if !assembled_packet.is_empty() { + assembled_packet.as_ref() + } else { + return Ok(ReceiveResult::Unassociated); + } + } else { + std::array::from_ref(&incoming_physical_packet_buf) + }; + (None, packet_type, fragments) + } + }; + + debug_assert!(!fragments.is_empty()); + debug_assert!(incoming.is_none() || session.is_none()); + + let message = &mut [0u8; MAX_NOISE_HANDSHAKE_SIZE]; + let message_size = assemble_fragments_into::(fragments, message)?; + if message_size < MIN_PACKET_SIZE { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + + use OfferStateMachine::*; + match packet_type { + PACKET_TYPE_NOISE_XK_PATTERN_1 => { + // Alice (remote) --> Bob (local) + // -> e, es, e1 + app.event_log(LogEvent::ReceiveUncheckedXK1, current_time); + + if session.is_some() || incoming.is_some() { + return Err(byzantine_fault!(FaultType::OutOfSequence, false)); + } + if !(NoiseXKPattern1::MIN_SIZE..=NoiseXKPattern1::MAX_SIZE).contains(&message_size) { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + // The message id must be the first 8 bytes of the gcm tag. + // This forces the message id to be authenticated along with the entire message. + let p_auth_end = message_size - ChallengeResponse::SIZE; + if message[8..16] != message[p_auth_end - 8..p_auth_end] { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + let p_size = p_auth_end - NoiseXKPattern1::P_ENC_START - AES_GCM_TAG_SIZE; + let total_ratchet_fingerprints = p_size / RATCHET_SIZE; + if p_size % RATCHET_SIZE != 0 || total_ratchet_fingerprints > 2 { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + + let noise_pattern1: &NoiseXKPattern1 = byte_array_as_proto_buffer(message); + if let Some(remote_key_id) = NonZeroU32::new(u32::from_ne_bytes(noise_pattern1.alice_key_id)) { + let sha512 = &mut Application::Hash::new(); + // Let application filter incoming connection attempts by whatever criteria it wants. + // This should ideally prevent ZSSP from wasting time on DDOS attacks. + match check_allow_incoming_session() { + IncomingSessionAction::Allow => {} + IncomingSessionAction::Challenge => { + let response: &ChallengeResponse = byte_array_as_proto_buffer(&message[p_auth_end..message_size]); + let counter = u64::from_be_bytes(response.challenge_counter.try_into().unwrap()); + + sha512.reset(); + let mut hasher = ShaHasher(sha512); + let mut output = [0u8; HASHLEN]; + hasher.0.update(&response.challenge_counter); + remote_address.hash(&mut hasher); + hasher.0.update(&self.0.challenge_salt); + hasher.0.finish(&mut output); + let is_valid = self.check_challenge_window(counter) + && secure_eq(&output[..CHALLENGE_MAC_SIZE], &response.challenge_mac) + && verify_pow::(hasher.0, &message[p_auth_end..message_size]) + && self.update_challenge_window(counter); + app.event_log(LogEvent::ReceiveCheckXK1Challenge(is_valid), current_time); + if !is_valid { + // Alice failed the challenge so issue them a new challenge. + let mut challenge_buffer = [0u8; BobDOSChallenge::SIZE]; + let challenge: &mut BobDOSChallenge = byte_array_as_proto_buffer_mut(&mut challenge_buffer); + challenge.alice_key_id = remote_key_id.get().to_ne_bytes(); + // We attach a monotonically increasing counter value to the challenge + // so it cannot be replayed. + let counter = self.0.challenge_counter.fetch_add(1, Ordering::Relaxed); + challenge.challenge_counter = counter.to_be_bytes(); + + hasher.0.reset(); + hasher.0.update(&counter.to_be_bytes()); + remote_address.hash(&mut hasher); + hasher.0.update(&self.0.challenge_salt); + hasher.0.finish(&mut output); + challenge.challenge_mac.copy_from_slice(&output[..CHALLENGE_MAC_SIZE]); + challenge.prior_challenge_pow = response.challenge_pow; + // We haven't decrypted any of Alice's packet so we don't know the + // header protection cipher. + // For DOS resistance Alice will not accept unencrypted headers directly + // into their session defrag buffer, so we have to send them this reply + // through their incoming sessions cache. + send_with_fragmentation( + &mut send_unassociated_reply, + send_unassociated_mtu, + &mut challenge_buffer, + PACKET_TYPE_BOB_DOS_CHALLENGE, + None, + self.0.rng.lock().unwrap().next_u64(), + None::<&Application::PrpEnc>, + ); + return Ok(ReceiveResult::Unassociated); + } + // Alice succeeded at the challenge so continue to decryption. + } + IncomingSessionAction::Drop => return Ok(ReceiveResult::Rejected), + } + + // Noise process handshake prologue. + let noise_h = mix_hash( + sha512, + &INITIAL_H, + &message[NoiseXKPattern1::PROLOGUE_START..NoiseXKPattern1::PROLOGUE_END], + ); + let noise_h = mix_hash(sha512, &noise_h, self.0.static_keypair.public_key_bytes()); + // Noise process pattern1 e token. + let mut noise_ck = SymmetricState::new(INITIAL_H); + let hmac = &mut Application::HmacHash::new(); + let mut noise_es = Secret::new(); + let noise_e_pattern1 = from_bytes_agreement::(&noise_pattern1.noise_e, &self.0.static_keypair, noise_es.as_mut()) + .ok_or(byzantine_fault!(FaultType::FailedAuthentication, false))?; + let noise_h_e = mix_hash(sha512, &noise_h, &noise_pattern1.noise_e); + noise_ck.mix_key(hmac, &noise_pattern1.noise_e); + // Noise process pattern1 es token. + let noise_k_es = noise_ck.mix_key_initialize_key(hmac, noise_es.as_ref()); + drop(noise_es); + // Noise process pattern1 e1 token. + let (is_auth, noise_h_ee1) = decrypt_and_hash::( + sha512, + &noise_k_es, + &noise_h_e, + packet_type, + 0, + &mut message[NoiseXKPattern1::E1_ENC_START..NoiseXKPattern1::P_ENC_START], + ); + if !is_auth { + // This could occur naturally if Alice's ApplicationLayer is dynamically + // changing their mtu, which in bad network conditions could clobber their + // resent KEX packet. + // Or maybe Alice randomly generated the same temporary id twice in a row. + // Since these situations are super unlikely to occur we still mark this error + // as unnatural. + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } + // Noise process pattern1 payload. + let (is_auth, noise_h_ee1p) = decrypt_and_hash::( + sha512, + &noise_k_es, + &noise_h_ee1, + packet_type, + 1, + &mut message[NoiseXKPattern1::P_ENC_START..p_auth_end], + ); + drop(noise_k_es); + if !is_auth { + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } + let (header_b2a_key, header_a2b_key) = noise_ck.get_ask2(hmac, LABEL_HEADER_KEY, &noise_h_ee1p); + // Get ratchet key. + let noise_pattern1: &NoiseXKPattern1 = byte_array_as_proto_buffer(message); + let mut ratchet_state = RatchetState::Null; + for i in 0..total_ratchet_fingerprints { + match app.restore_by_fingerprint( + (&noise_pattern1.payload[i * RATCHET_SIZE..(i + 1) * RATCHET_SIZE]).try_into().unwrap(), + current_time, + ) { + Ok(RatchetState::Null) | Ok(RatchetState::Empty) => {} + Ok(rs) => { + ratchet_state = rs; + break; + } + Err(e) => return Err(ReceiveError::RatchetIoError(e)), + } + } + if ratchet_state.is_null() { + if app.hello_requires_recognized_ratchet(current_time) { + return Ok(ReceiveResult::Rejected); + } + ratchet_state = RatchetState::Empty; + } + + // Start of Noise XKhfs+psk2 pattern2. + let mut message2 = [0u8; NoiseXKPattern2::SIZE]; + let noise_pattern2: &mut NoiseXKPattern2 = byte_array_as_proto_buffer_mut(&mut message2); + // Noise process pattern2 e token. + let noise_e_pattern2_secret = Application::KeyPair::generate(&mut self.0.rng.lock().unwrap()); + noise_pattern2.noise_e = *noise_e_pattern2_secret.public_key_bytes(); + let noise_h_ee1pe = mix_hash(sha512, &noise_h_ee1p, &noise_pattern2.noise_e); + noise_ck.mix_key(hmac, &noise_pattern2.noise_e); + // Noise process pattern2 ee token. + let mut noise_ee = Secret::new(); + if !noise_e_pattern2_secret.agree(&noise_e_pattern1, noise_ee.as_mut()) { + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } + let noise_k_esee = noise_ck.mix_key_initialize_key(hmac, noise_ee.as_ref()); + drop(noise_ee); + // Noise process pattern2 ekem1 token. + let (noise_ekem1, noise_ekem1_secret) = pqc_kyber::encapsulate(&noise_pattern1.noise_e1, self.0.rng.lock().unwrap().deref_mut()) + .map_err(|_| byzantine_fault!(FaultType::FailedAuthentication, false)) + .map(|(ct, ekem1)| (ct, Secret(ekem1)))?; + // Alice fully authenticated. + noise_pattern2.noise_ekem1 = noise_ekem1; + let noise_h_ee1peekem1 = encrypt_and_hash::( + sha512, + &noise_k_esee, + &noise_h_ee1pe, + PACKET_TYPE_NOISE_XK_PATTERN_2, + 0, + &mut message2[NoiseXKPattern2::EKEM1_ENC_START..NoiseXKPattern2::P_ENC_START], + ); + drop(noise_k_esee); + noise_ck.mix_key(hmac, noise_ekem1_secret.as_ref()); + drop(noise_ekem1_secret); + // Noise process pattern2 psk token. + let ratchet_key = ratchet_state.key().unwrap(); + let (temp_h, noise_k_eseeekem1psk) = noise_ck.mix_key_and_hash_initialize_key(hmac, ratchet_key); + let noise_h_ee1peekem1psk = mix_hash(sha512, &noise_h_ee1peekem1, &temp_h); + // Noise process pattern2 payload. + // We try to prevent the id we generate from colliding with another session but + // because we might have handshakes in flight it's impossible to 100% prevent. + // In those exceedingly rare cases we have to drop Alice's session and start over. + let local_key_id = generate_key_id(&self.0.session_map.read().unwrap(), &mut self.0.rng.lock().unwrap()); + let noise_pattern2: &mut NoiseXKPattern2 = byte_array_as_proto_buffer_mut(&mut message2); + noise_pattern2.bob_key_id = local_key_id.get().to_ne_bytes(); + + let noise_h_ee1peekem1pskp = encrypt_and_hash::( + sha512, + &noise_k_eseeekem1psk, + &noise_h_ee1peekem1psk, + PACKET_TYPE_NOISE_XK_PATTERN_2, + 0, + &mut message2[NoiseXKPattern2::P_ENC_START..NoiseXKPattern2::P_AUTH_END], + ); + + app.event_log(LogEvent::ReceiveValidXK1, current_time); + let handshake = Arc::new(NoiseXKBobHandshakeState { + local_key_id, + remote_key_id, + ratchet_state, + noise_h_ee1peekem1pskp, + noise_ck_eseeekem1psk: noise_ck.clone(), + noise_k_eseeekem1psk: noise_k_eseeekem1psk.clone(), + noise_e_secret: noise_e_pattern2_secret, + header_receive_key: header_a2b_key.clone(), + header_send_key: header_b2a_key.clone(), + noise_pattern3_defrag: Mutex::new(Fragged::new()), + }); + self.0.unassociated_handshake_states.insert(local_key_id, handshake, current_time); + + // We put a copy of the gcm tag in the header so Alice can tell this packet apart + // from any other pattern 1 packet we send, without having to make Bob maintain state. + let mut pattern2_id = 0u64.to_ne_bytes(); + pattern2_id[5] = message2[NoiseXKPattern2::P_AUTH_END - 3]; + pattern2_id[6] = message2[NoiseXKPattern2::P_AUTH_END - 2]; + pattern2_id[7] = message2[NoiseXKPattern2::P_AUTH_END - 1]; + send_with_fragmentation( + &mut send_unassociated_reply, + send_unassociated_mtu, + &mut message2, + PACKET_TYPE_NOISE_XK_PATTERN_2, + Some(remote_key_id), + u64::from_be_bytes(pattern2_id), + Some(&Application::PrpEnc::new(header_b2a_key.first_n::())), + ); + + return Ok(ReceiveResult::Unassociated); + } else { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + } + PACKET_TYPE_BOB_DOS_CHALLENGE => { + let message = &mut message[..message_size]; + app.event_log(LogEvent::ReceiveUncheckedDOSChallenge, current_time); + + // We expect Bob to only send this to us through our unassociated defrag cache. + if incoming.is_some() || session.is_some() { + return Err(byzantine_fault!(FaultType::OutOfSequence, false)); + } + if message.len() != BobDOSChallenge::SIZE { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + let challenge: &BobDOSChallenge = byte_array_as_proto_buffer(message); + + if let Some(local_key_id) = NonZeroU32::new(u32::from_ne_bytes(challenge.alice_key_id)) { + if let Some(session) = self.0.session_map.read().unwrap().get(&local_key_id).and_then(|s| s.0.upgrade()) { + // We don't need to hold the kex lock because we are not transitioning state. + let mut state = session.state.write().unwrap(); + if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { + if let NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_message, noise_message_len, .. } = &mut handshake_state.offer { + let response_raw = &mut noise_message[*noise_message_len - ChallengeResponse::SIZE..]; + + let response: &mut ChallengeResponse = byte_array_as_proto_buffer_mut(response_raw); + // Only people who know what Alice's prior pow was can convince us to + // compute a new pow. + if challenge.prior_challenge_pow != response.challenge_pow { + // This can occur if Bob sends us multiple challenges and they + // arrive OOO. + return Err(byzantine_fault!(FaultType::FailedAuthentication, true)); + } + response.challenge_counter.copy_from_slice(&challenge.challenge_counter); + response.challenge_mac.copy_from_slice(&challenge.challenge_mac); + let mut pow = self.0.rng.lock().unwrap().next_u64(); + let sha512 = &mut Application::Hash::new(); + loop { + let response: &mut ChallengeResponse = byte_array_as_proto_buffer_mut(response_raw); + response.challenge_pow.copy_from_slice(&pow.to_be_bytes()); + if verify_pow::(sha512, response_raw) { + break; + } + pow = pow.wrapping_add(1); + } + + app.event_log(LogEvent::ReceiveValidDOSChallenge(&session), current_time); + return Ok(ReceiveResult::Unassociated); + } else { + // This could happen if Bob challenges Alice, but their challenge packet + // gets massively delayed. + return Err(byzantine_fault!(FaultType::OutOfSequence, true)); + } + } else { + // This could happen if Bob challenges Alice, but their challenge packet + // gets massively delayed. + return Err(byzantine_fault!(FaultType::OutOfSequence, true)); + } + } else { + // This can occur naturally if Alice's session was dropped. + return Err(byzantine_fault!(FaultType::UnknownLocalKeyId, true)); + } + } else { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + } + PACKET_TYPE_NOISE_XK_PATTERN_2 => { + // Bob (remote) --> Alice (local) + // <- e, ee, ekem1, psk + let message = &mut message[..message_size]; + app.event_log(LogEvent::ReceiveUncheckedXK2, current_time); + + if incoming.is_some() { + return Err(byzantine_fault!(FaultType::OutOfSequence, false)); + } + if message.len() != NoiseXKPattern2::SIZE { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + + let session = session.ok_or(byzantine_fault!(FaultType::UnknownLocalKeyId, false))?; + let kex_lock = session.state_machine_lock.lock().unwrap(); + let state = session.state.read().unwrap(); + + if let NoiseXKPattern1or3(handshake_state) = &state.outgoing_offer { + if let NoiseXKAliceHandshakeState::NoiseXKPattern1 { + noise_h_ee1p, noise_e_secret, noise_e1_secret, noise_ck_es, .. + } = &handshake_state.offer + { + let noise_pattern2: &NoiseXKPattern2 = byte_array_as_proto_buffer(message); + // Authenticate header counter. + if noise_pattern2.header[13..16] != noise_pattern2.p_gcm_tag[13..16] { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + + // Noise process pattern2 e token. + let mut noise_ee = Secret::new(); + if let Some(noise_e_pattern2) = + from_bytes_agreement::(&noise_pattern2.noise_e, noise_e_secret, noise_ee.as_mut()) + { + let sha512 = &mut Application::Hash::new(); + let hmac = &mut Application::HmacHash::new(); + let mut noise_ck = noise_ck_es.clone(); + let noise_h_ee1pe = mix_hash(sha512, noise_h_ee1p, noise_e_pattern2.as_bytes()); + noise_ck.mix_key(hmac, noise_e_pattern2.as_bytes()); + // Noise process pattern2 ee token. + let noise_k_esee = noise_ck.mix_key_initialize_key(hmac, noise_ee.as_ref()); + drop(noise_ee); + // Noise process pattern2 ekem1 token. + let (is_auth, noise_h_ee1peekem1) = decrypt_and_hash::( + sha512, + &noise_k_esee, + &noise_h_ee1pe, + packet_type, + 0, + &mut message[NoiseXKPattern2::EKEM1_ENC_START..NoiseXKPattern2::P_ENC_START], + ); + let noise_pattern2: &NoiseXKPattern2 = byte_array_as_proto_buffer(message); + let noise_ekem1_secret = pqc_kyber::decapsulate(&noise_pattern2.noise_ekem1, noise_e1_secret.as_ref()).map(Secret); + if let Some(Ok(noise_ekem1_secret)) = is_auth.then_some(noise_ekem1_secret) { + noise_ck.mix_key(hmac, noise_ekem1_secret.as_ref()); + drop(noise_ekem1_secret); + + // We attempt to decrypt the payload at most three times. First two times with + // the ratchet key Alice remembers, and final time with a ratchet + // key of zero if Alice allows ratchet downgrades. + // The following code is not constant time, meaning we leak to an + // attacker whether or not we downgraded. + // We don't currently consider this sensitive enough information to hide. + let mut test_ratchet_key = |ratchet_key| -> Option<(NonZeroU32, SymmetricState, Secret, [u8; 64])> { + // Check for which ratchet key Bob wants to use. + let mut noise_ck = noise_ck.clone(); + let mut payload = [0u8; NoiseXKPattern2::P_AUTH_END - NoiseXKPattern2::P_ENC_START]; + payload.copy_from_slice(&message[NoiseXKPattern2::P_ENC_START..NoiseXKPattern2::P_AUTH_END]); + // Noise process pattern2 psk token. + let (temp_h, noise_k_eseeekem1psk) = noise_ck.mix_key_and_hash_initialize_key(hmac, ratchet_key); + let noise_h_ee1peekem1psk = mix_hash(sha512, &noise_h_ee1peekem1, &temp_h); + // Noise process pattern2 payload. + let (is_auth, noise_h_ee1peekem1pskp) = decrypt_and_hash::( + sha512, + &noise_k_eseeekem1psk, + &noise_h_ee1peekem1psk, + packet_type, + 0, + &mut payload, + ); + if is_auth { + let key_id = NonZeroU32::new(u32::from_ne_bytes( + (&payload[..NoiseXKPattern2::P_AUTH_START - NoiseXKPattern2::P_ENC_START]) + .try_into() + .unwrap(), + )); + key_id.map(|kid| (kid, noise_ck, noise_k_eseeekem1psk, noise_h_ee1peekem1pskp)) + } else { + None + } + }; + // Check first key. + let mut ratchet_i = 0; + let mut result = None; + let mut chain_len = 0; + if let Some(key) = state.ratchet_states[0].key() { + chain_len = state.ratchet_states[0].chain_len(); + result = test_ratchet_key(key); + } + // Check second key. + if result.is_none() { + ratchet_i = 1; + if let Some(key) = state.ratchet_states[1].key() { + chain_len = state.ratchet_states[1].chain_len(); + result = test_ratchet_key(key); + } + } + // Check zero key. + if result.is_none() && !app.initiator_disallows_downgrade(&session, current_time) { + chain_len = 0; + result = test_ratchet_key(&[0u8; RATCHET_SIZE]); + if result.is_some() { + // TODO: add some kind of warning callback or signal. + } + } + + if let Some((remote_key_id, mut noise_ck, noise_k_eseeekem1psk, noise_h_ee1peekem1pskp)) = result { + // Start of Noise XKhfs+psk2 pattern3. + let mut message3 = [0u8; NoiseXKPattern3::MAX_SIZE]; + // Noise process pattern3 s token. + let mut noise_se = Secret::new(); + if self.0.static_keypair.agree(&noise_e_pattern2, noise_se.as_mut()) { + let payload = handshake_state.alice_identity_blob.as_ref(); + // Packet fully authenticated. + let s_enc_start = HEADER_SIZE; + let s_auth_start = s_enc_start + P384_PUBLIC_KEY_SIZE; + let p_enc_start = s_auth_start + AES_GCM_TAG_SIZE; + let p_auth_start = p_enc_start + payload.len(); + let p_auth_end = p_auth_start + AES_GCM_TAG_SIZE; + let message3_len = p_auth_end; + + message3[s_enc_start..s_auth_start].copy_from_slice(self.0.static_keypair.public_key_bytes()); + let noise_h_ee1peekem1pskps = encrypt_and_hash::( + sha512, + &noise_k_eseeekem1psk, + &noise_h_ee1peekem1pskp, + PACKET_TYPE_NOISE_XK_PATTERN_3, + 1, + &mut message3[s_enc_start..p_enc_start], + ); + drop(noise_k_eseeekem1psk); + // Noise process pattern3 se token. + let noise_k_eseeekem1pskse = noise_ck.mix_key_initialize_key(hmac, noise_se.as_ref()); + drop(noise_se); + // Noise process pattern3 payload token. + message3[p_enc_start..p_auth_start].copy_from_slice(payload); + let noise_h_ee1peekem1pskpsp = encrypt_and_hash::( + sha512, + &noise_k_eseeekem1pskse, + &noise_h_ee1peekem1pskps, + PACKET_TYPE_NOISE_XK_PATTERN_3, + 0, + &mut message3[p_enc_start..p_auth_end], + ); + drop(noise_k_eseeekem1pskse); + // Alice finished Noise XKhfs+psk2 handshake. + // Transition offer state machine to the NoiseXKPattern3 state. + let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_ee1peekem1pskpsp); + let new_ratchet_state = RatchetState::new_nonempty(rk, rf, NonZeroU64::new(chain_len + 1).unwrap()); + + let ratchet_to_preserve = &state.ratchet_states[ratchet_i]; + let result = app.save_ratchet_state( + &session.remote_static_key, + &session.application_data, + [&state.ratchet_states[0], &state.ratchet_states[1]], + [&new_ratchet_state, ratchet_to_preserve], + current_time, + ); + if let Err(e) = result { + return Err(ReceiveError::RatchetIoError(e)); + } + + let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(hmac, LABEL_KEX_KEY, &noise_h_ee1peekem1pskpsp); + + let local_key_id = handshake_state.local_key_id; + drop(state); + let mut state = session.state.write().unwrap(); + session + .kex_send_cipher + .lock() + .unwrap() + .replace(Application::AeadEnc::new(kex_key_a2b.as_ref())); + session + .kex_receive_cipher + .lock() + .unwrap() + .replace(Application::AeadDec::new(kex_key_b2a.as_ref())); + state.ratchet_states[1] = state.ratchet_states[ratchet_i].clone(); + state.ratchet_states[0] = new_ratchet_state; + + state.cipher_states[0].replace(SessionKey::new( + hmac, + noise_ck, + local_key_id, + remote_key_id, + INIT_COUNTER, + false, + )); + debug_assert!(state.cipher_states[1].is_none()); + if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { + handshake_state.next_retry_time = + AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)); + handshake_state.timeout = current_time.saturating_add(Application::INITIAL_OFFER_TIMEOUT_MS); + handshake_state.offer = NoiseXKAliceHandshakeState::NoiseXKPattern3 { + noise_message: message3, + noise_message_len: p_auth_end, + }; + } + drop(state); + drop(kex_lock); + + if let Some((mut send, mut mtu)) = send_to(&session) { + mtu = mtu.max(MIN_TRANSPORT_MTU); + send_with_fragmentation( + &mut send, + mtu, + &mut message3[..message3_len], + PACKET_TYPE_NOISE_XK_PATTERN_3, + Some(remote_key_id), + 0, + Some(&session.header_send_cipher), + ); + } + app.event_log(LogEvent::ReceiveValidXK2(&session), current_time); + return Ok(ReceiveResult::Session(session, SessionEvent::Control)); + } + } + } + } + // Bob failed authentication so we must restart our offer according to Noise. + // We restart the offer instead of dropping the session to defend against DOS. + drop(state); + let mut state = session.state.write().unwrap(); + let ratchet_state = state.ratchet_states.clone(); + if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { + if !handshake_state.reinitialize( + &session, + &ratchet_state, + &mut self.0.session_map.write().unwrap(), + &mut self.0.rng.lock().unwrap(), + current_time, + ) { + session.expire() + } + } + drop(state); + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } else { + return Err(byzantine_fault!(FaultType::OutOfSequence, false)); + } + } else { + return Err(byzantine_fault!(FaultType::OutOfSequence, false)); + } + } + PACKET_TYPE_NOISE_XK_PATTERN_3 => { + // Alice (remote) --> Bob (local) + // -> s, se + let message = &mut message[..message_size]; + app.event_log(LogEvent::ReceiveUncheckedXK3, current_time); + + if session.is_some() { + return Err(byzantine_fault!(FaultType::OutOfSequence, false)); + } + if message.len() < NoiseXKPattern3::MIN_SIZE || message.len() > NoiseXKPattern3::MAX_SIZE { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + // The code above guarantees to us that each `incoming` handshake state that reaches + // this point will be strictly unique, even for the same remote peer. + // This property is strictly necessary to prevent catastrophic nonce reuse due to + // two session being created with the same set of keys. + let handshake_state = incoming.ok_or(byzantine_fault!(FaultType::UnknownLocalKeyId, false))?; + let s_enc_start = HEADER_SIZE; + + let s_auth_start = s_enc_start + P384_PUBLIC_KEY_SIZE; + let p_enc_start = s_auth_start + AES_GCM_TAG_SIZE; + let p_auth_end = message.len(); + let p_auth_start = p_auth_end - AES_GCM_TAG_SIZE; + + if !(p_enc_start <= p_auth_start) { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + // Do not read from the message before this point, otherwise an array out of bounds + // error is possible. + // Noise process pattern3 s token. + let sha512 = &mut Application::Hash::new(); + let hmac = &mut Application::HmacHash::new(); + let (is_auth, noise_h_ee1peekem1pskps) = decrypt_and_hash::( + sha512, + &handshake_state.noise_k_eseeekem1psk, + &handshake_state.noise_h_ee1peekem1pskp, + packet_type, + 1, + &mut message[s_enc_start..p_enc_start], + ); + if !is_auth { + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } + // Noise process pattern3 se token. + let mut noise_se = Secret::new(); + if let Some(remote_s_public_key) = + from_bytes_agreement::(&message[s_enc_start..s_auth_start], &handshake_state.noise_e_secret, noise_se.as_mut()) + { + let mut noise_ck = handshake_state.noise_ck_eseeekem1psk.clone(); + let noise_k_eseeekem1pskse = noise_ck.mix_key_initialize_key(hmac, noise_se.as_ref()); + drop(noise_se); + // Noise process pattern3 payload. + let (is_auth, noise_h_ee1peekem1pskpsp) = decrypt_and_hash::( + sha512, + &noise_k_eseeekem1pskse, + &noise_h_ee1peekem1pskps, + packet_type, + 0, + &mut message[p_enc_start..p_auth_end], + ); + drop(noise_k_eseeekem1pskse); + if !is_auth { + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } + // Bob finished Noise XKhfs+psk2 handshake. + let header_send_cipher = Application::PrpEnc::new(handshake_state.header_send_key.as_ref()); + let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(hmac, LABEL_KEX_KEY, &noise_h_ee1peekem1pskpsp); + let mut send_reject = || { + // 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 + // handshake is being dropped, so nonce reuse can't happen. + let (mut fragment, len) = encrypt_control( + &mut Application::AeadEnc::new(kex_key_b2a.as_ref()), + &header_send_cipher, + PACKET_TYPE_SESSION_REJECTED, + INIT_COUNTER, + handshake_state.remote_key_id.get(), + &[], + ); + send_unassociated_reply(&mut fragment[..len]); + }; + + let (responder_disallows_downgrade, responder_silently_rejects) = check_accept_session( + &remote_s_public_key, + &message[p_enc_start..p_auth_start], + handshake_state.ratchet_state.chain_len(), + ); + if let Some((responder_disallows_downgrade, application_data)) = responder_disallows_downgrade { + let result = app.restore_by_identity(&remote_s_public_key, &application_data, current_time); + match result { + Ok(true_ratchet_states) => { + let mut has_match = false; + for rs in &true_ratchet_states { + if !rs.is_null() { + has_match |= &handshake_state.ratchet_state == rs; + } + } + if !has_match { + if !responder_disallows_downgrade && handshake_state.ratchet_state.is_empty() { + // TODO: add some kind of warning callback or signal. + } else { + if !responder_silently_rejects { + send_reject(); + } + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } + } + + let mut noise_kk_ss = Secret::new(); + if !self.0.static_keypair.agree(&remote_s_public_key, noise_kk_ss.as_mut()) { + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } + let noise_kk_local_init_h = mix_hash(sha512, &INITIAL_H_REKEY, self.0.static_keypair.public_key_bytes()); + let noise_kk_local_init_h = mix_hash(sha512, &noise_kk_local_init_h, remote_s_public_key.as_bytes()); + let noise_kk_remote_init_h = mix_hash(sha512, &INITIAL_H_REKEY, remote_s_public_key.as_bytes()); + let noise_kk_remote_init_h = mix_hash(sha512, &noise_kk_remote_init_h, self.0.static_keypair.public_key_bytes()); + + let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_ee1peekem1pskpsp); + // We must make sure the ratchet key is saved before we transition. + let new_ratchet_state = + RatchetState::new_nonempty(rk, rf, NonZeroU64::new(handshake_state.ratchet_state.chain_len() + 1).unwrap()); + let result = app.save_ratchet_state( + &remote_s_public_key, + &application_data, + [&true_ratchet_states[0], &true_ratchet_states[1]], + [&new_ratchet_state, &RatchetState::Null], + current_time, + ); + if let Err(e) = result { + return Err(ReceiveError::RatchetIoError(e)); + } + + let mut session_queue = self.0.session_queue.lock().unwrap(); + let queue_idx = session_queue.reserve_index(); + let session = Arc::new(Session { + context: Arc::downgrade(&self.0), + queue_idx, + application_data, + remote_static_key: remote_s_public_key, + send_counter: AtomicU64::new(INIT_COUNTER), + session_has_expired: AtomicBool::new(false), + counter_antireplay_window: std::array::from_fn(|_| AtomicU64::new(0)), + state_machine_lock: Mutex::new(()), + state: RwLock::new(SessionMutableState { + ratchet_states: [new_ratchet_state.clone(), RatchetState::Null], + cipher_states: [ + Some(SessionKey::new( + hmac, + noise_ck, + handshake_state.local_key_id, + handshake_state.remote_key_id, + INIT_COUNTER, + true, + )), + None, + ], + current_key: 0, + outgoing_offer: KeyConfirm { + next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), + timeout: current_time.saturating_add(Application::EXPIRATION_TIMEOUT_MS), + }, + }), + header_receive_cipher: Application::PrpDec::new(handshake_state.header_receive_key.as_ref()), + header_send_cipher, + kex_send_cipher: Mutex::new(Some(Application::AeadEnc::new(kex_key_b2a.as_ref()))), + kex_receive_cipher: Mutex::new(Some(Application::AeadDec::new(kex_key_a2b.as_ref()))), + noise_kk_ss, + noise_kk_local_init_h, + noise_kk_remote_init_h, + defrag: std::array::from_fn(|_| Mutex::new(Fragged::new())), + was_bob: true, + }); + let timer = Reverse(current_time.saturating_add(Application::RETRY_INTERVAL_MS)); + session_queue.push_reserved(queue_idx, Arc::downgrade(&session), timer); + drop(session_queue); + // There is the miniscule possibility this key id is already + // in use, in which case we have to drop this session like + // nothing ever happened. + let mut session_map = self.0.session_map.write().unwrap(); + if let std::collections::hash_map::Entry::Vacant(e) = session_map.entry(handshake_state.local_key_id) { + e.insert((Arc::downgrade(&session), false)); + drop(session_map); + let _ = + session.send_control(&session.state.read().unwrap(), send_unassociated_reply, PACKET_TYPE_KEY_CONFIRM, &[]); + + app.event_log(LogEvent::ReceiveValidXK3(&session.application_data), current_time); + return Ok(ReceiveResult::Session(session, SessionEvent::NewSession)); + } else { + // This can occur if we accidentally generate a key id collision. + // There is an extremely short amount of time during which + // another session can steal this session's id, we'll have to + // restart the handshake in this case. + return Err(byzantine_fault!(FaultType::UnknownLocalKeyId, true)); + } + } + Err(e) => { + return Err(ReceiveError::RatchetIoError(e)); + } + } + } else { + if !responder_silently_rejects { + send_reject(); + } + return Ok(ReceiveResult::Rejected); + } + } else { + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } + } + _ => return Err(byzantine_fault!(FaultType::InvalidPacket, false)), + } + } + /// Helper function for sending the empty string over the session. Useful for keep-alives. + /// + /// * `session` - The session to send to + /// * `send` - Function to call to send physical packet(s); the buffer passed to `send` is a + /// slice of `data` + /// * `current_time` - Current time in milliseconds + pub fn send_empty(&self, session: &Arc>, send: impl FnMut(&mut [u8]) -> bool, current_time: i64) -> Result<(), SendError> { + self.send(session, send, &mut [0u8; MIN_TRANSPORT_MTU], &[], current_time) + } + /// Send data over the session. + /// + /// * `session` - The session to send to + /// * `send` - Function to call to send physical packet(s); the buffer passed to `send` is a + /// slice of `data` + /// * `mtu_sized_buffer` - A writable work buffer whose size equals the MTU + /// * `data` - Data to send + /// * `current_time` - Current time in milliseconds + pub fn send( + &self, + session: &Arc>, + mut send: impl FnMut(&mut [u8]) -> bool, + mtu_sized_buffer: &mut [u8], + mut data: &[u8], + current_time: i64, + ) -> Result<(), SendError> { + if mtu_sized_buffer.len() < MIN_TRANSPORT_MTU { + return Err(SendError::InvalidParameter); + } + let state = session.state.read().unwrap(); + let key = state.cipher_states[state.current_key].as_ref().ok_or(SendError::SessionNotEstablished)?; + let counter = session.get_next_outgoing_counter()?; + + let mut c = key.get_send_cipher(counter)?; + c.set_iv(&create_message_nonce(PACKET_TYPE_DATA, counter)); + + let fragment_max_chunk_size = mtu_sized_buffer.len() - HEADER_SIZE; + let fragment_count = (data.len() + AES_GCM_TAG_SIZE + (fragment_max_chunk_size - 1)) / fragment_max_chunk_size; + if fragment_count > MAX_FRAGMENTS { + return Err(SendError::DataTooLarge); + } + let last_fragment_no = fragment_count - 1; + + for fragment_no in 0..fragment_count { + let chunk_size = fragment_max_chunk_size.min(data.len()); + let mut fragment_size = chunk_size + HEADER_SIZE; + + set_packet_header( + mtu_sized_buffer, + fragment_count as u8, + fragment_no as u8, + PACKET_TYPE_DATA, + key.remote_key_id.get(), + counter, + ); + + c.encrypt(&data[..chunk_size], &mut mtu_sized_buffer[HEADER_SIZE..fragment_size]); + data = &data[chunk_size..]; + + if fragment_no == last_fragment_no { + debug_assert!(data.is_empty()); + let tagged_fragment_size = fragment_size + AES_GCM_TAG_SIZE; + c.finish_encrypt((&mut mtu_sized_buffer[fragment_size..tagged_fragment_size]).try_into().unwrap()); + fragment_size = tagged_fragment_size; + } + + session.header_send_cipher.encrypt_in_place( + (&mut mtu_sized_buffer[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]) + .try_into() + .unwrap(), + ); + if !send(&mut mtu_sized_buffer[..fragment_size]) { + break; + } + } + drop(c); + if counter >= key.rekey_at_counter { + if let OfferStateMachine::Normal { .. } = &state.outgoing_offer { + drop(state); + if let Ok(timer) = initiate_rekey(&self.0, session, send, current_time) { + self.0.session_queue.lock().unwrap().change_priority(session.queue_idx, Reverse(timer)); + } + } + } + Ok(()) + } + /// Update the challenge window, returning true if the challenge is still valid. + fn check_challenge_window(&self, counter: u64) -> bool { + let slot = &self.0.challenge_antireplay_window[(counter as usize) % self.0.challenge_antireplay_window.len()]; + let counter = counter.wrapping_add(1); + let prev_counter = slot.load(Ordering::Relaxed); + prev_counter < counter + } + /// Update the challenge window, returning true if the challenge is still valid. + fn update_challenge_window(&self, counter: u64) -> bool { + let slot = &self.0.challenge_antireplay_window[(counter as usize) % self.0.challenge_antireplay_window.len()]; + let counter = counter.wrapping_add(1); + let prev_counter = slot.fetch_max(counter, Ordering::Relaxed); + prev_counter < counter + } +} +/// Initiate the rekeying protocol. This session will now begin attempting to rekey this session +/// with its peer, if it was not already. +fn initiate_rekey( + context: &Arc>, + session: &Arc>, + send: impl FnOnce(&mut [u8]) -> bool, + current_time: i64, +) -> Result { + let mut message = [0u8; NoiseKKPattern1or2::SIZE]; + + let kex_lock = session.state_machine_lock.lock().unwrap(); + let state = session.state.read().unwrap(); + // We may only attempt to rekey if we are not already doing so. + match &state.outgoing_offer { + OfferStateMachine::Normal { .. } => (), + _ => return Err(()), + } + let sha512 = &mut Application::Hash::new(); + let hmac = &mut Application::HmacHash::new(); + // Start of Noise KKpsk0 pattern1. + // Noise process pattern1 psk0 token. + let mut noise_ck = SymmetricState::new(INITIAL_H_REKEY); + let noise_temp_h = noise_ck.mix_key_and_hash(hmac, state.ratchet_states[0].key().unwrap()); + let noise_h_psk = mix_hash(sha512, &session.noise_kk_local_init_h, &noise_temp_h); + // Noise process pattern1 e token. + let noise_e_secret = Application::KeyPair::generate(&mut context.rng.lock().unwrap()); + let noise_h_pske = mix_hash(sha512, &noise_h_psk, noise_e_secret.public_key_bytes()); + noise_ck.mix_key(hmac, noise_e_secret.public_key_bytes()); + + let noise_pattern1: &mut NoiseKKPattern1or2 = byte_array_as_proto_buffer_mut(&mut message); + noise_pattern1.noise_e = *noise_e_secret.public_key_bytes(); + // Noise process pattern1 es token. + let mut noise_es = Secret::new(); + if !noise_e_secret.agree(&session.remote_static_key, noise_es.as_mut()) { + return Err(()); + } + noise_ck.mix_key(hmac, noise_es.as_ref()); + drop(noise_es); + // Noise process pattern1 ss token. + let noise_k_pskesss = noise_ck.mix_key_initialize_key(hmac, session.noise_kk_ss.as_ref()); + // Noise process pattern1 payload token. + let mut session_map = context.session_map.write().unwrap(); + let new_key_id = generate_key_id(&session_map, &mut context.rng.lock().unwrap()); + let next_key_index = state.current_key ^ 1; + session_map.insert(new_key_id, (Arc::downgrade(session), next_key_index > 0)); + drop(session_map); + + let noise_pattern1: &mut NoiseKKPattern1or2 = byte_array_as_proto_buffer_mut(&mut message); + noise_pattern1.key_id = new_key_id.get().to_ne_bytes(); + let noise_h_pskep = encrypt_and_hash::( + sha512, + &noise_k_pskesss, + &noise_h_pske, + PACKET_TYPE_NOISE_KK_PATTERN_1, + 0, + &mut message[NoiseKKPattern1or2::ENC_START..NoiseKKPattern1or2::AUTH_END], + ); + drop(noise_k_pskesss); + + drop(state); + let mut state = session.state.write().unwrap(); + state.outgoing_offer = OfferStateMachine::NoiseKKPattern1 { + next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), + timeout: current_time.saturating_add(Application::EXPIRATION_TIMEOUT_MS), + new_key_id, + noise_e_secret, + noise_message: message.clone(), + noise_h_pskep, + noise_ck: noise_ck.clone(), + }; + drop(state); + drop(kex_lock); + let _ = session.send_control(&session.state.read().unwrap(), send, PACKET_TYPE_NOISE_KK_PATTERN_1, &message); + Ok(current_time.saturating_add(Application::RETRY_INTERVAL_MS)) +} +fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mut [u8]) -> bool>( + context: &Context, + session: Arc>, + app: &Application, + mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, + packet_type: u8, + counter: u64, + fragment: &mut [u8], + current_time: i64, +) -> Result, ReceiveError> { + let kex_lock = session.state_machine_lock.lock().unwrap(); + let state = session.state.read().unwrap(); + let mut c = session.kex_receive_cipher.lock().unwrap(); + let message = decrypt_control( + c.as_mut().ok_or(byzantine_fault!(FaultType::OutOfSequence, false))?, + packet_type, + counter, + fragment, + )?; + drop(c); + session.update_receive_window(counter); + use OfferStateMachine::*; + return match packet_type { + PACKET_TYPE_SESSION_REJECTED => { + if let NoiseXKPattern1or3(_) = &state.outgoing_offer { + drop(state); + let mut state = session.state.write().unwrap(); + state.outgoing_offer = OfferStateMachine::Null; + drop(state); + drop(kex_lock); + Ok(ReceiveResult::Session(session, SessionEvent::Rejected)) + } else { + // This can occur naturally because of control packet resends. + Err(byzantine_fault!(FaultType::OutOfSequence, true)) + } + } + PACKET_TYPE_KEY_CONFIRM => { + drop(state); + app.event_log(LogEvent::ReceiveValidKeyConfirm(&session), current_time); + let mut state = session.state.write().unwrap(); + // We only want to stop sending NoiseKKPattern2 offers when the latest derived + // key is confirmed. And we only want to do that once. + let (used_latest_key, try_delete, ret) = match &state.outgoing_offer { + NoiseKKPattern2 { .. } => (true, true, SessionEvent::Control), + NoiseXKPattern1or3(handshake_state) => { + if let NoiseXKAliceHandshakeState::NoiseXKPattern3 { .. } = &handshake_state.offer { + (true, true, SessionEvent::Established) + } else { + (false, false, SessionEvent::Control) + } + } + Null => (false, false, SessionEvent::Control), + _ => (true, false, SessionEvent::Control), + }; + if try_delete { + let result = if !state.ratchet_states[1].is_null() { + app.save_ratchet_state( + &session.remote_static_key, + &session.application_data, + [&state.ratchet_states[0], &state.ratchet_states[1]], + [&state.ratchet_states[0], &RatchetState::Null], + current_time, + ) + } else { + Ok(()) + }; + if let Err(e) = result { + return Err(ReceiveError::RatchetIoError(e)); + } + if let NoiseKKPattern2 { kex_send_key, .. } = &state.outgoing_offer { + session + .kex_send_cipher + .lock() + .unwrap() + .replace(Application::AeadEnc::new(kex_send_key.as_ref())); + } + state.ratchet_states[1] = RatchetState::Null; + state.current_key ^= 1; + state.outgoing_offer = new_normal_state(context.0.rng.lock().unwrap().next_u64(), current_time); + } + drop(state); + drop(kex_lock); + if used_latest_key { + if let Some((send, _)) = send_to(&session) { + let _ = session.send_control(&session.state.read().unwrap(), send, PACKET_TYPE_ACK, &[]); + } + } + Ok(ReceiveResult::Session(session, ret)) + } + PACKET_TYPE_ACK => { + if let KeyConfirm { .. } = &state.outgoing_offer { + drop(state); + app.event_log(LogEvent::ReceiveValidAck(&session), current_time); + let mut state = session.state.write().unwrap(); + // Check if we should end any current offers and transition back to Normal state + state.outgoing_offer = new_normal_state(context.0.rng.lock().unwrap().next_u64(), current_time); + drop(kex_lock); + drop(state); + Ok(ReceiveResult::Session(session, SessionEvent::Control)) + } else { + // This can occur naturally because of control packet resends. + Err(byzantine_fault!(FaultType::OutOfSequence, true)) + } + } + PACKET_TYPE_NOISE_KK_PATTERN_1 => { + app.event_log(LogEvent::ReceiveUncheckedKK1, current_time); + let message = &mut message[..NoiseKKPattern1or2::SIZE]; + let noise_pattern1: &NoiseKKPattern1or2 = byte_array_as_proto_buffer(message); + // We need the following operation to be atomic with the change of offer type + let (should_rekey_as_bob, chosen_id) = match &state.outgoing_offer { + // Check rekey rate limits. + Normal { .. } => (true, None), + // In the following situation, both parties are in state NoiseKKPattern1, + // we need to deterministically allow only one of them to transition to + // NoiseKKPattern2. + NoiseKKPattern1 { new_key_id, .. } => (session.was_bob, Some(*new_key_id)), + _ => (false, None), + }; + if !should_rekey_as_bob { + // This can be triggered if both parties attempt rekeying simultaneously, or if the + // remote party sent us a duplicate rekey request. + // The code above handles this case and only lets one party through to rekeying. + drop(state); + drop(kex_lock); + return Ok(ReceiveResult::Session(session, SessionEvent::Control)); + } + // Noise process pattern1 psk0 token. + let sha512 = &mut Application::Hash::new(); + let hmac = &mut Application::HmacHash::new(); + let mut noise_ck = SymmetricState::new(INITIAL_H_REKEY); + let noise_temp_h = noise_ck.mix_key_and_hash(hmac, state.ratchet_states[0].key().unwrap()); + let noise_h_psk = mix_hash(sha512, &session.noise_kk_remote_init_h, &noise_temp_h); + // Noise process pattern1 e token. + // Get public key validation out of the way early + let mut noise_es = Secret::new(); + let mut noise_ee = Secret::new(); + let mut noise_se = Secret::new(); + if let Some(alice_e) = from_bytes_agreement::(&noise_pattern1.noise_e, &context.0.static_keypair, noise_es.as_mut()) { + let bob_e_secret = Application::KeyPair::generate(&mut context.0.rng.lock().unwrap()); + if bob_e_secret.agree(&alice_e, noise_ee.as_mut()) && bob_e_secret.agree(&session.remote_static_key, noise_se.as_mut()) { + let noise_h_pske = mix_hash(sha512, &noise_h_psk, alice_e.as_bytes()); + noise_ck.mix_key(hmac, alice_e.as_bytes()); + // Noise process pattern1 es token. + noise_ck.mix_key(hmac, noise_es.as_ref()); + drop(noise_es); + // Noise process pattern1 ss token. + let noise_k_pskesss = noise_ck.mix_key_initialize_key(hmac, session.noise_kk_ss.as_ref()); + + // Noise process pattern1 payload. + let (is_auth, noise_h_pskep) = decrypt_and_hash::( + sha512, + &noise_k_pskesss, + &noise_h_pske, + packet_type, + 0, + &mut message[NoiseKKPattern1or2::ENC_START..NoiseKKPattern1or2::AUTH_END], + ); + let noise_pattern1: &NoiseKKPattern1or2 = byte_array_as_proto_buffer(message); + if let (true, Some(remote_key_id)) = (is_auth, NonZeroU32::new(u32::from_ne_bytes(noise_pattern1.key_id))) { + // Alice fully authenticated. + // Start of Noise KKpsk0 pattern2. + // Noise process pattern2 e token. + let noise_h_pskepe = mix_hash(sha512, &noise_h_pskep, bob_e_secret.public_key_bytes()); + noise_ck.mix_key(hmac, bob_e_secret.public_key_bytes()); + // Noise process pattern2 ee token. + noise_ck.mix_key(hmac, noise_ee.as_ref()); + drop(noise_ee); + // Noise process pattern2 se token. + let noise_k_pskessseese = noise_ck.mix_key_initialize_key(hmac, noise_se.as_ref()); + drop(noise_se); + // Noise process pattern2 payload. + let mut message2 = [0u8; NoiseKKPattern1or2::SIZE]; + let noise_pattern2: &mut NoiseKKPattern1or2 = byte_array_as_proto_buffer_mut(&mut message2); + noise_pattern2.noise_e = *bob_e_secret.public_key_bytes(); + let mut session_map = context.0.session_map.write().unwrap(); + // If we already generated a new key id mapping reuse it. + let new_key_id = chosen_id.unwrap_or_else(|| generate_key_id(&session_map, &mut context.0.rng.lock().unwrap())); + noise_pattern2.key_id = new_key_id.get().to_ne_bytes(); + + let noise_h_pskepep = encrypt_and_hash::( + sha512, + &noise_k_pskessseese, + &noise_h_pskepe, + PACKET_TYPE_NOISE_KK_PATTERN_2, + 0, + &mut message2[NoiseKKPattern1or2::ENC_START..NoiseKKPattern1or2::AUTH_END], + ); + drop(noise_k_pskessseese); + // Bob finished Noise KKpsk0 handshake. + let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_pskepep); + let new_ratchet_state = RatchetState::new_nonempty(rk, rf, NonZeroU64::new(state.ratchet_states[0].chain_len() + 1).unwrap()); + let result = app.save_ratchet_state( + &session.remote_static_key, + &session.application_data, + [&state.ratchet_states[0], &state.ratchet_states[1]], + [&new_ratchet_state, &state.ratchet_states[0]], + current_time, + ); + if let Err(e) = result { + drop(state); + drop(kex_lock); + return Err(ReceiveError::RatchetIoError(e)); + } + let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(hmac, LABEL_KEX_KEY, &noise_h_pskepep); + // The new "Bob" doesn't know yet if Alice has received the new key, so the + // new key is recorded as the "alt" (key_index ^ 1) but the current key is + // not advanced yet. + let next_key_index = state.current_key ^ 1; + session_map.insert(new_key_id, (Arc::downgrade(&session), next_key_index > 0)); + if let Some(pre_id) = state.cipher_states[next_key_index].as_ref().map(|k| k.local_key_id) { + session_map.remove(&pre_id); + } + drop(session_map); + drop(state); + let mut state = session.state.write().unwrap(); + let current_counter = session.send_counter.load(Ordering::Relaxed); + session + .kex_receive_cipher + .lock() + .unwrap() + .replace(Application::AeadDec::new(kex_key_a2b.as_ref())); + state.ratchet_states[1] = state.ratchet_states[0].clone(); + state.ratchet_states[0] = new_ratchet_state.clone(); + + state.cipher_states[next_key_index].replace(SessionKey::new( + hmac, + noise_ck, + new_key_id, + remote_key_id, + current_counter, + true, + )); + let timer = current_time.saturating_add(Application::RETRY_INTERVAL_MS); + state.outgoing_offer = NoiseKKPattern2 { + next_retry_time: AtomicI64::new(timer), + timeout: current_time.saturating_add(Application::EXPIRATION_TIMEOUT_MS), + noise_message: message2, + kex_send_key: kex_key_b2a.clone(), + }; + drop(state); + drop(kex_lock); + context.0.session_queue.lock().unwrap().change_priority(session.queue_idx, Reverse(timer)); + + if let Some((send, _)) = send_to(&session) { + let _ = session.send_control(&session.state.read().unwrap(), send, PACKET_TYPE_NOISE_KK_PATTERN_2, &message2); + } + app.event_log(LogEvent::ReceiveValidKK1(&session), current_time); + return Ok(ReceiveResult::Session(session, SessionEvent::Control)); + } + } + } + Err(byzantine_fault!(FaultType::FailedAuthentication, false)) + } + PACKET_TYPE_NOISE_KK_PATTERN_2 => { + app.event_log(LogEvent::ReceiveUncheckedKK2, current_time); + let message = &mut message[..NoiseKKPattern1or2::SIZE]; + let noise_pattern2: &NoiseKKPattern1or2 = byte_array_as_proto_buffer(message); + + if let NoiseKKPattern1 { new_key_id, noise_e_secret, noise_ck, noise_h_pskep, .. } = &state.outgoing_offer { + // Noise process pattern2 e token. + let mut noise_ee = Secret::new(); + let mut noise_se = Secret::new(); + if let Some(bob_e) = from_bytes_agreement::(&noise_pattern2.noise_e, noise_e_secret, noise_ee.as_mut()) { + if context.0.static_keypair.agree(&bob_e, noise_se.as_mut()) { + let sha512 = &mut Application::Hash::new(); + let hmac = &mut Application::HmacHash::new(); + let mut noise_ck = noise_ck.clone(); + let noise_h_pskepe = mix_hash(sha512, noise_h_pskep, bob_e.as_bytes()); + noise_ck.mix_key(hmac, bob_e.as_bytes()); + // Noise process pattern2 ee token. + noise_ck.mix_key(hmac, noise_ee.as_ref()); + drop(noise_ee); + // Noise process pattern2 se token. + let noise_k_pskessseese = noise_ck.mix_key_initialize_key(hmac, noise_se.as_ref()); + drop(noise_se); + // Noise process pattern2 payload. + let (is_auth, noise_h_pskepep) = decrypt_and_hash::( + sha512, + &noise_k_pskessseese, + &noise_h_pskepe, + packet_type, + 0, + &mut message[NoiseKKPattern1or2::ENC_START..NoiseKKPattern1or2::AUTH_END], + ); + let noise_pattern2: &NoiseKKPattern1or2 = byte_array_as_proto_buffer(message); + if let (true, Some(remote_key_id)) = (is_auth, NonZeroU32::new(u32::from_ne_bytes(noise_pattern2.key_id))) { + // Bob fully authenticated. + // Alice finished Noise KKpsk0 handshake. + let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_pskepep); + let new_ratchet_state = + RatchetState::new_nonempty(rk, rf, NonZeroU64::new(state.ratchet_states[0].chain_len() + 1).unwrap()); + let result = app.save_ratchet_state( + &session.remote_static_key, + &session.application_data, + [&state.ratchet_states[0], &state.ratchet_states[1]], + [&new_ratchet_state, &RatchetState::Null], + current_time, + ); + if let Err(e) = result { + drop(state); + drop(kex_lock); + return Err(ReceiveError::RatchetIoError(e)); + } + let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(hmac, LABEL_KEX_KEY, &noise_h_pskepep); + + let new_key_id = *new_key_id; + drop(state); + let mut state = session.state.write().unwrap(); + let next_key_index = state.current_key ^ 1; + state.current_key = next_key_index; + if let Some(key) = state.cipher_states[next_key_index].as_ref() { + context.0.session_map.write().unwrap().remove(&key.local_key_id); + } + session + .kex_receive_cipher + .lock() + .unwrap() + .replace(Application::AeadDec::new(kex_key_b2a.as_ref())); + session + .kex_send_cipher + .lock() + .unwrap() + .replace(Application::AeadEnc::new(kex_key_a2b.as_ref())); + state.ratchet_states[1] = RatchetState::Null; + state.ratchet_states[0] = new_ratchet_state.clone(); + + state.cipher_states[next_key_index].replace(SessionKey::new( + hmac, + noise_ck, + new_key_id, + remote_key_id, + session.send_counter.load(Ordering::Relaxed), + false, + )); + state.outgoing_offer = KeyConfirm { + next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), + timeout: current_time.saturating_add(Application::EXPIRATION_TIMEOUT_MS), + }; + drop(state); + drop(kex_lock); + // Let Bob know we got the key. + if let Some((send, _)) = send_to(&session) { + let _ = session.send_control(&session.state.read().unwrap(), send, PACKET_TYPE_KEY_CONFIRM, &[]); + } + app.event_log(LogEvent::ReceiveValidKK2(&session), current_time); + return Ok(ReceiveResult::Session(session, SessionEvent::Control)); + } + } + } + // Bob failed authentication so according to Noise we must terminate this + // handshake. + // This should not happen in practice since this packet will have already passed + // authentication under the current key. + session.expire(); + Err(byzantine_fault!(FaultType::FailedAuthentication, false)) + } else { + drop(state); + drop(kex_lock); + Ok(ReceiveResult::Session(session, SessionEvent::Control)) + } + } + _ => Err(byzantine_fault!(FaultType::InvalidPacket, false)), + }; +} + +impl Session { + /// This can only fail with `MaxKeyLifetimeExceeded` or `SessionNotEstablished`. + fn send_control( + &self, + state: &SessionMutableState, + send: impl FnOnce(&mut [u8]) -> bool, + packet_type: u8, + packet: &[u8], + ) -> Result<(), SendError> { + let key = state.cipher_states[state.current_key].as_ref().ok_or(SendError::SessionNotEstablished)?; + let counter = self.get_next_outgoing_counter()?; + let mut c = self.kex_send_cipher.lock().unwrap(); + let (mut fragment, len) = encrypt_control( + c.as_mut().ok_or(SendError::SessionNotEstablished)?, + &self.header_send_cipher, + packet_type, + counter, + key.remote_key_id.get(), + packet, + ); + send(&mut fragment[..len]); + Ok(()) + } + /// Check whether this session is established. + pub fn established(&self) -> bool { + let state = self.state.read().unwrap(); + !matches!(&state.outgoing_offer, OfferStateMachine::NoiseXKPattern1or3(_) | OfferStateMachine::Null) + } + /// The static public key of the remote peer. + pub fn remote_s_public_key(&self) -> &Application::PublicKey { + &self.remote_static_key + } + /// The current ratchet state of this session. + /// The returned values are sensitive and should be securely erased before being dropped. + pub fn ratchet_states(&self) -> [RatchetState; 2] { + let state = self.state.read().unwrap(); + state.ratchet_states.clone() + } + /// The current ratchet count of this session. + pub fn ratchet_count(&self) -> u64 { + self.state.read().unwrap().ratchet_states[0].chain_len() + } + /// 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. + pub fn expire(&self) { + if let Some(context) = self.context.upgrade() { + self.expire_inner(&context, &mut context.session_queue.lock().unwrap()); + } + } + fn expire_inner( + &self, + context: &Arc>, + session_queue: &mut IndexedBinaryHeap>, Reverse>, + ) { + // Prevent this session from being updated. + session_queue.remove(self.queue_idx); + self.session_has_expired.store(true, Ordering::Relaxed); + let _kex_lock = self.state_machine_lock.lock().unwrap(); + let mut state = self.state.write().unwrap(); + let mut session_map = context.session_map.write().unwrap(); + for key in &state.cipher_states { + if let Some(pre_id) = key.as_ref().map(|k| k.local_key_id) { + session_map.remove(&pre_id); + } + } + use OfferStateMachine::*; + match &state.outgoing_offer { + NoiseXKPattern1or3(handshake_state) => session_map.remove(&handshake_state.local_key_id), + NoiseKKPattern1 { new_key_id, .. } => session_map.remove(new_key_id), + _ => None, + }; + state.outgoing_offer = OfferStateMachine::Null; + } + + /// Get the next outgoing counter value. + fn get_next_outgoing_counter(&self) -> Result { + if self.session_has_expired.load(Ordering::Relaxed) { + Err(SendError::SessionExpired) + } else { + let counter = self.send_counter.fetch_add(1, Ordering::Relaxed); + if counter > THREAD_SAFE_COUNTER_HARD_EXPIRE { + // Because this thread sets the flag itself it will never be able to increment the + // counter again. + // For that reason the other atomic orderings can be `Relaxed`. + self.session_has_expired.store(true, Ordering::SeqCst) + } + Ok(counter) + } + } + /// Check the receive window without mutating state. + fn check_receive_window(&self, counter: u64) -> bool { + let slot = &self.counter_antireplay_window[(counter as usize) % self.counter_antireplay_window.len()]; + let counter = counter.wrapping_add(1); + let prev_counter = slot.load(Ordering::Relaxed); + prev_counter < counter && counter.wrapping_sub(prev_counter) <= COUNTER_WINDOW_MAX_SKIP_AHEAD + } + /// Update the receive window, returning true if the packet is still valid. + /// This should only be called after the packet is authenticated. + fn update_receive_window(&self, counter: u64) -> bool { + let slot = &self.counter_antireplay_window[(counter as usize) % self.counter_antireplay_window.len()]; + let counter = counter.wrapping_add(1); + let prev_counter = slot.fetch_max(counter, Ordering::Relaxed); + prev_counter < counter && counter.wrapping_sub(prev_counter) <= COUNTER_WINDOW_MAX_SKIP_AHEAD + } +} +impl Drop for Session { + fn drop(&mut self) { + if let Some(context) = self.context.upgrade() { + self.expire_inner(&context, &mut context.session_queue.lock().unwrap()); + } + } +} + +impl NoiseXKAliceHandshake { + /// Can only fail with `OpenError::InvalidPublicKey` because of remote_s_public_key. + /// Corresponds to Noise `Initialize`. + fn initialize( + local_key_id: NonZeroU32, + remote_s_public_key: &Application::PublicKey, + ratchet_state: &[RatchetState; 2], + rng: &mut Application::Rng, + ) -> Result< + ( + NoiseXKAliceHandshakeState, + Secret, + Secret, + ), + OpenError, + > { + let mut message = [0u8; NoiseXKPattern1::MAX_SIZE]; + let sha512 = &mut Application::Hash::new(); + let hmac = &mut Application::HmacHash::new(); + // Start of Noise XKhfs+psk2 pattern1. + let noise_pattern1: &mut NoiseXKPattern1 = byte_array_as_proto_buffer_mut(&mut message); + let noise_e_secret = Application::KeyPair::generate(rng); + let noise_e1_secret = pqc_kyber::keypair(rng); + noise_pattern1.alice_key_id = local_key_id.get().to_ne_bytes(); + noise_pattern1.noise_e = *noise_e_secret.public_key_bytes(); + noise_pattern1.noise_e1 = noise_e1_secret.public; + // Noise process prologue. + let noise_h = mix_hash( + sha512, + &INITIAL_H, + &message[NoiseXKPattern1::PROLOGUE_START..NoiseXKPattern1::PROLOGUE_END], + ); + let noise_h = mix_hash(sha512, &noise_h, remote_s_public_key.as_bytes()); + // Noise process pattern1 e token. + let mut noise_ck = SymmetricState::new(INITIAL_H); + let noise_h_e = mix_hash(sha512, &noise_h, noise_e_secret.public_key_bytes()); + noise_ck.mix_key(hmac, noise_e_secret.public_key_bytes()); + // Noise process pattern1 es token. + let mut noise_es = Secret::new(); + if !noise_e_secret.agree(remote_s_public_key, noise_es.as_mut()) { + return Err(OpenError::InvalidPublicKey); + } + let noise_k_es = noise_ck.mix_key_initialize_key(hmac, noise_es.as_ref()); + drop(noise_es); + // Noise process pattern1 e1 token. + let noise_h_ee1 = encrypt_and_hash::( + sha512, + &noise_k_es, + &noise_h_e, + PACKET_TYPE_NOISE_XK_PATTERN_1, + 0, + &mut message[NoiseXKPattern1::E1_ENC_START..NoiseXKPattern1::P_ENC_START], + ); + // Noise process pattern1 payload. + let noise_pattern1: &mut NoiseXKPattern1 = byte_array_as_proto_buffer_mut(&mut message); + let mut idx = 0; + for rs in ratchet_state { + if let Some(rf) = rs.fingerprint() { + let next_idx = idx + RATCHET_SIZE; + noise_pattern1.payload[idx..next_idx].copy_from_slice(rf); + idx = next_idx; + } + } + let p_auth_end = NoiseXKPattern1::P_ENC_START + idx + AES_GCM_TAG_SIZE; + let noise_message_len = p_auth_end + ChallengeResponse::SIZE; + + let noise_h_ee1p = encrypt_and_hash::( + sha512, + &noise_k_es, + &noise_h_ee1, + PACKET_TYPE_NOISE_XK_PATTERN_1, + 1, + &mut message[NoiseXKPattern1::P_ENC_START..p_auth_end], + ); + drop(noise_k_es); + let (header_b2a_key, header_a2b_key) = noise_ck.get_ask2(hmac, LABEL_HEADER_KEY, &noise_h_ee1p); + let message_id = u64::from_be_bytes(message[p_auth_end - 8..p_auth_end].try_into().unwrap()); + + message[noise_message_len - CHALLENGE_POW_SIZE..noise_message_len].copy_from_slice(&rng.next_u64().to_ne_bytes()); + Ok(( + NoiseXKAliceHandshakeState::NoiseXKPattern1 { + noise_h_ee1p, + noise_e_secret, + noise_e1_secret: Secret(noise_e1_secret.secret), + noise_ck_es: noise_ck, + noise_message_len, + noise_message: message, + message_id, + }, + header_a2b_key, + header_b2a_key, + )) + } + /// Should not fail unless Bob's public key is adversarial. + fn reinitialize( + &mut self, + session: &Arc>, + ratchet_state: &[RatchetState; 2], + session_map: &mut HashMap>, bool)>, + rng: &mut Application::Rng, + current_time: i64, + ) -> bool { + let local_key_id = generate_key_id(session_map, rng); + if let Ok((offer, a2b_header_key, b2a_header_key)) = Self::initialize(local_key_id, &session.remote_static_key, ratchet_state, rng) { + self.timeout = current_time.saturating_add(Application::INITIAL_OFFER_TIMEOUT_MS); + session_map.remove(&self.local_key_id); + session_map.insert(local_key_id, (Arc::downgrade(session), false)); + self.local_key_id = local_key_id; + self.offer = offer; + session.header_send_cipher.reset(a2b_header_key.as_ref()); + session.header_receive_cipher.reset(b2a_header_key.as_ref()); + true + } else { + false + } + } +} + +/// Create the normal state of the offer state machine, with the correct timestamps. +fn new_normal_state(rand: u64, current_time: i64) -> OfferStateMachine { + OfferStateMachine::Normal { + timeout: current_time + .saturating_add(Application::REKEY_AFTER_TIME_MS) + .saturating_sub(rand as i64 % Application::REKEY_AFTER_TIME_MAX_JITTER_MS), + } +} +/// Get a timestamp of when this timer should trigger next, or None if it should trigger now. +fn process_timer(timer: &AtomicI64, wait_time: i64, current_time: i64) -> Option { + let ts = timer.load(Ordering::Relaxed); + if ts <= current_time && timer.fetch_max(ts.saturating_add(wait_time), Ordering::Relaxed) == ts { + None + } else { + Some(ts) + } +} + +/// Corresponds to Noise `EncryptAndHash`. +fn encrypt_and_hash( + sha512: &mut Application::Hash, + noise_k: &Secret, + noise_h: &[u8; HASHLEN], + packet_type: u8, + noise_k_uses: u64, + message: &mut [u8], +) -> [u8; HASHLEN] { + let auth_start = message.len() - AES_GCM_TAG_SIZE; + let mut gcm = Application::AeadEnc::new(noise_k.as_ref()); + // Encrypt and add authentication tag. + gcm.set_iv(&create_message_nonce(packet_type, INIT_COUNTER + noise_k_uses)); + gcm.set_aad(noise_h); + if auth_start > 0 { + gcm.encrypt_in_place(&mut message[..auth_start]); + } + gcm.finish_encrypt((&mut message[auth_start..]).try_into().unwrap()); + mix_hash(sha512, noise_h, message) +} +/// Corresponds to Noise `DecryptAndHash`. +fn decrypt_and_hash( + sha512: &mut Application::Hash, + noise_k: &Secret, + noise_h: &[u8; HASHLEN], + packet_type: u8, + noise_k_uses: u64, + message: &mut [u8], +) -> (bool, [u8; HASHLEN]) { + let auth_start = message.len() - AES_GCM_TAG_SIZE; + let noise_h_c = mix_hash(sha512, noise_h, message); + let mut gcm = Application::AeadDec::new(noise_k.as_ref()); + gcm.set_iv(&create_message_nonce(packet_type, INIT_COUNTER + noise_k_uses)); + gcm.set_aad(noise_h); + if auth_start > 0 { + gcm.decrypt_in_place(&mut message[..auth_start]); + } + (gcm.finish_decrypt((&message[auth_start..]).try_into().unwrap()), noise_h_c) +} +/// Encrypt a standardized control packet. +fn encrypt_control( + c: &mut impl AesGcmEnc, + header_cipher: &impl AesEnc, + packet_type: u8, + counter: u64, + remote_key_id: u32, + packet: &[u8], +) -> ([u8; CONTROL_PACKET_MAX_SIZE], usize) { + let mut fragment = [0u8; CONTROL_PACKET_MAX_SIZE]; + let fragment_len = packet.len() + HEADER_SIZE + AES_GCM_TAG_SIZE; + + c.set_iv(&create_message_nonce(packet_type, counter)); + if !packet.is_empty() { + fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE].copy_from_slice(packet); + c.encrypt_in_place(&mut fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE]); + } + c.finish_encrypt((&mut fragment[fragment_len - AES_GCM_TAG_SIZE..fragment_len]).try_into().unwrap()); + set_packet_header(&mut fragment, 1, 0, packet_type, remote_key_id, counter); + header_cipher.encrypt_in_place((&mut fragment[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]).try_into().unwrap()); + (fragment, fragment_len) +} +fn decrypt_control<'a, IoError>( + c: &mut impl AesGcmDec, + packet_type: u8, + counter: u64, + fragment: &'a mut [u8], +) -> Result<&'a mut [u8], ReceiveError> { + let fragment_len = fragment.len(); + if !(CONTROL_PACKET_MIN_SIZE..=CONTROL_PACKET_MAX_SIZE).contains(&fragment_len) { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + c.set_iv(&create_message_nonce(packet_type, counter)); + c.decrypt_in_place(&mut fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE]); + if !c.finish_decrypt((&fragment[fragment_len - AES_GCM_TAG_SIZE..fragment_len]).try_into().unwrap()) { + // This can occur naturally if one of the remote peers resent a + // control packet that got delayed and arrived out of order. + return Err(byzantine_fault!(FaultType::FailedAuthentication, true)); + } + Ok(&mut fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE]) +} + +fn set_packet_header(packet: &mut [u8], fragment_count: u8, fragment_no: u8, packet_type: u8, remote_key_id: u32, counter_or_id: u64) { + debug_assert!(packet.len() >= MIN_PACKET_SIZE); + debug_assert!(fragment_count > 0); + debug_assert!(fragment_count <= MAX_FRAGMENTS as u8); + debug_assert!(fragment_no < MAX_FRAGMENTS as u8); + debug_assert_eq!((packet_type << 1) >> 1, packet_type); + // [0..4] recipient key id + // -- start AES(ck_es * h_e_e1_p) encrypted block -- + // [4] fragment count (1..255) + // [5] fragment number (0..254) + // [6] reserved zero + // -- start of AES-GCM Nonce -- + // [7] packet type + // [8..16] 64-bit counter or packet id (big endian) + packet[4..16].copy_from_slice(&create_message_nonce(packet_type, counter_or_id)); + packet[0..4].copy_from_slice(&remote_key_id.to_ne_bytes()); + packet[4] = fragment_count; + packet[5] = fragment_no; + packet[6] = 0; +} +/// 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. +fn create_message_nonce(packet_type: u8, counter: u64) -> [u8; AES_GCM_IV_SIZE] { + let mut ret = [0u8; AES_GCM_IV_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 +} +/// returns `(fragment_count, fragment_no, packet_type, counter, header_nonce)`. +fn parse_packet_header(packet: &[u8]) -> (u8, u8, u8, u64, [u8; 10]) { + let header_nonce = packet[6..16].try_into().unwrap(); + let counter = packet[8..16].try_into().unwrap(); + // We intentionally ignore the version number for future revisions. + (packet[4], packet[5], packet[7], u64::from_be_bytes(counter), header_nonce) +} + +/// Break a packet into fragments and send them all. +/// +/// The contents of packet[] are mangled during this operation, so it should be discarded after. +/// This is only used for key exchange and control packets. For data packets this is done inline +/// for better performance with encryption and fragmentation happening at the same time. +fn send_with_fragmentation( + send: &mut impl FnMut(&mut [u8]) -> bool, + mtu: usize, + packet: &mut [u8], + packet_type: u8, + remote_key_id: Option, + counter_or_id: u64, + header_cipher: Option<&impl AesEnc>, +) -> bool { + let packet_len = packet.len(); + let fragment_count = (packet_len.saturating_add(mtu - 1)) / mtu; // integer ceiling divide + debug_assert!(fragment_count <= MAX_FRAGMENTS); + let mut fragment_start = 0; + let mut fragment_end = packet_len.min(mtu); + let mut fragment_no = 0; + loop { + let fragment = &mut packet[fragment_start..fragment_end]; + set_packet_header( + fragment, + fragment_count as u8, + fragment_no as u8, + packet_type, + remote_key_id.map_or(0, |n| n.get()), + counter_or_id, + ); + if let Some(hcc) = header_cipher { + hcc.encrypt_in_place((&mut fragment[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]).try_into().unwrap()); + } + if !send(fragment) { + return false; + } + fragment_no += 1; + if fragment_no < fragment_count { + fragment_start = fragment_end - HEADER_SIZE; + fragment_end = (fragment_start.saturating_add(mtu)).min(packet_len); + } else { + break; + } + } + true +} + +/// Assemble a series of fragments into a buffer and return the length of the assembled packet in +/// bytes. +/// +/// This is also only used for key exchange and control packets. For data packets decryption and +/// assembly happen in one pass for better performance. +fn assemble_fragments_into(fragments: &[A::IncomingPacketBuffer], d: &mut [u8]) -> Result> { + let mut l = 0; + for i in 0..fragments.len() { + let mut ff = fragments[i].as_ref(); + if i > 0 { + ff = &ff[HEADER_SIZE..]; + } + let j = l + ff.len(); + if j > d.len() { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + d[l..j].copy_from_slice(ff); + l = j; + } + Ok(l) +} +/// Generate a random local key id that is currently unused. +fn generate_key_id( + session_map: &HashMap>, bool)>, + rng: &mut Application::Rng, +) -> NonZeroU32 { + loop { + if let Some(local_key_id) = NonZeroU32::new(rng.next_u32()) { + if !session_map.contains_key(&local_key_id) { + return local_key_id; + } + } + } +} + +impl SessionKey { + fn new( + hmac: &mut Application::HmacHash, + ck: SymmetricState, + local_key_id: NonZeroU32, + remote_key_id: NonZeroU32, + current_counter: u64, + is_bob: bool, + ) -> Self { + let (b2a, a2b) = ck.split(hmac); + let (receive_key, send_key) = if is_bob { + (&a2b, &b2a) + } else { + (&b2a, &a2b) + }; + let send_cipher_pool = std::array::from_fn(|_| Mutex::new(Application::AeadEnc::new(send_key.as_ref()))); + let receive_cipher_pool = std::array::from_fn(|_| Mutex::new(Application::AeadDec::new(receive_key.as_ref()))); + Self { + local_key_id, + remote_key_id, + send_cipher_pool, + receive_cipher_pool, + rekey_at_counter: current_counter.saturating_add(Application::REKEY_AFTER_USES), + expire_at_counter: current_counter.saturating_add(Application::EXPIRE_AFTER_USES), + } + } + + fn get_send_cipher(&self, counter: u64) -> Result, SendError> { + if counter < self.expire_at_counter { + Ok(self.send_cipher_pool[(counter as usize) % self.send_cipher_pool.len()].lock().unwrap()) + } else { + Err(SendError::SessionExpired) + } + } + + fn get_receive_cipher(&self, counter: u64) -> MutexGuard { + let idx = (counter as usize) % self.receive_cipher_pool.len(); + self.receive_cipher_pool[idx].lock().unwrap() + } +} + +/// MixHash to update 'h' during negotiation. +fn mix_hash(hasher: &mut impl Sha512, h: &[u8; HASHLEN], m: &[u8]) -> [u8; HASHLEN] { + let mut output = [0u8; HASHLEN]; + hasher.reset(); + hasher.update(h); + hasher.update(m); + hasher.finish(&mut output); + output +} +/// Check if the proof of work attached to the first message contains the correct number of leading +/// zeros. +fn verify_pow(hasher: &mut Application::Hash, response: &[u8]) -> bool { + if Application::PROOF_OF_WORK_BIT_DIFFICULTY == 0 { + return true; + } + hasher.reset(); + hasher.update(response); + let mut output = [0u8; HASHLEN]; + hasher.finish(&mut output); + let n = u32::from_be_bytes(output[..4].try_into().unwrap()); + n.leading_zeros() >= Application::PROOF_OF_WORK_BIT_DIFFICULTY +} +fn from_bytes_agreement( + public: &[u8], + private: &Application::KeyPair, + output: &mut [u8; P384_ECDH_SHARED_SECRET_SIZE], +) -> Option { + Application::PublicKey::from_bytes(public.try_into().unwrap()).and_then(|e| private.agree(&e, output).then_some(e)) +} diff --git a/src/zssp.rs b/src/zssp.rs index 98f9a3f..2288a43 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -16,15 +16,17 @@ use std::ops::DerefMut; use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering}; use std::sync::{Arc, Mutex, MutexGuard, RwLock, Weak}; -use crate::crypto::aes::{AesDec, AesEnc}; -use crate::crypto::aes_gcm::{AesGcmDec, AesGcmEnc, AES_GCM_IV_SIZE, AES_GCM_KEY_SIZE, AES_GCM_TAG_SIZE}; +use arrayvec::ArrayVec; +use zeroize::Zeroizing; + +use crate::challenge::ChallengeContext; +use crate::crypto::aes::{AesDec, AesEnc, AES_256_KEY_SIZE}; use crate::crypto::p384::{P384KeyPair, P384PublicKey, P384_ECDH_SHARED_SECRET_SIZE, P384_PUBLIC_KEY_SIZE}; use crate::crypto::pqc_kyber::KYBER_SECRETKEYBYTES; use crate::crypto::rand_core::RngCore; -use crate::crypto::secret::{secure_eq, Secret}; -use crate::crypto::sha512::{HmacSha512, Sha512}; +use crate::crypto::sha512::{HmacSha512, HashSha512}; -use crate::error::{FaultType, OpenError, ReceiveError, SendError}; +use crate::result::{FaultType, OpenError, ReceiveError, SendError}; use crate::frag_cache::UnassociatedFragCache; use crate::fragged::{Assembled, Fragged}; use crate::handshake_cache::UnassociatedHandshakeCache; @@ -53,9 +55,7 @@ pub struct ContextInner { /// `session_queue -> state_machine_lock -> state -> session_map` session_queue: Mutex>, Reverse>>, session_map: RwLock>, bool)>>, - challenge_counter: AtomicU64, - challenge_antireplay_window: [AtomicU64; CHALLENGE_COUNTER_WINDOW_MAX_OOO], - challenge_salt: [u8; CHALLENGE_SALT_SIZE], + challenge: ChallengeContext, rng: Mutex, } @@ -114,18 +114,18 @@ pub enum IncomingSessionAction { /// 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 struct Session { /// An arbitrary application defined object associated with each session. - pub application_data: Application::Data, + pub application_data: App::SessionData, /// Is true if the local peer acted as Bob, the responder in the initial key exchange. pub was_bob: bool, /// The receive context associated with this session, /// only this context can receive messages from the remote peer. - context: Weak>, + context: Weak>, /// Handle into the session queue for changing the update timer. queue_idx: BinaryHeapIndex, - remote_static_key: Application::PublicKey, + remote_static_key: App::PublicKey, send_counter: AtomicU64, /// This bool signals to all threads to stop incrementing the counter and instead error out. session_has_expired: AtomicBool, @@ -137,16 +137,16 @@ pub struct Session { /// it goes `session_queue -> state_machine_lock -> state -> session_map`. /// Any lock can be skipped but they must be locked in that order. state_machine_lock: Mutex<()>, - state: RwLock>, - defrag: [Mutex>; SESSION_MAX_FRAGMENTS_OOO], - header_send_cipher: Application::PrpEnc, - header_receive_cipher: Application::PrpDec, - kex_send_cipher: Mutex>, - kex_receive_cipher: Mutex>, + state: RwLock>, + defrag: [Mutex>; SESSION_MAX_FRAGMENTS_OOO], + header_send_cipher: App::PrpEnc, + header_receive_cipher: App::PrpDec, + kex_send_cipher: Mutex>, + kex_receive_cipher: Mutex>, /// Pre-computed rekeying values. - noise_kk_ss: Secret, - noise_kk_local_init_h: [u8; NOISE_HASHLEN], - noise_kk_remote_init_h: [u8; NOISE_HASHLEN], + noise_kk_ss: Zeroizing<[u8; P384_ECDH_SHARED_SECRET_SIZE]>, + noise_kk_local_init_h: [u8; HASHLEN], + noise_kk_remote_init_h: [u8; HASHLEN], } /// `AesGcm` is not threadsafe, but it is threadsafe when inside a `Mutex`. unsafe impl Send for Session {} @@ -161,6 +161,8 @@ struct SessionMutableState { /// This is the index of `noise_cipher_state` that contains the most recent key. /// It will be attached to fragment headers to help with OOO transport. current_key: usize, + resent_timer: AtomicI64, + timeout_timer: i64, /// This defines the exact state of the offer state machine we are in. outgoing_offer: OfferStateMachine, } @@ -168,73 +170,57 @@ struct SessionMutableState { /// These offer enums form a state machine. /// Documented below are the only legal transitions for this state machine. /// A session is initialized with an `outgoing_offer` of either NoiseXKPattern1 or Normal. -enum OfferStateMachine { - Normal { - timeout: i64, - }, // -> NoiseKKPattern1, NoiseKKPattern2 +enum OfferStateMachine { + Normal, // -> NoiseKKPattern1, NoiseKKPattern2 /// This state uses a lot of memory so we put it on the heap. - NoiseXKPattern1or3(Box>), // -> Normal + NoiseXKPattern1or3(Box>), // -> Normal NoiseKKPattern1 { - next_retry_time: AtomicI64, - timeout: i64, new_key_id: NonZeroU32, - noise_e_secret: Application::KeyPair, - noise_message: [u8; NoiseKKPattern1or2::SIZE], - noise_ck: SymmetricState, - noise_h_pskep: [u8; NOISE_HASHLEN], + noise_e_secret: App::KeyPair, + noise_message: ArrayVec, + noise_ck: SymmetricState, }, // -> NoiseKKPattern2, KeyConfirm NoiseKKPattern2 { - next_retry_time: AtomicI64, - timeout: i64, - noise_message: [u8; NoiseKKPattern1or2::SIZE], - kex_send_key: Secret, - }, // -> Normal - KeyConfirm { - next_retry_time: AtomicI64, - timeout: i64, + noise_message: ArrayVec, + kex_send_key: Zeroizing<[u8; AES_256_KEY_SIZE]>, }, // -> Normal + KeyConfirm, // -> Normal Null, } -pub(crate) struct NoiseXKBobHandshakeState { +pub(crate) struct NoiseXKBobHandshakeState { /// Can never be Null. ratchet_state: RatchetState, remote_key_id: NonZeroU32, local_key_id: NonZeroU32, - header_receive_key: Secret, - header_send_key: Secret, - noise_h_ee1peekem1pskp: [u8; NOISE_HASHLEN], - noise_e_secret: Application::KeyPair, - noise_ck_eseeekem1psk: SymmetricState, - noise_k_eseeekem1psk: Secret, - noise_pattern3_defrag: Mutex>, + header_receive_key: Zeroizing<[u8; AES_256_KEY_SIZE]>, + header_send_key: Zeroizing<[u8; AES_256_KEY_SIZE]>, + noise_e_secret: App::KeyPair, + noise_ck_eseeekem1psk: SymmetricState, + noise_pattern3_defrag: Mutex>, } -struct NoiseXKAliceHandshake { - next_retry_time: AtomicI64, - timeout: i64, +struct NoiseXKAliceHandshake { /// A secure random number put in the header of Alice's fragments to identify them. /// If a DDOS attacker could guess this they could block Alice starting the handshake. local_key_id: NonZeroU32, - alice_identity_blob: Application::LocalIdentityBlob, - offer: NoiseXKAliceHandshakeState, + alice_identity_blob: App::LocalIdentityBlob, + offer: NoiseXKAliceHandshakeState, } -enum NoiseXKAliceHandshakeState { +enum NoiseXKAliceHandshakeState { NoiseXKPattern1 { - noise_h_ee1p: [u8; NOISE_HASHLEN], - noise_e_secret: Application::KeyPair, - noise_e1_secret: Secret, - noise_ck_es: SymmetricState, + noise_h_ee1p: [u8; HASHLEN], + noise_e_secret: App::KeyPair, + noise_e1_secret: App::Kem, + noise_ck_es: SymmetricState, /// ZSSP assumes an unreliable, out-of-order physical transport environment, so for that /// reason we have to resend key offers. - noise_message: [u8; NoiseXKPattern1::MAX_SIZE], - noise_message_len: usize, + noise_message: ArrayVec, message_id: u64, }, NoiseXKPattern3 { - noise_message: [u8; NoiseXKPattern3::MAX_SIZE], - noise_message_len: usize, + noise_message: ArrayVec, }, } @@ -476,10 +462,10 @@ impl Context { mut send: impl FnMut(&mut [u8]) -> bool, mut mtu: usize, remote_static_key: Application::PublicKey, - application_data: Application::Data, + application_data: Application::SessionData, local_identity_blob: Application::LocalIdentityBlob, current_time: i64, - ) -> Result>, OpenError> { + ) -> Result>, OpenError> { mtu = mtu.max(MIN_TRANSPORT_MTU); if local_identity_blob.as_ref().len() > MAX_IDENTITY_BLOB_SIZE { return Err(OpenError::DataTooLarge); @@ -602,7 +588,7 @@ impl Context { &self, app: &Application, check_allow_incoming_session: impl FnOnce() -> IncomingSessionAction, - check_accept_session: impl FnOnce(&Application::PublicKey, &[u8], u64) -> (Option<(bool, Application::Data)>, bool), + check_accept_session: impl FnOnce(&Application::PublicKey, &[u8], u64) -> (Option<(bool, Application::SessionData)>, bool), mut send_unassociated_reply: impl FnMut(&mut [u8]) -> bool, mut send_unassociated_mtu: usize, mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, @@ -610,7 +596,7 @@ impl Context { data_buf: &'a mut [u8], mut incoming_physical_packet_buf: Application::IncomingPacketBuffer, current_time: i64, - ) -> Result, ReceiveError> { + ) -> Result, ReceiveError> { send_unassociated_mtu = send_unassociated_mtu.max(MIN_TRANSPORT_MTU); let incoming_physical_packet: &mut [u8] = incoming_physical_packet_buf.as_mut(); let incoming_physical_packet_len = incoming_physical_packet.len(); @@ -902,7 +888,7 @@ impl Context { sha512.reset(); let mut hasher = ShaHasher(sha512); - let mut output = [0u8; NOISE_HASHLEN]; + let mut output = [0u8; HASHLEN]; hasher.0.update(&response.challenge_counter); remote_address.hash(&mut hasher); hasher.0.update(&self.0.challenge_salt); @@ -1823,7 +1809,7 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu counter: u64, fragment: &mut [u8], current_time: i64, -) -> Result, ReceiveError> { +) -> Result, ReceiveError> { let kex_lock = session.state_machine_lock.lock().unwrap(); let state = session.state.read().unwrap(); let mut c = session.kex_receive_cipher.lock().unwrap(); @@ -2307,7 +2293,7 @@ impl NoiseXKAliceHandshake { Secret, Secret, ), - OpenError, + OpenError, > { let mut message = [0u8; NoiseXKPattern1::MAX_SIZE]; let sha512 = &mut Application::Hash::new(); @@ -2433,11 +2419,11 @@ fn process_timer(timer: &AtomicI64, wait_time: i64, current_time: i64) -> Option fn encrypt_and_hash( sha512: &mut Application::Hash, noise_k: &Secret, - noise_h: &[u8; NOISE_HASHLEN], + noise_h: &[u8; HASHLEN], packet_type: u8, noise_k_uses: u64, message: &mut [u8], -) -> [u8; NOISE_HASHLEN] { +) -> [u8; HASHLEN] { let auth_start = message.len() - AES_GCM_TAG_SIZE; let mut gcm = Application::AeadEnc::new(noise_k.as_ref()); // Encrypt and add authentication tag. @@ -2453,11 +2439,11 @@ fn encrypt_and_hash( fn decrypt_and_hash( sha512: &mut Application::Hash, noise_k: &Secret, - noise_h: &[u8; NOISE_HASHLEN], + noise_h: &[u8; HASHLEN], packet_type: u8, noise_k_uses: u64, message: &mut [u8], -) -> (bool, [u8; NOISE_HASHLEN]) { +) -> (bool, [u8; HASHLEN]) { let auth_start = message.len() - AES_GCM_TAG_SIZE; let noise_h_c = mix_hash(sha512, noise_h, message); let mut gcm = Application::AeadDec::new(noise_k.as_ref()); @@ -2604,7 +2590,7 @@ fn send_with_fragmentation( /// /// This is also only used for key exchange and control packets. For data packets decryption and /// assembly happen in one pass for better performance. -fn assemble_fragments_into(fragments: &[A::IncomingPacketBuffer], d: &mut [u8]) -> Result> { +fn assemble_fragments_into(fragments: &[A::IncomingPacketBuffer], d: &mut [u8]) -> Result> { let mut l = 0; for i in 0..fragments.len() { let mut ff = fragments[i].as_ref(); @@ -2676,8 +2662,8 @@ impl SessionKey { } /// MixHash to update 'h' during negotiation. -fn mix_hash(hasher: &mut impl Sha512, h: &[u8; NOISE_HASHLEN], m: &[u8]) -> [u8; NOISE_HASHLEN] { - let mut output = [0u8; NOISE_HASHLEN]; +fn mix_hash(hasher: &mut impl Sha512, h: &[u8; HASHLEN], m: &[u8]) -> [u8; HASHLEN] { + let mut output = [0u8; HASHLEN]; hasher.reset(); hasher.update(h); hasher.update(m); @@ -2692,7 +2678,7 @@ fn verify_pow(hasher: &mut Application::Hash, res } hasher.reset(); hasher.update(response); - let mut output = [0u8; NOISE_HASHLEN]; + let mut output = [0u8; HASHLEN]; hasher.finish(&mut output); let n = u32::from_be_bytes(output[..4].try_into().unwrap()); n.leading_zeros() >= Application::PROOF_OF_WORK_BIT_DIFFICULTY From 82e3bc647f9dbd559d36858a893333a96bfc564c Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 7 Aug 2023 15:04:02 -0400 Subject: [PATCH 16/50] added sending --- rustfmt.toml | 2 +- src/applicationlayer.rs | 2 +- src/challenge.rs | 4 +- src/context.rs | 20 +- src/frag_cache.rs | 31 +- src/fragged.rs | 22 +- src/handshake_cache.rs | 12 +- src/lib.rs | 12 +- src/log_event.rs | 2 +- src/proto.rs | 107 +- src/result.rs | 2 + src/symmetric_state.rs | 32 +- src/zeta.rs | 1085 +++++++-------- src/zssp.rs | 2761 ++++++--------------------------------- 14 files changed, 1129 insertions(+), 2965 deletions(-) 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 7f18f60..2dcfc96 100644 --- a/src/applicationlayer.rs +++ b/src/applicationlayer.rs @@ -14,7 +14,7 @@ use crate::crypto::rand_core::{CryptoRng, RngCore}; use crate::crypto::sha512::{HmacSha512, HashSha512}; use crate::RatchetState; use crate::proto::RATCHET_SIZE; -use crate::zssp::Session; +use crate::zeta::Session; //use crate::{log_event::LogEvent, Session}; /// A container for a vast majority of the dynamic settings within ZSSP, including all time-based settings. diff --git a/src/challenge.rs b/src/challenge.rs index 76e6cc5..c47af7b 100644 --- a/src/challenge.rs +++ b/src/challenge.rs @@ -54,12 +54,12 @@ impl ChallengeContext { &self, addr: &impl std::hash::Hash, response: &[u8; CHALLENGE_SIZE], - ) -> Result { + ) -> Result<(), [u8; CHALLENGE_SIZE]> { let c = u64::from_be_bytes(response[..COUNTER_SIZE].try_into().unwrap()); let mut work_buf = [0u8; SHA512_HASH_SIZE]; if self.antireplay_window.check(c) && secure_eq(&response[COUNTER_SIZE..POW_START], &self.create_mac::(c, addr)) && verify_pow::(response, &mut work_buf) { self.antireplay_window.update(c); - Ok(true) + Ok(()) } else { let mut challenge = [0u8; CHALLENGE_SIZE]; let d = self.counter.fetch_add(1, Ordering::Relaxed); diff --git a/src/context.rs b/src/context.rs index ba69585..2320365 100644 --- a/src/context.rs +++ b/src/context.rs @@ -8,6 +8,8 @@ use std::sync::{Arc, Mutex, Weak, RwLock}; use crate::applicationlayer::ApplicationLayer; use crate::crypto::aes::{AES_256_KEY_SIZE, AES_GCM_IV_SIZE}; +use crate::frag_cache::UnassociatedFragCache; +use crate::handshake_cache::UnassociatedHandshakeCache; use crate::indexed_heap::IndexedBinaryHeap; //use crate::fragmentation::{send_with_fragmentation, DefragBuffer}; use crate::proto::*; @@ -46,6 +48,8 @@ pub(crate) struct ContextInner { pub(crate) s_secret: App::KeyPair, pub(crate) session_queue: Mutex>, Reverse>>, pub(crate) session_map: SessionMap, + unassociated_defrag_cache: Mutex>, + unassociated_handshake_states: UnassociatedHandshakeCache, //pub(crate) b2_map: Mutex>>, //hello_defrag: Mutex, @@ -70,19 +74,13 @@ impl Context { Self(Arc::new(ContextInner { rng: Mutex::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: RwLock::new(HashMap::new()), + challenge, + session_queue: Mutex::new(IndexedBinaryHeap::new()), + unassociated_defrag_cache: Mutex::new(UnassociatedFragCache::new()), + unassociated_handshake_states: UnassociatedHandshakeCache::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; - } - /// 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 diff --git a/src/frag_cache.rs b/src/frag_cache.rs index 62ddfc0..f0c9c27 100644 --- a/src/frag_cache.rs +++ b/src/frag_cache.rs @@ -10,6 +10,7 @@ use std::collections::hash_map::RandomState; use std::hash::{BuildHasher, Hash, Hasher}; use std::mem::MaybeUninit; +use crate::crypto::aes::AES_GCM_IV_SIZE; use crate::fragged::Assembled; use crate::proto::{MAX_FRAGMENTS, MAX_UNASSOCIATED_FRAGMENTS, MAX_UNASSOCIATED_PACKETS, MAX_UNASSOCIATED_PACKET_SIZE}; @@ -17,7 +18,7 @@ struct PacketMetadata { key: u64, frags_idx: u32, fragment_have: u64, - fragment_count: u32, + fragment_count: u8, packet_size: u32, creation_time: i64, } @@ -55,24 +56,24 @@ impl UnassociatedFragCache { /// Will check that aad is the same for all fragments. pub(crate) fn assemble( &mut self, - nonce: [u8; 10], + nonce: &[u8; AES_GCM_IV_SIZE], remote_address: impl Hash, fragment_size: usize, fragment: Fragment, - fragment_no: u8, - fragment_count: u8, - timeout: i64, + fragment_no: usize, + fragment_count: usize, + timeout_interval: i64, current_time: i64, ret_assembled: &mut Assembled, ) { debug_assert!(MAX_FRAGMENTS < MAX_UNASSOCIATED_FRAGMENTS); - if fragment_no >= fragment_count || (fragment_count as usize) > MAX_FRAGMENTS || fragment_size > MAX_UNASSOCIATED_PACKET_SIZE { + if fragment_no >= fragment_count || fragment_count > MAX_FRAGMENTS || fragment_size > MAX_UNASSOCIATED_PACKET_SIZE { return; } let mut hasher = self.dos_salt.build_hasher(); remote_address.hash(&mut hasher); - hasher.write(&nonce); + hasher.write(nonce); let mut key = hasher.finish(); if key == 0 { key = 1; @@ -98,7 +99,7 @@ impl UnassociatedFragCache { } else if self.map[idx0].key == 0 || self.map[idx1].key == 0 { if (fragment_count as usize) > self.frags_unused_size { // There are not enough free fragment slots so attempt to expire a bunch of entries. - self.check_for_expiry(timeout, current_time); + self.check_for_expiry(timeout_interval, current_time); } if self.map[idx0].key == 0 { idx0 @@ -107,7 +108,7 @@ impl UnassociatedFragCache { } } else { // No room for a new entry so attempt to expire a bunch of entries. - self.check_for_expiry(timeout, current_time); + self.check_for_expiry(timeout_interval, current_time); if self.map[idx0].key == 0 { idx0 } else if self.map[idx1].key == 0 { @@ -125,7 +126,7 @@ impl UnassociatedFragCache { entry.key = key; entry.frags_idx = self.frags_first_unused as u32; entry.fragment_have = 0; - entry.fragment_count = fragment_count as u32; + entry.fragment_count = fragment_count as u8; entry.packet_size = 0; entry.creation_time = current_time; @@ -143,7 +144,7 @@ impl UnassociatedFragCache { let new_size = entry.packet_size + fragment_size as u32; let got = 1u64.wrapping_shl(fragment_no as u32); - if got & entry.fragment_have == 0 && fragment_count == entry.fragment_count as u8 && new_size <= MAX_UNASSOCIATED_PACKET_SIZE as u32 { + if got & entry.fragment_have == 0 && fragment_count == entry.fragment_count as usize && new_size <= MAX_UNASSOCIATED_PACKET_SIZE as u32 { entry.packet_size = new_size; entry.fragment_have |= got; @@ -272,9 +273,9 @@ fn test_cache() { if drop != j { let fragment = vec![0, 1, 2, 3, 4, 5, 6, r]; // If the timeout is 1 we should be guaranteed to get our packet cached. - let mut nonce = [0; 10]; + let mut nonce = [0; 12]; nonce[..4].copy_from_slice(&i.to_be_bytes()); - cache.assemble(nonce, 0, fragment.len(), fragment, j as u8, fragment_count as u8, 1, time, &mut assembled); + cache.assemble(&nonce, 0, fragment.len(), fragment, j, fragment_count, 1, time, &mut assembled); time += 1; } } @@ -297,9 +298,9 @@ fn test_cache() { let (no, fragment) = packet.swap_remove(xorshift64_random() as usize % packet.len()); assembled.empty(); - let mut nonce = [0; 10]; + let mut nonce = [0; 12]; nonce[..4].copy_from_slice(&id.to_be_bytes()); - cache.assemble(nonce, 0, fragment.len(), fragment, no, fragment_count, 1000, time, &mut assembled); + cache.assemble(&nonce, 0, fragment.len(), fragment, no as usize, fragment_count as usize, 1000, time, &mut assembled); time += 1; in_progress_fragments -= 1; diff --git a/src/fragged.rs b/src/fragged.rs index 77ee095..145b5d8 100644 --- a/src/fragged.rs +++ b/src/fragged.rs @@ -9,7 +9,8 @@ use std::mem::{needs_drop, zeroed, MaybeUninit}; use std::ptr::slice_from_raw_parts; -use crate::proto::MAX_FRAGMENTS; +use crate::crypto::aes::AES_GCM_IV_SIZE; +use crate::proto::{MAX_FRAGMENTS, NONCE_SIZE_DIFF}; pub(crate) struct Assembled(pub(crate) [MaybeUninit; MAX_FRAGMENTS], pub(crate) usize); @@ -42,9 +43,9 @@ impl Drop for Assembled { /// Fast packet defragmenter pub struct Fragged { - count: u32, - have: u64, nonce: [u8; 10], + count: u8, + have: u64, size: usize, frags: [MaybeUninit; MAX_FRAGMENTS], } @@ -63,28 +64,29 @@ impl Fragged { /// Will check that aad is the same for all fragments. pub(crate) fn assemble( &mut self, - nonce: [u8; 10], + nonce: &[u8; AES_GCM_IV_SIZE], fragment: Fragment, - fragment_no: u8, - fragment_count: u8, + fragment_no: usize, + fragment_count: usize, ret_assembled: &mut Assembled, ) { - if fragment_no < fragment_count && (fragment_count as usize) <= MAX_FRAGMENTS { + if fragment_no < fragment_count && fragment_count <= MAX_FRAGMENTS { + let nonce = nonce[NONCE_SIZE_DIFF..].try_into().unwrap(); // If the counter has changed, reset the structure to receive a new packet. if nonce != self.nonce { self.drop_in_place(); - self.count = fragment_count as u32; + self.count = fragment_count as u8; self.nonce = nonce; self.size = 0; } let got = 1u64.wrapping_shl(fragment_no as u32); - if got & self.have == 0 && self.count as u8 == fragment_count { + if got & self.have == 0 && self.count == fragment_count as u8 { self.have |= got; unsafe { self.frags.get_unchecked_mut(fragment_no as usize).write(fragment); } - if self.have == 1u64.wrapping_shl(self.count) - 1 { + if self.have == 1u64.wrapping_shl(self.count as u32) - 1 { self.have = 0; self.count = 0; self.nonce = [0; 10]; diff --git a/src/handshake_cache.rs b/src/handshake_cache.rs index 25f219a..8ce3852 100644 --- a/src/handshake_cache.rs +++ b/src/handshake_cache.rs @@ -10,7 +10,7 @@ use std::num::NonZeroU32; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, RwLock}; -use crate::zssp::NoiseXKBobHandshakeState; +use crate::zeta::StateB2; use crate::{proto::MAX_UNASSOCIATED_HANDSHAKE_STATES, ApplicationLayer}; pub(crate) struct UnassociatedHandshakeCache { @@ -18,10 +18,10 @@ pub(crate) struct UnassociatedHandshakeCache { cache: RwLock>, } /// SoA format -struct CacheInner { +struct CacheInner { local_ids: [Option; MAX_UNASSOCIATED_HANDSHAKE_STATES], timeouts: [i64; MAX_UNASSOCIATED_HANDSHAKE_STATES], - handshakes: [Option>>; MAX_UNASSOCIATED_HANDSHAKE_STATES], + handshakes: [Option>>; MAX_UNASSOCIATED_HANDSHAKE_STATES], } /// Linear-search cache for capping the memory consumption of handshake data. @@ -38,7 +38,7 @@ impl UnassociatedHandshakeCache { }), } } - pub(crate) fn get(&self, local_id: NonZeroU32) -> Option>> { + pub(crate) fn get(&self, local_id: NonZeroU32) -> Option>> { let cache = self.cache.read().unwrap(); for (i, id) in cache.local_ids.iter().enumerate() { if *id == Some(local_id) { @@ -47,7 +47,7 @@ impl UnassociatedHandshakeCache { } None } - pub(crate) fn insert(&self, local_id: NonZeroU32, state: Arc>, current_time: i64) { + pub(crate) fn insert(&self, local_id: NonZeroU32, state: Arc>, current_time: i64) { let mut cache = self.cache.write().unwrap(); let mut idx = 0; for i in 0..cache.local_ids.len() { @@ -59,7 +59,7 @@ impl UnassociatedHandshakeCache { } } cache.local_ids[idx] = Some(local_id); - cache.timeouts[idx] = current_time.saturating_add(Application::INITIAL_OFFER_TIMEOUT_MS); + cache.timeouts[idx] = current_time + Application::SETTINGS.fragment_assembly_timeout as i64; cache.handshakes[idx] = Some(state); self.has_pending.store(true, Ordering::Release); } diff --git a/src/lib.rs b/src/lib.rs index 16c9ec0..61c0db5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,20 +8,20 @@ pub mod crypto; mod applicationlayer; -//mod frag_cache; -//mod fragged; -//mod handshake_cache; +mod fragged; +mod frag_cache; +mod handshake_cache; mod indexed_heap; -//mod log_event; +mod log_event; mod proto; mod ratchet_state; mod symmetric_state; mod antireplay; mod challenge; pub mod result; -//mod zssp; +mod zssp; mod zeta; -mod context; +//mod context; //pub mod error; pub use crate::applicationlayer::ApplicationLayer; diff --git a/src/log_event.rs b/src/log_event.rs index be543aa..cf96367 100644 --- a/src/log_event.rs +++ b/src/log_event.rs @@ -7,7 +7,7 @@ */ use std::sync::Arc; -use crate::{ApplicationLayer, zssp::Session}; +use crate::{ApplicationLayer, zeta::Session}; /// ZSSP events that might be interesting to log or aggregate into metrics. pub enum LogEvent<'a, Application: ApplicationLayer> { diff --git a/src/proto.rs b/src/proto.rs index 0d466a7..3f11bb1 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -1,8 +1,9 @@ +use crate::crypto::{aes::{AES_GCM_TAG_SIZE, AES_GCM_IV_SIZE}, kyber1024::{KYBER_CIPHERTEXT_SIZE, KYBER_PUBLIC_KEY_SIZE}, p384::P384_PUBLIC_KEY_SIZE, sha512::SHA512_HASH_SIZE}; /* Common constants */ -use crate::crypto::{sha512::SHA512_HASH_SIZE, p384::P384_PUBLIC_KEY_SIZE, kyber1024::{KYBER_PUBLIC_KEY_SIZE, KYBER_CIPHERTEXT_SIZE}, aes::AES_GCM_TAG_SIZE}; - +/// 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. @@ -44,33 +45,11 @@ 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; -/// Maximum window over which session packets may be reordered to be defragmented and -/// reassembled. Out of order fragments may be dropped in favor of newer fragments. -/// Increasing this value makes a session consume more significantly more memory. -pub(crate) const SESSION_MAX_FRAGMENTS_OOO: usize = 32; -/// The maximum number of unassociated packets that a receive context will cache. -/// Additional packets will either be dropped or cause a different packet to be dropped -/// from the cache. -/// Larger values consume more memory but provide better reliability and DDOS resistance. -pub(crate) const MAX_UNASSOCIATED_PACKETS: usize = 32; -/// The maximum number of fragments of unassociated packets that a receive context will -/// cache. -/// All unassociated fragments share the same buffer, when it fills up additional -/// fragments will be dropped or cause other fragments to be dropped from the cache. -/// Larger values consume more memory but provide better reliability and DDOS resistance. -pub(crate) const MAX_UNASSOCIATED_FRAGMENTS: usize = 32 * 32; -/// The maximum number of `NoiseXKBobHandshakeState` that a receive context will cache. -/// These are extremely large and since Alice has not been authenticated we put a hard -/// limit to how many we cache. -/// Larger values consume more memory but provide better reliability and DDOS resistance. -pub(crate) const MAX_UNASSOCIATED_HANDSHAKE_STATES: usize = 32; - -/// The maximum size a packet that is not associated to a session may be. -/// Excludes the size of headers for fragmentation. -pub(crate) const MAX_UNASSOCIATED_PACKET_SIZE: usize = HANDSHAKE_HELLO_MAX_SIZE - HEADER_SIZE; - +pub(crate) const NONCE_SIZE_DIFF: usize = AES_GCM_IV_SIZE - PACKET_NONCE_SIZE; /* Key exchange constants */ /* @@ -93,9 +72,11 @@ 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"; @@ -104,9 +85,21 @@ 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; +pub(crate) const THREAD_SAFE_COUNTER_HARD_EXPIRE: u64 = u64::MAX - 1<<16; /* Packet constants */ @@ -122,20 +115,64 @@ 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 MAX_HANDSHAKE_SIZE: usize = MAX_FRAGMENTS * MIN_TRANSPORT_MTU; - 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_CHALLENGE_MIN_SIZE: usize = HANDSHAKE_HELLO_MIN_SIZE + CHALLENGE_SIZE; +pub(crate) const HANDSHAKE_HELLO_CHALLENGE_MAX_SIZE: usize = HANDSHAKE_HELLO_MAX_SIZE + CHALLENGE_SIZE; + +pub(crate) const HEADERED_HANDSHAKE_HELLO_CHALLENGE_MAX_SIZE: usize = HANDSHAKE_HELLO_CHALLENGE_MAX_SIZE + HEADER_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 HEADERED_HANDSHAKE_RESPONSE_SIZE: usize = HANDSHAKE_RESPONSE_SIZE + HEADER_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 = MAX_HANDSHAKE_SIZE; +pub(crate) const HANDSHAKE_COMPLETION_MAX_SIZE: usize = HANDSHAKE_COMPLETION_MIN_SIZE + IDENTITY_MAX_SIZE; + +pub(crate) const HEADERED_HANDSHAKE_COMPLETION_MAX_SIZE: usize = HANDSHAKE_COMPLETION_MAX_SIZE + HEADER_SIZE; pub(crate) const KEY_CONFIRMATION_SIZE: usize = AES_GCM_TAG_SIZE; +pub(crate) const HEADERED_KEY_CONFIRMATION_SIZE: usize = KEY_CONFIRMATION_SIZE + HEADER_SIZE; + pub(crate) const ACKNOWLEDGEMENT_SIZE: usize = AES_GCM_TAG_SIZE; +pub(crate) const HEADERED_ACKNOWLEDGEMENT_SIZE: usize = ACKNOWLEDGEMENT_SIZE + HEADER_SIZE; + pub(crate) const SESSION_REJECTED_SIZE: usize = AES_GCM_TAG_SIZE; +pub(crate) const HEADERED_SESSION_REJECTED_SIZE: usize = SESSION_REJECTED_SIZE + HEADER_SIZE; pub(crate) const REKEY_SIZE: usize = P384_PUBLIC_KEY_SIZE + KID_SIZE + AES_GCM_TAG_SIZE + AES_GCM_TAG_SIZE; +pub(crate) const HEADERED_REKEY_SIZE: usize = P384_PUBLIC_KEY_SIZE + KID_SIZE + AES_GCM_TAG_SIZE + AES_GCM_TAG_SIZE; -pub(crate) const MAX_IDENTITY_SIZE: usize = MAX_HANDSHAKE_SIZE - HANDSHAKE_COMPLETION_MIN_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 IDENTITY_MAX_SIZE: usize = 4096; + +/* DOS mitigation constants */ + +/// The maximum number of `NoiseXKBobHandshakeState` that a receive context will cache. +/// These are extremely large and since Alice has not been authenticated we put a hard +/// limit to how many we cache. +/// Larger values consume more memory but provide better reliability and DDOS resistance. +pub(crate) const MAX_UNASSOCIATED_HANDSHAKE_STATES: usize = 32; + +/// The maximum number of unassociated packets that a receive context will cache. +/// Additional packets will either be dropped or cause a different packet to be dropped +/// from the cache. +/// Larger values consume more memory but provide better reliability and DDOS resistance. +pub(crate) const MAX_UNASSOCIATED_PACKETS: usize = 32; +/// The maximum number of fragments of unassociated packets that a receive context will +/// cache. +/// All unassociated fragments share the same buffer, when it fills up additional +/// fragments will be dropped or cause other fragments to be dropped from the cache. +/// Larger values consume more memory but provide better reliability and DDOS resistance. +pub(crate) const MAX_UNASSOCIATED_FRAGMENTS: usize = 32 * 32; + +/// The maximum size a packet that is not associated to a session may be. +/// Excludes the size of headers for fragmentation. +pub(crate) const MAX_UNASSOCIATED_PACKET_SIZE: usize = HANDSHAKE_HELLO_MAX_SIZE; + +pub(crate) const SESSION_MAX_FRAGMENTS_OOO: usize = 64; diff --git a/src/result.rs b/src/result.rs index d542227..4710492 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 a2caf2b..f2e2a22 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -80,11 +80,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, } } @@ -98,6 +98,14 @@ impl SymmetricState { *self.ck = *next_ck; self.k.clone_from_slice(&temp_k[..AES_256_KEY_SIZE]); } + /// Corresponds to Noise `MixKey`. + pub fn mix_key_no_init(&mut self, hmac: &mut App::HmacHash, input_key_material: &[u8]) { + let mut next_ck = Zeroizing::new([0u8; HASHLEN]); + + self.kbkdf(hmac, input_key_material, LABEL_KBKDF_CHAIN, 2, &mut next_ck, None, None); + + *self.ck = *next_ck; + } /// Corresponds to Noise `MixHash`. pub fn mix_hash(&mut self, hash: &mut App::Hash, data: &[u8]) { hash.update(&self.h); @@ -124,6 +132,24 @@ impl SymmetricState { self.mix_hash(hash, &temp_h); self.k.clone_from_slice(&temp_k[..AES_256_KEY_SIZE]); } + /// Corresponds to Noise `MixKeyAndHash`. + pub fn mix_key_and_hash_no_init(&mut self, hash: &mut App::Hash, hmac: &mut App::HmacHash, input_key_material: &[u8]) { + let mut next_ck = Zeroizing::new([0u8; HASHLEN]); + let mut temp_h = [0u8; HASHLEN]; + + self.kbkdf( + hmac, + input_key_material, + LABEL_KBKDF_CHAIN, + 3, + &mut next_ck, + Some(&mut temp_h), + None, + ); + + *self.ck = *next_ck; + self.mix_hash(hash, &temp_h); + } /// Corresponds to Noise `EncryptAndHash`. #[must_use] pub fn encrypt_and_hash_in_place(&mut self, hash: &mut App::Hash, iv: [u8; AES_GCM_IV_SIZE], data: &mut [u8]) -> [u8; AES_GCM_TAG_SIZE] { diff --git a/src/zeta.rs b/src/zeta.rs index 159df8b..9203426 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -4,7 +4,7 @@ use std::cmp::Reverse; use std::collections::HashMap; use std::num::NonZeroU32; use std::ops::{Deref, DerefMut}; -use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::atomic::{AtomicU64, Ordering, AtomicBool}; use std::sync::{Arc, Mutex, Weak, RwLock}; use zeroize::Zeroizing; @@ -12,19 +12,18 @@ use crate::antireplay::Window; use crate::applicationlayer::ApplicationLayer; use crate::applicationlayer::RatchetUpdate; use crate::challenge::{gen_null_response, respond_to_challenge_in_place}; -use crate::context::ContextInner; +use crate::zssp::{ContextInner, log}; //use crate::context::{log, ContextInner, SessionMap}; -use crate::crypto::aes::{HighThroughputAesGcmPool, LowThroughputAesGcm, AES_GCM_IV_SIZE, AES_256_KEY_SIZE, AES_GCM_TAG_SIZE}; +use crate::crypto::aes::*; use crate::crypto::p384::{P384KeyPair, P384PublicKey, P384_ECDH_SHARED_SECRET_SIZE, P384_PUBLIC_KEY_SIZE}; use crate::crypto::sha512::{HashSha512, HmacSha512}; use crate::crypto::kyber1024::{Kyber1024PrivateKey, KYBER_PUBLIC_KEY_SIZE, KYBER_CIPHERTEXT_SIZE, KYBER_PLAINTEXT_SIZE}; use crate::indexed_heap::BinaryHeapIndex; -//use crate::indexed_heap::BinaryHeapIndex; -//use crate::fragmentation::DefragBuffer; use crate::proto::*; use crate::ratchet_state::RatchetState; use crate::result::{byzantine_fault, FaultType, OpenError, ReceiveError, SendError}; use crate::symmetric_state::SymmetricState; +use crate::fragged::Fragged; #[cfg(feature = "logging")] use crate::LogEvent::*; @@ -50,12 +49,23 @@ pub(crate) fn from_nonce(n: &[u8]) -> (u8, u64) { let c_start = n.len() - 8; (n[c_start - 1], u64::from_be_bytes(n[c_start..].try_into().unwrap())) } -fn create_ratchet_state(hmac: &mut App::HmacHash, noise: &mut SymmetricState, pre_chain_len: u64) -> RatchetState { +fn create_ratchet_state(hmac: &mut App::HmacHash, noise: &SymmetricState, pre_chain_len: u64) -> RatchetState { let mut rk = Zeroizing::new([0u8; HASHLEN]); let mut rf = Zeroizing::new([0u8; HASHLEN]); noise.get_ask(hmac, LABEL_RATCHET_STATE, &mut rk, &mut rf); RatchetState::new(Zeroizing::new(rk[..RATCHET_SIZE].try_into().unwrap()), Zeroizing::new(rf[..RATCHET_SIZE].try_into().unwrap()), pre_chain_len + 1) } +fn get_counter(session: &Session, state: &MutableState) -> Option<(u64, bool)> { + if session.session_has_expired.load(Ordering::Relaxed) { + None + } else { + let c = session.send_counter.fetch_add(1, Ordering::Relaxed); + if c > THREAD_SAFE_COUNTER_HARD_EXPIRE { + session.session_has_expired.store(true, Ordering::SeqCst) + } + Some((c, c > state.key_creation_counter + App::SETTINGS.rekey_after_key_uses)) + } +} /// Corresponds to the Zeta State Machine found in Section 4.1. pub(crate) struct Session { @@ -66,16 +76,19 @@ pub(crate) struct Session { pub was_bob: bool, queue_idx: BinaryHeapIndex, - s_remote: App::PublicKey, + pub(crate) s_remote: App::PublicKey, send_counter: AtomicU64, + session_has_expired: AtomicBool, pub window: Window, - //defrag: [Mutex>; SESSION_MAX_FRAGMENTS_OOO], + pub(crate) defrag: [Mutex>; SESSION_MAX_FRAGMENTS_OOO], + pub(crate) hk_send: App::PrpEnc, + pub(crate) hk_recv: App::PrpDec, state_machine_lock: Mutex<()>, - state: RwLock>, + pub(crate) state: RwLock>, - /// Pre-computed rekeying values. + /// Pre-computed rekeying value. noise_kk_ss: Zeroizing<[u8; P384_ECDH_SHARED_SECRET_SIZE]>, } pub(crate) struct MutableState { @@ -85,7 +98,6 @@ pub(crate) struct MutableState { key_creation_counter: u64, key_index: bool, keys: [DuplexKey; 2], - pub hk_send: Zeroizing<[u8; AES_256_KEY_SIZE]>, resend_timer: i64, timeout_timer: i64, @@ -98,23 +110,40 @@ pub(crate) struct StateB2 { kid_send: NonZeroU32, pub kid_recv: NonZeroU32, pub hk_send: Zeroizing<[u8; AES_256_KEY_SIZE]>, + pub hk_recv: Zeroizing<[u8; AES_256_KEY_SIZE]>, e_secret: App::KeyPair, noise: SymmetricState, - //pub defrag: DefragBuffer, + pub defrag: Mutex>, } -#[derive(Default)] pub(crate) struct DuplexKey { send: Keys, recv: Keys, nk: Option, } +impl Default for DuplexKey { + fn default() -> Self { + Self { send: Default::default(), recv: Default::default(), nk: None } + } +} +impl DuplexKey { + fn replace_nk(&mut self, nk_send: &[u8; HASHLEN], nk_recv: &[u8; HASHLEN]) { + self.nk = Some(App::AeadPool::new((&nk_send[..AES_256_KEY_SIZE]).try_into().unwrap(), (&nk_recv[..AES_256_KEY_SIZE]).try_into().unwrap())) + } +} #[derive(Default)] pub(crate) struct Keys { kek: Option>, kid: Option, } +impl Keys { + fn replace_kek(&mut self, kek: &[u8; HASHLEN]) { + // We want to give rust the best chance of implementing this in a way that does + // not leak the key on the stack. + self.kek.get_or_insert(Zeroizing::new([0u8; AES_256_KEY_SIZE])).copy_from_slice(&kek[..AES_256_KEY_SIZE]); + } +} /// Corresponds to the tuple of values the Transition Algorithms send to the remote peer in Section 4.3. //#[derive(Clone)] @@ -126,31 +155,29 @@ pub(crate) struct StateA1 { noise: SymmetricState, e_secret: App::KeyPair, e1_secret: App::Kem, - identity: ArrayVec, - kid_send: u32, - nonce: [u8; AES_GCM_IV_SIZE], - packet: ArrayVec, + identity: ArrayVec, + x1: ArrayVec, } /// Corresponds to the ZKE Automata found in Section 4.1 - Definition 2. pub(crate) enum ZetaAutomata { Null, - A1(StateA1), + A1(Box>), A3 { - identity: ArrayVec, + identity: ArrayVec, kid_send: u32, nonce: [u8; AES_GCM_IV_SIZE], - packet: ArrayVec, + x3: ArrayVec, }, S1, S2, R1 { noise: SymmetricState, e_secret: App::KeyPair, - k1: Vec, + k1: ArrayVec, }, R2 { - k2: Vec, + k2: ArrayVec, }, } @@ -205,6 +232,11 @@ impl MutableState { } } +fn set_header(packet: &mut [u8], kid_send: u32, nonce: &[u8; AES_GCM_IV_SIZE]) { + packet[..KID_SIZE].copy_from_slice(&kid_send.to_be_bytes()); + packet[PACKET_NONCE_START..HEADER_SIZE].copy_from_slice(&nonce[NONCE_SIZE_DIFF..]); +} + fn create_a1_state( hash: &mut App::Hash, hmac: &mut App::HmacHash, rng: &Mutex, s_remote: &App::PublicKey, @@ -212,12 +244,13 @@ fn create_a1_state( ratchet_state1: &RatchetState, ratchet_state2: Option<&RatchetState>, identity: &[u8], -) -> Option> { +) -> Option>> { // <- s // ... // -> e, es, e1 let mut noise = SymmetricState::::initialize(PROTOCOL_NAME_NOISE_XK); - let mut x1 = ArrayVec::::new(); + let mut x1 = ArrayVec::::new(); + x1.extend([0u8; HEADER_SIZE]); // Noise process prologue. let kid = kid_recv.get().to_be_bytes(); x1.extend(kid); @@ -245,16 +278,18 @@ fn create_a1_state( let c = u64::from_be_bytes(x1[x1.len() - 8..].try_into().unwrap()); + // Process challenge x1.extend(gen_null_response(rng.lock().unwrap().deref_mut())); - Some(StateA1 { + + set_header(&mut x1, 0, &to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, c)); + + Some(Box::new(StateA1 { noise, e_secret, e1_secret, identity: identity.try_into().unwrap(), - kid_send: 0, - nonce: to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, c), - packet: x1, - }) + x1, + })) } /// Corresponds to Transition Algorithm 1 found in Section 4.3. pub(crate) fn trans_to_a1( @@ -263,7 +298,7 @@ pub(crate) fn trans_to_a1( s_remote: App::PublicKey, session_data: App::SessionData, identity: &[u8], - //send: impl FnOnce(&Packet), + send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Result>, OpenError> { let (ratchet_state1, ratchet_state2) = app .restore_by_identity(&s_remote, &session_data) @@ -276,12 +311,18 @@ pub(crate) fn trans_to_a1( let hash = &mut App::Hash::new(); let hmac = &mut App::HmacHash::new(); let a1 = create_a1_state(hash, hmac, &ctx.rng, &s_remote, kid_recv, &ratchet_state1, ratchet_state2.as_ref(), identity).ok_or(OpenError::InvalidPublicKey)?; - let packet = a1.packet.clone(); + + let mut noise_kk_ss = Zeroizing::new([0u8; P384_ECDH_SHARED_SECRET_SIZE]); + if !ctx.s_secret.agree(&s_remote, &mut noise_kk_ss) { + return Err(OpenError::InvalidPublicKey) + } let mut hk_recv = Zeroizing::new([0u8; HASHLEN]); let mut hk_send = Zeroizing::new([0u8; HASHLEN]); a1.noise.get_ask(hmac, LABEL_HEADER_KEY, &mut hk_recv, &mut hk_send); + let mut x1 = a1.x1.clone(); + let current_time = app.time(); let queue_idx = session_queue.reserve_index(); let mut session = Arc::new(Session { @@ -290,20 +331,23 @@ pub(crate) fn trans_to_a1( queue_idx, s_remote, send_counter: AtomicU64::new(0), + session_has_expired: AtomicBool::new(false), window: Window::new(), state_machine_lock: Mutex::new(()), state: RwLock::new(MutableState { - ratchet_state1, - ratchet_state2, + ratchet_state1: ratchet_state1.clone(), + ratchet_state2: ratchet_state2.clone(), key_creation_counter: 0, key_index: true, keys: [DuplexKey::default(), DuplexKey::default()], - hk_send: Zeroizing::new(hk_send[..AES_256_KEY_SIZE].try_into().unwrap()), resend_timer: current_time + App::SETTINGS.resend_time as i64, timeout_timer: current_time + App::SETTINGS.initial_offer_timeout as i64, beta: ZetaAutomata::A1(a1), }), - noise_kk_ss: Zeroizing::new([0u8; P384_ECDH_SHARED_SECRET_SIZE]), + noise_kk_ss: noise_kk_ss.clone(), + defrag: std::array::from_fn(|_| Mutex::new(Fragged::new())), + hk_send: App::PrpEnc::new((&hk_send[..AES_256_KEY_SIZE]).try_into().unwrap()), + hk_recv: App::PrpDec::new((&hk_recv[..AES_256_KEY_SIZE]).try_into().unwrap()), }); let mut state = session.state.write().unwrap(); state.key_mut(true).recv.kid = Some(kid_recv); @@ -315,37 +359,46 @@ pub(crate) fn trans_to_a1( Reverse(state.next_timer()), ); - //send(&packet); + send(&mut x1, None); 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 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(), -// challenge, -// (&mut x1[response_start..]).try_into().unwrap(), -// ); -// } -//} +pub(crate) fn respond_to_challenge(session: &mut Session, rng: &Mutex, challenge: &[u8; CHALLENGE_SIZE]) { + let mut state = session.state.write().unwrap(); + if let ZetaAutomata::A1(a1) = &mut state.beta { + let response_start = a1.x1.len() - CHALLENGE_SIZE; + respond_to_challenge_in_place::( + rng.lock().unwrap().deref_mut(), + challenge, + (&mut a1.x1[response_start..]).try_into().unwrap(), + ); + } +} /// Corresponds to Transition Algorithm 2 found in Section 4.3. pub(crate) fn received_x1_trans( app: &App, ctx: &ContextInner, + remote_address: &impl std::hash::Hash, n: [u8; AES_GCM_IV_SIZE], - mut x1: Vec, - //send: impl FnOnce(&Packet, &[u8; AES_256_KEY_SIZE]), + x1: &mut [u8], + send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Result<(), ReceiveError> { use FaultType::*; // <- s // ... // -> e, es, e1 // <- e, ee, ekem1, psk - if !(HANDSHAKE_HELLO_MIN_SIZE..=HANDSHAKE_HELLO_MAX_SIZE).contains(&x1.len()) { + if !(HANDSHAKE_HELLO_CHALLENGE_MIN_SIZE..=HANDSHAKE_HELLO_CHALLENGE_MAX_SIZE).contains(&x1.len()) { return Err(byzantine_fault!(InvalidPacket, true)); } + + if let Err(challenge) = ctx.challenge.process_hello::(remote_address, (&x1[x1.len() - CHALLENGE_SIZE..]).try_into().unwrap()) { + /// + + return Err(byzantine_fault!(FailedAuth, false)); + } + if &n[AES_GCM_IV_SIZE - 8..] != &x1[x1.len() - 8..] { return Err(byzantine_fault!(FailedAuth, true)); } @@ -405,7 +458,8 @@ pub(crate) fn received_x1_trans( let mut hk_send = Zeroizing::new([0u8; HASHLEN]); noise.get_ask(hmac, LABEL_HEADER_KEY, &mut hk_recv, &mut hk_send); - let mut x2 = ArrayVec::new(); + let mut x2 = ArrayVec::::new(); + x2.extend([0u8; HEADER_SIZE]); // Process message pattern 2 e token. let e_secret = noise.write_e(hash, hmac, &ctx.rng, &mut x2); // Process message pattern 2 ee token. @@ -435,21 +489,24 @@ pub(crate) fn received_x1_trans( c[7] = x2[i - 1]; let c = u64::from_be_bytes(c); - /// - //ctx.b2_map.lock().unwrap().insert( - // kid_recv, - // StateB2 { - // ratchet_state, - // kid_send, - // kid_recv, - // hk_send: hk_send.clone(), - // e_secret, - // noise, - // defrag: DefragBuffer::new(Some(hk_recv)), - // }, - //); + set_header(&mut x2, kid_send.get(), &to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, c)); - //send(&Packet(kid_send.get(), to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, c), x2), &hk_send); + ctx.unassociated_handshake_states.insert( + kid_recv, + Arc::new(StateB2 { + ratchet_state, + kid_send, + kid_recv, + hk_send: Zeroizing::new(hk_send[..AES_256_KEY_SIZE].try_into().unwrap()), + hk_recv: Zeroizing::new(hk_recv[..AES_256_KEY_SIZE].try_into().unwrap()), + e_secret, + noise, + defrag: Mutex::new(Fragged::new()), + }), + app.time() + ); + + send(&mut x2, Some(&App::PrpEnc::new(&hk_send[..AES_256_KEY_SIZE].try_into().unwrap()))); Ok(()) } /// Corresponds to Transition Algorithm 3 found in Section 4.3. @@ -460,7 +517,7 @@ pub(crate) fn received_x2_trans( kid: NonZeroU32, n: [u8; AES_GCM_IV_SIZE], mut x2: &[u8], - //send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), + send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Result<(), ReceiveError> { use FaultType::*; // <- e, ee, ekem1, psk @@ -481,14 +538,14 @@ pub(crate) fn received_x2_trans( if c >= COUNTER_WINDOW_MAX_SKIP_AHEAD || &n[AES_GCM_IV_SIZE - 3..] != &x2[x2.len() - 3..] { return Err(byzantine_fault!(FailedAuth, true)); } - let result = (|| { - if let ZetaAutomata::A1(StateA1 { noise, e_secret, e1_secret, identity, .. }) = &state.beta { - let mut noise = noise.clone(); + let mut result = (|| { + if let ZetaAutomata::A1(a1) = &state.beta { + let mut noise = a1.noise.clone(); let mut i = 0; // Process message pattern 2 e token. let e_remote = noise.read_e(hash, hmac, &mut i, &x2).ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 ee token. - noise.mix_dh(hmac, e_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise.mix_dh(hmac, &a1.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; @@ -497,7 +554,7 @@ pub(crate) fn received_x2_trans( return Err(byzantine_fault!(FailedAuth, true)); } let mut ekem1_secret = Zeroizing::new([0u8; KYBER_PLAINTEXT_SIZE]); - if !e1_secret.decapsulate((&x2[i..j]).try_into().unwrap(), &mut ekem1_secret) { + if !a1.e1_secret.decapsulate((&x2[i..j]).try_into().unwrap(), &mut ekem1_secret) { return Err(byzantine_fault!(FailedAuth, true)); } noise.mix_key(hmac, ekem1_secret.as_ref()); @@ -514,7 +571,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 mut test_ratchet_key = |ratchet_key| -> Option<(NonZeroU32, SymmetricState)> { let mut noise = noise.clone(); let mut payload = payload.clone(); // Process message pattern 2 psk token. @@ -547,8 +604,9 @@ pub(crate) fn received_x2_trans( } let (kid_send, mut noise) = result.ok_or(byzantine_fault!(FailedAuth, true))?; - let mut x3 = ArrayVec::new(); + let mut x3 = ArrayVec::::new(); + x3.extend([0u8; HEADER_SIZE]); // Process message pattern 3 s token. let i = x3.len(); x3.extend(ctx.s_secret.public_key_bytes()); @@ -557,7 +615,7 @@ pub(crate) fn received_x2_trans( noise.mix_dh(hmac, &ctx.s_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 3 payload. let i = x3.len(); - x3.try_extend_from_slice(identity).unwrap(); + x3.try_extend_from_slice(&a1.identity).unwrap(); x3.extend(noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 0), &mut x3[i..])); let new_ratchet_state = create_ratchet_state(hmac, &mut noise, chain_len); @@ -582,10 +640,10 @@ pub(crate) fn received_x2_trans( return Err(ReceiveError::RatchetIoError(e)); } - let kek_recv = Zeroizing::new([0u8; HASHLEN]); - let kek_send = Zeroizing::new([0u8; HASHLEN]); - let nk_recv = Zeroizing::new([0u8; HASHLEN]); - let nk_send = Zeroizing::new([0u8; HASHLEN]); + let mut kek_recv = Zeroizing::new([0u8; HASHLEN]); + let mut kek_send = Zeroizing::new([0u8; HASHLEN]); + let mut nk_recv = Zeroizing::new([0u8; HASHLEN]); + let mut nk_send = Zeroizing::new([0u8; HASHLEN]); noise.get_ask(hmac, LABEL_KEX_KEY, &mut kek_recv, &mut kek_send); noise.split(hmac, &mut nk_recv, &mut nk_send); let nonce = to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 0); @@ -595,37 +653,37 @@ pub(crate) fn received_x2_trans( let mut state = session.state.write().unwrap(); state.key_mut(true).send.kid = Some(kid_send); - state.key_mut(true).send.kek = Some(Zeroizing::new(kek_send[..AES_256_KEY_SIZE].try_into().unwrap())); - state.key_mut(true).recv.kek = Some(Zeroizing::new(kek_recv[..AES_256_KEY_SIZE].try_into().unwrap())); - state.key_mut(true).nk = Some(App::AeadPool::new((&nk_send[..AES_256_KEY_SIZE]).try_into().unwrap(), (&nk_recv[..AES_256_KEY_SIZE]).try_into().unwrap())); + state.key_mut(true).send.replace_kek(&kek_send); + state.key_mut(true).recv.replace_kek(&kek_recv); + state.key_mut(true).replace_nk(&nk_send, &nk_recv); state.ratchet_state2 = Some(state.ratchet_state1.clone()); state.ratchet_state1 = new_ratchet_state.clone(); let current_time = app.time(); state.key_creation_counter = session.send_counter.load(Ordering::Relaxed); state.resend_timer = current_time + App::SETTINGS.resend_time as i64; state.timeout_timer = current_time + App::SETTINGS.initial_offer_timeout as i64; - state.beta = ZetaAutomata::A3 { identity: identity.clone(), packet: x3, kid_send: kid_send.get(), nonce }; + state.beta = ZetaAutomata::A3 { identity: a1.identity.clone(), x3: x3.clone(), kid_send: kid_send.get(), nonce }; - Ok(()) + Ok(x3) } else { Err(byzantine_fault!(FailedAuth, true)) } })(); - match &result { - Err(ReceiveError::ByzantineFault { .. }) => timeout_trans(state, session, app, ctx, app.time(), send), - Ok(packet) => send(packet, Some(&state.hk_send)), + match &mut result { + Err(ReceiveError::ByzantineFault { .. }) => process_timers(app, ctx, session, app.time(), true, false, send), + Ok(mut packet) => send(&mut packet, Some(&session.hk_send)), _ => {} } result.map(|_| ()) } /// Corresponds to Transition Algorithm 4 found in Section 4.3. pub(crate) fn received_x3_trans( - zeta: StateB2, app: &App, ctx: &Arc>, + zeta: StateB2, kid: NonZeroU32, mut x3: Vec, - send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), + send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Result>, ReceiveError> { use FaultType::*; // -> s, se @@ -635,6 +693,8 @@ pub(crate) fn received_x3_trans( if kid != zeta.kid_recv { return Err(byzantine_fault!(UnknownLocalKeyId, true)); } + let mut hash = &mut App::Hash::new(); + let mut hmac = &mut App::HmacHash::new(); let mut noise = zeta.noise.clone(); let mut i = 0; @@ -642,39 +702,42 @@ pub(crate) fn received_x3_trans( let j = i + P384_PUBLIC_KEY_SIZE; let k = j + AES_GCM_TAG_SIZE; let tag = x3[j..k].try_into().unwrap(); - if !noise.decrypt_and_hash_in_place(to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 1), &mut x3[i..j], tag) { + if !noise.decrypt_and_hash_in_place(hash, 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))?; 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(hmac, &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; let tag = x3[j..k].try_into().unwrap(); - if !noise.decrypt_and_hash_in_place(to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 0), &mut x3[i..j], tag) { + if !noise.decrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 0), &mut x3[i..j], tag) { return Err(byzantine_fault!(FailedAuth, true)); } let identity_start = i; let identity_end = j; - let (kek_send, kek_recv) = noise.get_ask(LABEL_KEX_KEY); - let c = INIT_COUNTER; + let mut kek_recv = Zeroizing::new([0u8; HASHLEN]); + let mut kek_send = Zeroizing::new([0u8; HASHLEN]); + noise.get_ask(hmac, LABEL_KEX_KEY, &mut kek_send, &mut kek_recv); + let c = 0; 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); - 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 // handshake is being dropped, so nonce reuse can't happen. - Packet(zeta.kid_send.get(), n, d) + let mut d = ArrayVec::::new(); + d.extend([0u8; HEADER_SIZE]); + let nonce = to_nonce(PACKET_TYPE_SESSION_REJECTED, c); + d.extend(App::Aead::encrypt_in_place((&kek_send[..AES_256_KEY_SIZE]).try_into().unwrap(), &nonce, &[], &mut [])); + set_header(&mut d, zeta.kid_send.get(), &nonce); + d }; if let Some(session_data) = session_data { let result = app.restore_by_identity(&s_remote, &session_data); @@ -685,15 +748,22 @@ pub(crate) fn received_x3_trans( // TODO: add some kind of warning callback or signal. } else { if !responder_silently_rejects { - send(&create_reject(), Some(&zeta.hk_send)) + send(&mut create_reject(), Some(&App::PrpEnc::new(&zeta.hk_send))) } return Err(byzantine_fault!(FailedAuth, true)); } } - let (rk, rf) = noise.get_ask(LABEL_RATCHET_STATE); + let mut noise_kk_ss = Zeroizing::new([0u8; P384_ECDH_SHARED_SECRET_SIZE]); + if !ctx.s_secret.agree(&s_remote, &mut noise_kk_ss) { + return Err(byzantine_fault!(FailedAuth, true)); + } + let mut nk_recv = Zeroizing::new([0u8; HASHLEN]); + let mut nk_send = Zeroizing::new([0u8; HASHLEN]); + noise.split(hmac, &mut nk_send, &mut nk_recv); + + let new_ratchet_state = create_ratchet_state(hmac, &mut noise, zeta.ratchet_state.chain_len); // 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 result = app.save_ratchet_state( &s_remote, &session_data, @@ -709,77 +779,89 @@ pub(crate) fn received_x3_trans( return Err(ReceiveError::RatchetIoError(e)); } - 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 []); - c1.extend(&tag); + let (session, current_time) = { + let mut session_map = ctx.session_map.write().unwrap(); + 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 + // before Alice was able to respond. It is unlikely but possible. + Occupied(_) => return Err(byzantine_fault!(OutOfSequence, false)), + Vacant(entry) => entry, + }; + let mut session_queue = ctx.session_queue.lock().unwrap(); + let queue_idx = session_queue.reserve_index(); + let current_time = app.time(); + let session = Arc::new(Session { + session_data, + was_bob: true, + s_remote, + send_counter: AtomicU64::new(c + 1), + session_has_expired: AtomicBool::new(false), + state_machine_lock: Mutex::new(()), + state: RwLock::new(MutableState { + ratchet_state1: new_ratchet_state.clone(), + ratchet_state2: None, + key_creation_counter: c + 1, + key_index: false, + keys: [DuplexKey::default(), DuplexKey::default()], + resend_timer: current_time + App::SETTINGS.resend_time as i64, + timeout_timer: current_time + App::SETTINGS.rekey_timeout as i64, + beta: ZetaAutomata::S1, + }), + window: Window::new(), + queue_idx, + noise_kk_ss: noise_kk_ss.clone(), + defrag: std::array::from_fn(|_| Mutex::new(Fragged::new())), + hk_send: App::PrpEnc::new(&zeta.hk_send), + hk_recv: App::PrpDec::new(&zeta.hk_recv), + }); - let (nk1, nk2) = noise.split(); - let keys = DuplexKey { - send: Keys { kek: Some(kek_send), nk: Some(nk1), kid: Some(zeta.kid_send) }, - recv: Keys { kek: Some(kek_recv), nk: Some(nk2), kid: Some(zeta.kid_recv) }, + let mut state = session.state.write().unwrap(); + state.key_mut(false).replace_nk(&nk_send, &nk_recv); + state.key_mut(false).recv.kid = Some(zeta.kid_recv); + state.key_mut(false).recv.replace_kek(&kek_recv); + state.key_mut(false).send.kid = Some(zeta.kid_send); + state.key_mut(false).send.replace_kek(&kek_send); + + session_queue.push_reserved(queue_idx, Arc::downgrade(&session), Reverse(state.next_timer())); + entry.insert(Arc::downgrade(&session)); + (session, current_time) }; - let current_time = app.time(); + process_timers(app, ctx, &session, current_time, false, true, send); - let mut session_map = ctx.session_map.lock().unwrap(); - 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 - // before Alice was able to respond. It is unlikely but possible. - Occupied(_) => return Err(byzantine_fault!(OutOfSequence, false)), - Vacant(entry) => entry, - }; - let session = Arc::new(Session(Mutex::new(Zeta { - ctx: Arc::downgrade(ctx), - session_data, - was_bob: true, - s_remote, - send_counter: INIT_COUNTER + 1, - key_creation_counter: INIT_COUNTER + 1, - key_index: false, - keys: [keys, DuplexKey::default()], - 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, - beta: ZetaAutomata::S1, - counter_antireplay_window: std::array::from_fn(|_| 0), - defrag: zeta.defrag, - }))); - entry.insert(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) } Err(e) => Err(ReceiveError::RatchetIoError(e)), } } else { if !responder_silently_rejects { - send(&create_reject(), Some(&zeta.hk_send)) + //send(&create_reject(), Some(&zeta.hk_send)) } 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, - rng: &Mutex, + ctx: &Arc>, + session: &Arc>, kid: NonZeroU32, - n: [u8; AES_GCM_IV_SIZE], - c1: Vec, - send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), + n: &[u8; AES_GCM_IV_SIZE], + c1: &[u8], + send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Result> { use FaultType::*; if c1.len() != KEY_CONFIRMATION_SIZE { return Err(byzantine_fault!(InvalidPacket, true)); } - let is_other = if Some(kid) == zeta.key_ref(true).recv.kid { + + let kex_lock = session.state_machine_lock.lock().unwrap(); + let mut state = session.state.read().unwrap(); + + let is_other = if Some(kid) == state.key_ref(true).recv.kid { true - } else if Some(kid) == zeta.key_ref(false).recv.kid { + } else if Some(kid) == state.key_ref(false).recv.kid { false } else { // Some key confirmation may have arrived extremely delayed. @@ -787,28 +869,28 @@ 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 = state.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) { + if !App::Aead::decrypt_in_place(specified_key, n, &[], &mut [], tag) { return Err(byzantine_fault!(FailedAuth, true)); } - let (_, c) = from_nonce(&n); - if !zeta.update_counter_window(c) { + let (_, c) = from_nonce(n); + if !session.window.update(c) { return Err(byzantine_fault!(ExpiredCounter, true)); } - let just_establised = is_other && matches!(&zeta.beta, ZetaAutomata::A3 { .. }); + let just_establised = is_other && matches!(&state.beta, ZetaAutomata::A3 { .. }); if is_other { - if let ZetaAutomata::A3 { .. } | ZetaAutomata::R2 { .. } = &zeta.beta { - if zeta.ratchet_state2.is_some() { + if let ZetaAutomata::A3 { .. } | ZetaAutomata::R2 { .. } = &state.beta { + if state.ratchet_state2.is_some() { let result = app.save_ratchet_state( - &zeta.s_remote, - &zeta.session_data, + &session.s_remote, + &session.session_data, RatchetUpdate { - state1: &zeta.ratchet_state1, + state1: &state.ratchet_state1, state2: None, state1_was_just_added: false, - state_deleted1: zeta.ratchet_state2.as_ref(), + state_deleted1: state.ratchet_state2.as_ref(), state_deleted2: None, }, ); @@ -816,127 +898,261 @@ pub(crate) fn received_c1_trans( return Err(ReceiveError::RatchetIoError(e)); } } + drop(state); + let mut state_mut = session.state.write().unwrap(); - zeta.ratchet_state2 = None; - zeta.key_index ^= true; - zeta.timeout_timer = app.time() + state_mut.ratchet_state2 = None; + state_mut.key_index ^= true; + state_mut.timeout_timer = app.time() + App::SETTINGS .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 = ZetaAutomata::S2; + .saturating_sub(ctx.rng.lock().unwrap().next_u64() % App::SETTINGS.rekey_time_max_jitter) as i64; + state_mut.resend_timer = i64::MAX; + state_mut.beta = ZetaAutomata::S2; + drop(state); + state = session.state.read().unwrap(); } } - let mut c2 = Vec::new(); - 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 tag = App::Aead::encrypt_in_place(latest_confirmed_key, n, None, &mut []); - c2.extend(&tag); + let mut c2 = ArrayVec::::new(); + c2.extend([0u8; HEADER_SIZE]); + let (c, should_rekey) = get_counter(session, &state).ok_or(byzantine_fault!(ExpiredCounter, false))?; + let nonce = to_nonce(PACKET_TYPE_ACK, c); + let latest_confirmed_key = state.key_ref(false).send.kek.as_ref().ok_or(byzantine_fault!(OutOfSequence, true))?; + c2.extend(App::Aead::encrypt_in_place(latest_confirmed_key, &nonce, &[], &mut [])); + let kid_send = state.key_ref(false).send.kid.ok_or(byzantine_fault!(OutOfSequence, false))?; + set_header(&mut c2, kid_send.get(), &nonce); - send(&Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, c2), Some(&zeta.hk_send)); + send(&mut c2, Some(&session.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, - rng: &Mutex, + ctx: &Arc>, + session: &Arc>, kid: NonZeroU32, - n: [u8; AES_GCM_IV_SIZE], - c2: Vec, + n: &[u8; AES_GCM_IV_SIZE], + c2: &[u8], ) -> Result<(), ReceiveError> { use FaultType::*; if c2.len() != ACKNOWLEDGEMENT_SIZE { return Err(byzantine_fault!(InvalidPacket, true)); } - if Some(kid) != zeta.key_ref(false).recv.kid { + + let kex_lock = session.state_machine_lock.lock().unwrap(); + let state = session.state.read().unwrap(); + + if Some(kid) != state.key_ref(false).recv.kid { // Some acknowledgement may have arrived extremely delayed. return Err(byzantine_fault!(UnknownLocalKeyId, false)); } - if !matches!(&zeta.beta, ZetaAutomata::S1) { + if !matches!(&state.beta, ZetaAutomata::S1) { // Some acknowledgement may have arrived extremely delayed. return Err(byzantine_fault!(OutOfSequence, false)); } 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(state.key_ref(false).recv.kek.as_ref().unwrap(), n, &[], &mut [], tag) { return Err(byzantine_fault!(FailedAuth, true)); } - let (_, c) = from_nonce(&n); - if !zeta.update_counter_window(c) { + let (_, c) = from_nonce(n); + if !session.window.update(c) { return Err(byzantine_fault!(ExpiredCounter, true)); } + drop(state); + let mut state = session.state.write().unwrap(); - zeta.timeout_timer = app.time() + state.timeout_timer = app.time() + App::SETTINGS .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 = ZetaAutomata::S2; + .saturating_sub(ctx.rng.lock().unwrap().next_u64() % App::SETTINGS.rekey_time_max_jitter) as i64; + state.resend_timer = i64::MAX; + state.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, + app: &App, + ctx: &Arc>, + session: &Arc>, kid: NonZeroU32, - n: [u8; AES_GCM_IV_SIZE], - d: Vec, + n: &[u8; AES_GCM_IV_SIZE], + d: &[u8], ) -> Result<(), ReceiveError> { use FaultType::*; if d.len() != SESSION_REJECTED_SIZE { return Err(byzantine_fault!(InvalidPacket, true)); } - if Some(kid) != zeta.key_ref(true).recv.kid || !matches!(&zeta.beta, ZetaAutomata::A3 { .. }) { + + let kex_lock = session.state_machine_lock.lock().unwrap(); + let state = session.state.read().unwrap(); + + if Some(kid) != state.key_ref(true).recv.kid || !matches!(&state.beta, ZetaAutomata::A3 { .. }) { return Err(byzantine_fault!(OutOfSequence, true)); } 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(state.key_ref(true).recv.kek.as_ref().unwrap(), n, &[], &mut [], tag) { return Err(byzantine_fault!(FailedAuth, true)); } - let (_, c) = from_nonce(&n); - if !zeta.update_counter_window(c) { + let (_, c) = from_nonce(n); + if !session.window.update(c) { return Err(byzantine_fault!(ExpiredCounter, true)); } - - zeta.expire(); + /// + //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>, - ctx: &Arc>, +pub(crate) fn process_timers( app: &App, + ctx: &Arc>, + session: &Arc>, current_time: i64, - send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), + force_timeout: bool, + force_resend: bool, + send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) { - 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 kex_lock = session.state_machine_lock.lock().unwrap(); + let mut state = session.state.read().unwrap(); + if force_timeout || state.timeout_timer <= current_time { + // Corresponds to the timeout timer Transition Algorithm described in Section 4.1 - Definition 3. + match &state.beta { + ZetaAutomata::Null => {} + ZetaAutomata::A1(_) | ZetaAutomata::A3 { .. } => { + let identity = match &state.beta { + ZetaAutomata::A1(a1) => &a1.identity, + ZetaAutomata::A3 { identity, .. } => identity, + _ => unreachable!(), + }; + if matches!(&state.beta, ZetaAutomata::A1(_)) { + log!(app, TimeoutX1(session)); + } else { + log!(app, TimeoutX3(session)); + } + let new_kid_recv = remap(ctx, session, &state); - let (p, mut control_payload) = match &zeta.beta { - ZetaAutomata::Null => return, - ZetaAutomata::A1(StateA1 { packet, .. }) => { - log!(app, ResentX1(session)); - return send(packet, None); + let mut hash = &mut App::Hash::new(); + let mut hmac = &mut App::HmacHash::new(); + if let Some(a1) = create_a1_state( + hash, + hmac, + &ctx.rng, + &session.s_remote, + new_kid_recv, + &state.ratchet_state1, + state.ratchet_state2.as_ref(), + identity, + ) { + let mut hk_recv = Zeroizing::new([0u8; HASHLEN]); + let mut hk_send = Zeroizing::new([0u8; HASHLEN]); + a1.noise.get_ask(hmac, LABEL_HEADER_KEY, &mut hk_recv, &mut hk_send); + let mut x1 = a1.x1.clone(); + + drop(state); + { + let mut state = session.state.write().unwrap(); + session.hk_recv.reset((&hk_recv[..AES_256_KEY_SIZE]).try_into().unwrap()); + session.hk_send.reset((&hk_send[..AES_256_KEY_SIZE]).try_into().unwrap()); + *state.key_mut(true) = DuplexKey::default(); + state.key_mut(true).recv.kid = Some(new_kid_recv); + state.resend_timer = current_time + App::SETTINGS.resend_time as i64; + state.timeout_timer = current_time + App::SETTINGS.initial_offer_timeout as i64; + state.beta = ZetaAutomata::A1(a1); + } + + send(&mut x1, None); + } else { + //zeta.expire(); + } } - ZetaAutomata::A3 { packet, .. } => { + ZetaAutomata::S2 => { + // Corresponds to Transition Algorithm 6 found in Section 4.3. + log!(app, StartedRekeyingSentK1(session)); + let new_kid_recv = remap(ctx, session, &state); + // -> s + // <- s + // ... + // -> psk, e, es, ss + let mut noise = SymmetricState::initialize(PROTOCOL_NAME_NOISE_KK); + let mut hash = &mut App::Hash::new(); + let mut hmac = &mut App::HmacHash::new(); + let mut k1 = ArrayVec::::new(); + k1.extend([0u8; HEADER_SIZE]); + // Noise process prologue. + noise.mix_hash(hash, &ctx.s_secret.public_key_bytes()); + noise.mix_hash(hash, &session.s_remote.to_bytes()); + // Process message pattern 1 psk0 token. + noise.mix_key_and_hash(hash, hmac, state.ratchet_state1.key.as_ref()); + // Process message pattern 1 e token. + let e_secret = noise.write_e(hash, hmac, &ctx.rng, &mut k1); + // Process message pattern 1 es token. + if noise.mix_dh(hmac, &e_secret, &session.s_remote).is_none() { + //zeta.expire(); + return; + } + // Process message pattern 1 ss token. + noise.mix_key(hmac, session.noise_kk_ss.as_ref()); + // Process message pattern 1 payload. + let i = k1.len(); + k1.extend(new_kid_recv.get().to_be_bytes()); + k1.extend(noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_REKEY_INIT, 0), &mut k1[i..])); + + drop(state); + { + let mut state = session.state.write().unwrap(); + state.key_mut(true).recv.kid = Some(new_kid_recv); + state.timeout_timer = current_time + App::SETTINGS.rekey_timeout as i64; + state.resend_timer = current_time + App::SETTINGS.resend_time as i64; + state.beta = ZetaAutomata::R1 { noise, e_secret, k1: k1.clone() }; + } + let state = session.state.read().unwrap(); + + if let Some((c, should_rekey)) = get_counter(session, &state) { + let nonce = to_nonce(PACKET_TYPE_REKEY_INIT, c); + k1.extend(App::Aead::encrypt_in_place(state.key_ref(false).send.kek.as_ref().unwrap(), &nonce, &[], &mut k1)); + set_header(&mut k1, state.key_ref(false).send.kid.unwrap().get(), &nonce); + + send(&mut k1, Some(&session.hk_send)); + } + } + ZetaAutomata::S1 { .. } => { + log!(app, TimeoutKeyConfirm(session)); + //zeta.expire(); + } + ZetaAutomata::R1 { .. } => { + log!(app, TimeoutK1(session)); + //zeta.expire(); + } + ZetaAutomata::R2 { .. } => { + log!(app, TimeoutK2(session)); + //zeta.expire(); + } + } + } else if force_resend || state.resend_timer <= current_time { + // Corresponds to the resend timer rules found in Section 4.1 - Definition 3. + state.resend_timer = current_time + App::SETTINGS.resend_time as i64; + + let (packet_type, mut control_payload) = match &state.beta { + ZetaAutomata::Null => return, + ZetaAutomata::A1(a1) => { + log!(app, ResentX1(session)); + return send(&mut a1.x1.clone(), None); + } + ZetaAutomata::A3 { x3, .. } => { log!(app, ResentX3(session)); - return send(packet, Some(&zeta.hk_send)); + return send(&mut x3.clone(), Some(&session.hk_send)); } ZetaAutomata::S1 => { log!(app, ResentKeyConfirm(session)); - (PACKET_TYPE_KEY_CONFIRM, Vec::new()) + let mut c1 = ArrayVec::new(); + c1.extend([0u8; HEADER_SIZE]); + (PACKET_TYPE_KEY_CONFIRM, c1) } ZetaAutomata::S2 => return, ZetaAutomata::R1 { k1, .. } => { @@ -948,142 +1164,37 @@ 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, should_rekey)) = get_counter(session, &state) { + let nonce = to_nonce(packet_type, c); + let tag = App::Aead::encrypt_in_place(state.key_ref(false).send.kek.as_ref().unwrap(), &nonce, &[], &mut control_payload); + control_payload.extend(tag); + set_header(&mut control_payload, state.key_ref(false).send.kid.unwrap().get(), &nonce); + + send(&mut control_payload, Some(&session.hk_send)); + } } } -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)) { +fn remap(ctx: &Arc>, session: &Arc>, state: &MutableState) -> NonZeroU32 { + let mut session_map = ctx.session_map.write().unwrap(); + let weak = if let Some(Some(weak)) = state.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(), ctx.rng.lock().unwrap().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, - session: &Arc>, - app: &App, - ctx: &Arc>, - current_time: i64, - send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), -) { - match &zeta.beta { - ZetaAutomata::Null => {} - ZetaAutomata::A1(StateA1 { identity, .. }) | ZetaAutomata::A3 { identity, .. } => { - if matches!(&zeta.beta, ZetaAutomata::A1(_)) { - log!(app, TimeoutX1(session)); - } else { - log!(app, TimeoutX3(session)); - } - 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(), - ) { - let (hk_recv, hk_send) = a1.noise.get_ask(LABEL_HEADER_KEY); - let packet = a1.packet.clone(); - - 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.beta = ZetaAutomata::A1(a1); - zeta.defrag = DefragBuffer::new(Some(hk_recv)); - - send(&packet, None); - } else { - zeta.expire(); - } - } - 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 - // <- s - // ... - // -> psk, e, es, ss - let mut k1 = Vec::new(); - let mut noise = SymmetricState::initialize(PROTOCOL_NAME_NOISE_KK); - // Noise process prologue. - 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()); - // Process message pattern 1 e token. - let e_secret = noise.write_e(&ctx.rng, &mut k1); - // Process message pattern 1 es token. - if noise.mix_dh(&e_secret, &zeta.s_remote).is_none() { - zeta.expire(); - return; - } - // Process message pattern 1 ss token. - if noise.mix_dh(&ctx.s_secret, &zeta.s_remote).is_none() { - zeta.expire(); - return; - } - // 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); - - 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 = 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); - - send(&Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, k1), Some(&zeta.hk_send)); - } - ZetaAutomata::S1 { .. } => { - log!(app, TimeoutKeyConfirm(session)); - zeta.expire(); - } - ZetaAutomata::R1 { .. } => { - log!(app, TimeoutK1(session)); - zeta.expire(); - } - 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>, app: &App, - rng: &Mutex, - session_map: &SessionMap, + ctx: &Arc>, + session: &Arc>, s_secret: &App::KeyPair, kid: NonZeroU32, - n: [u8; AES_GCM_IV_SIZE], - mut k1: Vec, - send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), + n: &[u8; AES_GCM_IV_SIZE], + k1: &mut [u8], + send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Result<(), ReceiveError> { use FaultType::*; // -> s @@ -1094,13 +1205,17 @@ pub(crate) fn received_k1_trans( if k1.len() != REKEY_SIZE { return Err(byzantine_fault!(InvalidPacket, true)); } - if Some(kid) != zeta.key_ref(false).recv.kid { + + let kex_lock = session.state_machine_lock.lock().unwrap(); + let state = session.state.read().unwrap(); + + if Some(kid) != state.key_ref(false).recv.kid { // Some rekey packet may have arrived extremely delayed. return Err(byzantine_fault!(UnknownLocalKeyId, false)); } - let should_rekey_as_bob = match &zeta.beta { + let should_rekey_as_bob = match &state.beta { ZetaAutomata::S2 { .. } => true, - ZetaAutomata::R1 { .. } => zeta.was_bob, + ZetaAutomata::R1 { .. } => session.was_bob, _ => false, }; if !should_rekey_as_bob { @@ -1110,285 +1225,229 @@ 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(state.key_ref(false).recv.kek.as_ref().unwrap(), n, &[], &mut k1[..i], tag) { return Err(byzantine_fault!(FailedAuth, true)); } - let (_, c) = from_nonce(&n); - if !zeta.update_counter_window(c) { + let (_, c) = from_nonce(n); + if !session.window.update(c) { return Err(byzantine_fault!(ExpiredCounter, true)); } - k1.truncate(i); let result = (|| { let mut i = 0; let mut noise = SymmetricState::::initialize(PROTOCOL_NAME_NOISE_KK); + let mut hash = &mut App::Hash::new(); + let mut hmac = &mut App::HmacHash::new(); // Noise process prologue. - noise.mix_hash(&zeta.s_remote.to_bytes()); - noise.mix_hash(&s_secret.public_key_bytes()); + noise.mix_hash(hash, &session.s_remote.to_bytes()); + noise.mix_hash(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(hash, hmac, state.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))?; + let e_remote = noise.read_e(hash, hmac, &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(hmac, 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_key(hmac, session.noise_kk_ss.as_ref()); // Process message pattern 1 payload. let j = i + KID_SIZE; let k = j + AES_GCM_TAG_SIZE; let tag = k1[j..k].try_into().unwrap(); - if !noise.decrypt_and_hash_in_place(to_nonce(PACKET_TYPE_REKEY_INIT, 0), &mut k1[i..j], tag) { + if !noise.decrypt_and_hash_in_place(hash, 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 mut k2 = Vec::new(); + let mut k2 = ArrayVec::::new(); + k2.extend([0u8; HEADER_SIZE]); // Process message pattern 2 e token. - let e_secret = noise.write_e(rng, &mut k2); + let e_secret = noise.write_e(hash, hmac, &ctx.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(hmac, &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(hmac, &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); - k2.extend(&new_kid_recv.get().to_be_bytes()); - noise.encrypt_and_hash_in_place(to_nonce(PACKET_TYPE_REKEY_COMPLETE, 0), i, &mut k2); + let new_kid_recv = remap(ctx, session, &state); + k2.extend(new_kid_recv.get().to_be_bytes()); + k2.extend(noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_REKEY_COMPLETE, 0), &mut k2[i..])); - 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 = create_ratchet_state(hmac, &noise, state.ratchet_state1.chain_len); let result = app.save_ratchet_state( - &zeta.s_remote, - &zeta.session_data, + &session.s_remote, + &session.session_data, RatchetUpdate { state1: &new_ratchet_state, - state2: Some(&zeta.ratchet_state1), + state2: Some(&state.ratchet_state1), state1_was_just_added: true, - state_deleted1: zeta.ratchet_state2.as_ref(), + state_deleted1: state.ratchet_state2.as_ref(), state_deleted2: None, }, ); if let Err(e) = result { return Err(ReceiveError::RatchetIoError(e)); } - let (kek_send, kek_recv) = noise.get_ask(LABEL_KEX_KEY); - let (nk_send, nk_recv) = noise.split(); - zeta.key_mut(true).send.kid = Some(kid_send); - zeta.key_mut(true).send.kek = Some(kek_send); - zeta.key_mut(true).send.nk = Some(nk_send); - 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_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; - zeta.resend_timer = current_time + App::SETTINGS.resend_time as i64; - zeta.beta = ZetaAutomata::R2 { k2: k2.clone() }; + let mut kek_recv = Zeroizing::new([0u8; HASHLEN]); + let mut kek_send = Zeroizing::new([0u8; HASHLEN]); + let mut nk_recv = Zeroizing::new([0u8; HASHLEN]); + let mut nk_send = Zeroizing::new([0u8; HASHLEN]); + noise.get_ask(hmac, LABEL_KEX_KEY, &mut kek_send, &mut kek_recv); + noise.split(hmac, &mut nk_send, &mut nk_recv); - let c = zeta.send_counter; - zeta.send_counter += 1; - 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); + drop(state); + { + let mut state = session.state.write().unwrap(); + state.key_mut(true).replace_nk(&nk_send, &nk_recv); + state.key_mut(true).send.kid = Some(kid_send); + state.key_mut(true).send.replace_kek(&kek_send); + state.key_mut(true).recv.kid = Some(new_kid_recv); + state.key_mut(true).recv.replace_kek(&kek_recv); + state.ratchet_state2 = Some(state.ratchet_state1.clone()); + state.ratchet_state1 = new_ratchet_state.clone(); + let current_time = app.time(); + state.key_creation_counter = session.send_counter.load(Ordering::Relaxed); + state.timeout_timer = current_time + App::SETTINGS.rekey_timeout as i64; + state.resend_timer = current_time + App::SETTINGS.resend_time as i64; + state.beta = ZetaAutomata::R2 { k2: k2.clone() }; + } + let mut state = session.state.read().unwrap(); - send(&Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, k2), Some(&zeta.hk_send)); + /// + let (c, should_rekey) = get_counter(session, &state).ok_or(byzantine_fault!(ExpiredCounter, false))?; + let nonce = to_nonce(PACKET_TYPE_REKEY_COMPLETE, c); + k2.extend(App::Aead::encrypt_in_place(state.key_ref(false).send.kek.as_ref().unwrap(), &nonce, &[], &mut k2)); + set_header(&mut k2, state.key_ref(false).send.kid.unwrap().get(), &nonce); + + send(&mut k2, Some(&session.hk_send)); Ok(()) })(); + /// if matches!(result, Err(ReceiveError::ByzantineFault { .. })) { - zeta.expire(); + //zeta.expire(); } result } /// Corresponds to Transition Algorithm 8 found in Section 4.3. pub(crate) fn received_k2_trans( - zeta: &mut Zeta, app: &App, + ctx: &Arc>, + session: &Arc>, kid: NonZeroU32, - n: [u8; AES_GCM_IV_SIZE], - mut k2: Vec, - send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), + n: &[u8; AES_GCM_IV_SIZE], + mut k2: &mut [u8], + send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Result<(), ReceiveError> { use FaultType::*; // <- e, ee, se if k2.len() != REKEY_SIZE { return Err(byzantine_fault!(InvalidPacket, true)); } - if Some(kid) != zeta.key_ref(false).recv.kid { + + let kex_lock = session.state_machine_lock.lock().unwrap(); + let state = session.state.read().unwrap(); + + if Some(kid) != state.key_ref(false).recv.kid { // Some rekey packet may have arrived extremely delayed. return Err(byzantine_fault!(UnknownLocalKeyId, false)); } - if !matches!(&zeta.beta, ZetaAutomata::R1 { .. }) { + if !matches!(&state.beta, ZetaAutomata::R1 { .. }) { // Some rekey packet may have arrived extremely delayed. return Err(byzantine_fault!(OutOfSequence, false)); } 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(state.key_ref(false).recv.kek.as_ref().unwrap(), n, &[], &mut k2[..i], tag) { return Err(byzantine_fault!(FailedAuth, true)); } - let (_, c) = from_nonce(&n); - if !zeta.update_counter_window(c) { + let (_, c) = from_nonce(n); + if !session.window.update(c) { return Err(byzantine_fault!(ExpiredCounter, true)); } - k2.truncate(i); let result = (|| { - if let ZetaAutomata::R1 { noise, e_secret, .. } = &zeta.beta { + if let ZetaAutomata::R1 { noise, e_secret, .. } = &state.beta { let mut noise = noise.clone(); let mut i = 0; + let mut hash = &mut App::Hash::new(); + let mut hmac = &mut App::HmacHash::new(); // Process message pattern 2 e token. - let e_remote = noise.read_e(&mut i, &k2).ok_or(byzantine_fault!(FailedAuth, true))?; + let e_remote = noise.read_e(hash, hmac, &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(hmac, 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(hmac, e_secret, &session.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; let tag = k2[j..k].try_into().unwrap(); - if !noise.decrypt_and_hash_in_place(to_nonce(PACKET_TYPE_REKEY_COMPLETE, 0), &mut k2[i..j], tag) { + if !noise.decrypt_and_hash_in_place(hash, 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 (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 = create_ratchet_state(hmac, &noise, state.ratchet_state1.chain_len); let result = app.save_ratchet_state( - &zeta.s_remote, - &zeta.session_data, + &session.s_remote, + &session.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(), + state_deleted1: Some(&state.ratchet_state1), + state_deleted2: state.ratchet_state2.as_ref(), }, ); if let Err(e) = result { return Err(ReceiveError::RatchetIoError(e)); } - let (kek_recv, kek_send) = noise.get_ask(LABEL_KEX_KEY); - let (nk_recv, nk_send) = noise.split(); + let mut kek_recv = Zeroizing::new([0u8; HASHLEN]); + let mut kek_send = Zeroizing::new([0u8; HASHLEN]); + let mut nk_recv = Zeroizing::new([0u8; HASHLEN]); + let mut nk_send = Zeroizing::new([0u8; HASHLEN]); + noise.get_ask(hmac, LABEL_KEX_KEY, &mut kek_recv, &mut kek_send); + noise.split(hmac, &mut nk_recv, &mut nk_send); - zeta.key_mut(true).send.kid = Some(kid_send); - zeta.key_mut(true).send.kek = Some(kek_send); - 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_state1 = new_ratchet_state; - 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.beta = ZetaAutomata::S1; + drop(state); + let current_time = { + let mut state = session.state.write().unwrap(); + state.key_mut(true).replace_nk(&nk_send, &nk_recv); + state.key_mut(true).send.kid = Some(kid_send); + state.key_mut(true).send.replace_kek(&kek_send); + state.key_mut(true).recv.replace_kek(&kek_recv); + state.ratchet_state1 = new_ratchet_state.clone(); + state.key_index ^= true; + let current_time = app.time(); + state.key_creation_counter = session.send_counter.load(Ordering::Relaxed); + state.timeout_timer = current_time + App::SETTINGS.rekey_timeout as i64; + state.resend_timer = current_time + App::SETTINGS.resend_time as i64; + state.beta = ZetaAutomata::S1; + current_time + }; + process_timers(app, ctx, session, current_time, false, true, send); - let mut c1 = Vec::new(); - let c = zeta.send_counter; - zeta.send_counter += 1; - 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)); Ok(()) } else { unreachable!() } })(); if matches!(result, Err(ReceiveError::ByzantineFault { .. })) { - zeta.expire(); + //zeta.expire(); } result } -/// Corresponds to Algorithm 9 found in Section 4.3. -pub(crate) fn send_payload( - zeta: &mut Zeta, - mut payload: Vec, - send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), -) -> Result<(), SendError> { - use SendError::*; - if matches!(&zeta.beta, ZetaAutomata::Null) { - return Err(SessionExpired); - } - if !matches!( - &zeta.beta, - ZetaAutomata::S1 | ZetaAutomata::S2 | ZetaAutomata::R1 { .. } | ZetaAutomata::R2 { .. } - ) { - 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 { - // Cause timeout to occur next service interval. - zeta.timeout_timer = i64::MIN; - } - } +//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. +// pub fn expire(&mut self) { +// self.0.lock().unwrap().expire(); +// } +//} - 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( - zeta: &mut Zeta, - kid: NonZeroU32, - n: [u8; AES_GCM_IV_SIZE], - payload: &mut Vec, -) -> Result<(), ReceiveError> { - use FaultType::*; - - if payload.len() < AES_GCM_TAG_SIZE { - return Err(byzantine_fault!(FailedAuth, true)); - } - let is_other = if Some(kid) == zeta.key_ref(true).recv.kid { - true - } else if Some(kid) == zeta.key_ref(false).recv.kid { - false - } else { - // A packet would have to be delayed by around an hour for this error to occur, but it can - // occur naturally due to just out-of-order transport. - return Err(byzantine_fault!(OutOfSequence, false)); - }; - - 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 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)); - } - let (_, c) = from_nonce(&n); - if !zeta.update_counter_window(c) { - // This error is marked as not happening naturally, but it could occur if something about - // the transport protocol is duplicating packets. - return Err(byzantine_fault!(ExpiredCounter, true)); - } - payload.truncate(i); - - Ok(()) -} - -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. - pub fn expire(&mut self) { - self.0.lock().unwrap().expire(); - } -} - -impl Drop for Session { - fn drop(&mut self) { - self.expire(); - } -} +//impl Drop for Session { +// fn drop(&mut self) { +// self.expire(); +// } +//} diff --git a/src/zssp.rs b/src/zssp.rs index 2288a43..ef49568 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -19,14 +19,15 @@ use std::sync::{Arc, Mutex, MutexGuard, RwLock, Weak}; use arrayvec::ArrayVec; use zeroize::Zeroizing; +use crate::zeta::*; use crate::challenge::ChallengeContext; -use crate::crypto::aes::{AesDec, AesEnc, AES_256_KEY_SIZE}; +use crate::crypto::aes::{AesDec, AesEnc, AES_256_KEY_SIZE, AES_GCM_TAG_SIZE, AES_GCM_IV_SIZE}; use crate::crypto::p384::{P384KeyPair, P384PublicKey, P384_ECDH_SHARED_SECRET_SIZE, P384_PUBLIC_KEY_SIZE}; use crate::crypto::pqc_kyber::KYBER_SECRETKEYBYTES; use crate::crypto::rand_core::RngCore; use crate::crypto::sha512::{HmacSha512, HashSha512}; -use crate::result::{FaultType, OpenError, ReceiveError, SendError}; +use crate::result::{FaultType, OpenError, ReceiveError, SendError, ReceiveOk, byzantine_fault, SessionEvent}; use crate::frag_cache::UnassociatedFragCache; use crate::fragged::{Assembled, Fragged}; use crate::handshake_cache::UnassociatedHandshakeCache; @@ -36,6 +37,15 @@ use crate::proto::*; use crate::symmetric_state::SymmetricState; use crate::{applicationlayer::*, RatchetState}; +/// Macro to turn off logging at compile time. +macro_rules! log { + ($app:expr, $event:expr) => { + #[cfg(feature = "logging")] + $app.event_log($event); + }; +} +pub(crate) use log; + /// Session context for local application. /// /// Each application using ZSSP must create an instance of this to own sessions and @@ -48,60 +58,19 @@ impl Clone for Context { Self(self.0.clone()) } } -pub struct ContextInner { - static_keypair: Application::KeyPair, - unassociated_defrag_cache: Mutex>, - unassociated_handshake_states: UnassociatedHandshakeCache, - /// `session_queue -> state_machine_lock -> state -> session_map` - session_queue: Mutex>, Reverse>>, - session_map: RwLock>, bool)>>, - challenge: ChallengeContext, - rng: Mutex, -} -/// Result generated by the context packet receive function, with possible payloads. -pub enum ReceiveResult<'b, Application: ApplicationLayer> { - /// 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<'b>), - /// Packet was a part of a handshake, and while it superficially appeared valid the application - /// explicitly rejected it. - /// Relates to callbacks `check_allow_incoming_session`, `hello_requires_recognized_ratchet` - /// and `check_accept_session`. - Rejected, -} +pub(crate) type SessionMap = RwLock>>>; +pub(crate) struct ContextInner { + pub rng: Mutex, + pub(crate) s_secret: App::KeyPair, + pub(crate) session_queue: Mutex>, Reverse>>, + pub(crate) session_map: SessionMap, + pub(crate) unassociated_defrag_cache: Mutex>, + pub(crate) unassociated_handshake_states: UnassociatedHandshakeCache, + //pub(crate) b2_map: Mutex>>, -#[derive(Debug, PartialEq, Eq)] -pub enum SessionEvent<'b> { - /// The received packet was valid, and it contained the necessary keys to fully establish a new - /// session with Alice, the handshake initiator. - /// - /// 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, - /// 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. - /// - /// Alice will receive this return value when the received packet confirms both parties - /// have completed the initial handshake and now have a shared session with each other. - /// If according to the upper protocol, Bob is the first party to send data, it is possible for - /// Alice to start receiving data from Bob before this value is returned. - /// - /// This return value can only occur once per session, only for session objects that were - /// created with `Context::open`. - Established, - /// Bob explicitly refused to establish a session with Alice, and sent us an error code. - /// The application should immediately drop this session as Bob will not allow us to connect. - /// - /// This return value cannot occur after a session is fully established. - Rejected, - /// The received packet was valid and a data payload was decoded and authenticated. - Data(&'b mut [u8]), - /// The received packet was some authentic protocol control packet. No action needs to be taken. - Control, + //hello_defrag: Mutex, + pub(crate) challenge: ChallengeContext, } #[derive(Debug, PartialEq, Eq)] @@ -111,333 +80,33 @@ pub enum IncomingSessionAction { Drop, } -/// ZeroTier Secure Session Protocol (ZSSP) Session -/// -/// A FIPS/NIST compliant variant of Noise_XK with hybrid Kyber1024 PQ data forward secrecy. -pub struct Session { - /// An arbitrary application defined object associated with each session. - pub application_data: App::SessionData, - /// Is true if the local peer acted as Bob, the responder in the initial key exchange. - pub was_bob: bool, - /// The receive context associated with this session, - /// only this context can receive messages from the remote peer. - context: Weak>, - /// Handle into the session queue for changing the update timer. - queue_idx: BinaryHeapIndex, - - remote_static_key: App::PublicKey, - send_counter: AtomicU64, - /// This bool signals to all threads to stop incrementing the counter and instead error out. - session_has_expired: AtomicBool, - /// The following is a ring buffer of previously seen counter values, where we use the counter's - /// value as the index of the head of the ring buffer. - counter_antireplay_window: [AtomicU64; COUNTER_WINDOW_MAX_OOO], - /// Enforces atomicity of state machine transitions. - /// There is a standard locking sequence, - /// it goes `session_queue -> state_machine_lock -> state -> session_map`. - /// Any lock can be skipped but they must be locked in that order. - state_machine_lock: Mutex<()>, - state: RwLock>, - defrag: [Mutex>; SESSION_MAX_FRAGMENTS_OOO], - header_send_cipher: App::PrpEnc, - header_receive_cipher: App::PrpDec, - kex_send_cipher: Mutex>, - kex_receive_cipher: Mutex>, - /// Pre-computed rekeying values. - noise_kk_ss: Zeroizing<[u8; P384_ECDH_SHARED_SECRET_SIZE]>, - noise_kk_local_init_h: [u8; HASHLEN], - noise_kk_remote_init_h: [u8; HASHLEN], -} -/// `AesGcm` is not threadsafe, but it is threadsafe when inside a `Mutex`. -unsafe impl Send for Session {} -unsafe impl Sync for Session {} - -/// Session state may only be mutated during atomic transitions of the offer state machine. -struct SessionMutableState { - ratchet_states: [RatchetState; 2], - /// For OOO rekeying reliability we allow our version of Noise CipherState to hold the last two - /// session keys, instead of just the most recent one. - cipher_states: [Option>; 2], - /// This is the index of `noise_cipher_state` that contains the most recent key. - /// It will be attached to fragment headers to help with OOO transport. - current_key: usize, - resent_timer: AtomicI64, - timeout_timer: i64, - /// This defines the exact state of the offer state machine we are in. - outgoing_offer: OfferStateMachine, +fn parse_fragment_header(incoming_fragment: &[u8]) -> Result<(usize, usize, [u8; AES_GCM_IV_SIZE]), ReceiveError> { + let fragment_no = incoming_fragment[FRAGMENT_NO_IDX] as usize; + let fragment_count = incoming_fragment[FRAGMENT_COUNT_IDX] as usize; + if fragment_no >= fragment_count || fragment_count > MAX_FRAGMENTS { + return Err(byzantine_fault!(FaultType::InvalidPacket, true)); + } + let mut nonce = [0u8; AES_GCM_IV_SIZE]; + nonce[2..].copy_from_slice(&incoming_fragment[PACKET_NONCE_START..HEADER_SIZE]); + Ok((fragment_no, fragment_count, nonce)) } -/// These offer enums form a state machine. -/// Documented below are the only legal transitions for this state machine. -/// A session is initialized with an `outgoing_offer` of either NoiseXKPattern1 or Normal. -enum OfferStateMachine { - Normal, // -> NoiseKKPattern1, NoiseKKPattern2 - /// This state uses a lot of memory so we put it on the heap. - NoiseXKPattern1or3(Box>), // -> Normal - NoiseKKPattern1 { - new_key_id: NonZeroU32, - noise_e_secret: App::KeyPair, - noise_message: ArrayVec, - noise_ck: SymmetricState, - }, // -> NoiseKKPattern2, KeyConfirm - NoiseKKPattern2 { - noise_message: ArrayVec, - kex_send_key: Zeroizing<[u8; AES_256_KEY_SIZE]>, - }, // -> Normal - KeyConfirm, // -> Normal - Null, -} -pub(crate) struct NoiseXKBobHandshakeState { - /// Can never be Null. - ratchet_state: RatchetState, - remote_key_id: NonZeroU32, - local_key_id: NonZeroU32, - header_receive_key: Zeroizing<[u8; AES_256_KEY_SIZE]>, - header_send_key: Zeroizing<[u8; AES_256_KEY_SIZE]>, - noise_e_secret: App::KeyPair, - noise_ck_eseeekem1psk: SymmetricState, - noise_pattern3_defrag: Mutex>, -} - -struct NoiseXKAliceHandshake { - /// A secure random number put in the header of Alice's fragments to identify them. - /// If a DDOS attacker could guess this they could block Alice starting the handshake. - local_key_id: NonZeroU32, - alice_identity_blob: App::LocalIdentityBlob, - offer: NoiseXKAliceHandshakeState, -} - -enum NoiseXKAliceHandshakeState { - NoiseXKPattern1 { - noise_h_ee1p: [u8; HASHLEN], - noise_e_secret: App::KeyPair, - noise_e1_secret: App::Kem, - noise_ck_es: SymmetricState, - /// ZSSP assumes an unreliable, out-of-order physical transport environment, so for that - /// reason we have to resend key offers. - noise_message: ArrayVec, - message_id: u64, - }, - NoiseXKPattern3 { - noise_message: ArrayVec, - }, -} - -struct SessionKey { - remote_key_id: NonZeroU32, - local_key_id: NonZeroU32, - /// Pool of reusable sending ciphers. - receive_cipher_pool: [Mutex; 8], - /// Pool of reusable receiving ciphers. - send_cipher_pool: [Mutex; 8], - /// Rekey at or after this counter. - rekey_at_counter: u64, - /// Hard error when this counter value is reached or exceeded. - expire_at_counter: u64, -} - -macro_rules! byzantine_fault { - ($name:expr, $is_natural:ident) => { - ReceiveError::ByzantineFault { - file: file!(), - line: line!(), - error: $name, - is_naturally_occurring: $is_natural, - } - }; -} - -impl Context { +impl Context { /// Create a new session context. - pub fn new(static_keypair: Application::KeyPair, mut rng: Application::Rng) -> Self { - debug_assert!(Application::REKEY_AFTER_TIME_MAX_JITTER_MS > 0, "Invalid protocol constant"); - let mut challenge_salt = [0u8; CHALLENGE_SALT_SIZE]; - rng.fill_bytes(&mut challenge_salt); + pub fn new(static_secret_key: App::KeyPair, mut rng: App::Rng) -> Self { + let challenge = ChallengeContext::new(&mut rng); Self(Arc::new(ContextInner { - static_keypair, + rng: Mutex::new(rng), + s_secret: static_secret_key, + session_map: RwLock::new(HashMap::new()), + challenge, + session_queue: Mutex::new(IndexedBinaryHeap::new()), unassociated_defrag_cache: Mutex::new(UnassociatedFragCache::new()), unassociated_handshake_states: UnassociatedHandshakeCache::new(), - session_map: RwLock::new(HashMap::new()), - session_queue: Mutex::new(IndexedBinaryHeap::new()), - challenge_counter: AtomicU64::new(INIT_COUNTER), - challenge_antireplay_window: std::array::from_fn(|_| AtomicU64::new(0)), - challenge_salt, - rng: Mutex::new(rng), })) } - /// Perform periodic background service and cleanup tasks. - /// - /// 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 second or two are not - /// a problem. - /// - /// * `send_to` - Function to get a sender and an MTU to send something over an active session - /// * `current_time` - 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 - /// should rekey. - pub fn service bool>( - &self, - app: &Application, - mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, - current_time: i64, - ) -> i64 { - let retry_next = current_time.saturating_add(Application::RETRY_INTERVAL_MS); - let mut next_service_time = 2 * Application::RETRY_INTERVAL_MS; - - let mut session_queue = self.0.session_queue.lock().unwrap(); - // This update system takes heavy advantage of the fact that sessions only need to be updated - // either roughly every second or roughly every hour. That big gap allows for minor optimizations. - // If the gap changes (unlikely) this code may need to be rewritten. - while let Some((session, timer, queue_idx)) = session_queue.peek() { - if timer.0 >= current_time { - next_service_time = next_service_time.min(timer.0 - current_time); - break; - } - let session = match session.upgrade() { - Some(s) => s, - _ => { - session_queue.remove(queue_idx); - continue; - } - }; - let state = session.state.read().unwrap(); - use OfferStateMachine::*; - let next_timer = match &state.outgoing_offer { - Normal { timeout, .. } => { - if *timeout <= current_time { - drop(state); - if let Some((send, _)) = send_to(&session) { - let result = initiate_rekey(&self.0, &session, send, current_time); - if result.is_ok() { - app.event_log(LogEvent::ServiceKKStart(&session), current_time); - } - result.unwrap_or(retry_next) - } else { - retry_next - } - } else { - *timeout - } - } - // If there's an outstanding attempt to open a session, retransmit this - // periodically in case the initial packet doesn't make it. - NoiseXKPattern1or3(handshake_state) => { - if let Some(ts) = process_timer(&handshake_state.next_retry_time, Application::RETRY_INTERVAL_MS, current_time) { - ts - } else { - // We have to eventually time out NoiseXKPattern3 because of unreliable network conditions. - if handshake_state.timeout <= current_time { - drop(state); - let _kex_lock = session.state_machine_lock.lock().unwrap(); - let mut state = session.state.write().unwrap(); - let ratchet_state = state.ratchet_states.clone(); - // Since we dropped the lock we must re-check if we are in the correct state. - if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { - if handshake_state.timeout <= current_time { - app.event_log(LogEvent::ServiceXKTimeout(&session), current_time); - handshake_state.reinitialize( - &session, - &ratchet_state, - &mut self.0.session_map.write().unwrap(), - &mut self.0.rng.lock().unwrap(), - current_time, - ); - } - } - } else if let Some((mut send, mut mtu)) = send_to(&session) { - mtu = mtu.max(MIN_TRANSPORT_MTU); - match &handshake_state.offer { - NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_message, noise_message_len, message_id, .. } => { - app.event_log(LogEvent::ServiceXK1Resend(&session), current_time); - // We are in state NoiseXKPattern1 so resend noise_pattern1. - send_with_fragmentation( - &mut send, - mtu, - &mut noise_message.clone()[..*noise_message_len], - PACKET_TYPE_NOISE_XK_PATTERN_1, - None, - *message_id, - None::<&Application::PrpEnc>, - ); - } - NoiseXKAliceHandshakeState::NoiseXKPattern3 { noise_message, noise_message_len, .. } => { - app.event_log(LogEvent::ServiceXK3Resend(&session), current_time); - send_with_fragmentation( - &mut send, - mtu, - &mut noise_message.clone()[..*noise_message_len], - PACKET_TYPE_NOISE_XK_PATTERN_3, - state.cipher_states[0].as_ref().map(|k| k.remote_key_id), - 0, - Some(&session.header_send_cipher), - ); - } - } - } - retry_next - } - } - NoiseKKPattern1 { next_retry_time, timeout, noise_message, .. } | NoiseKKPattern2 { next_retry_time, timeout, noise_message, .. } => { - if let Some(ts) = process_timer(next_retry_time, Application::RETRY_INTERVAL_MS, current_time) { - ts - } else { - if *timeout <= current_time { - app.event_log(LogEvent::ServiceKKTimeout(&session), current_time); - next_retry_time.store(i64::MAX, Ordering::Relaxed); - drop(state); - session.expire_inner(&self.0, &mut session_queue); - } else { - let packet_type = if let NoiseKKPattern1 { .. } = &state.outgoing_offer { - app.event_log(LogEvent::ServiceKK1Resend(&session), current_time); - PACKET_TYPE_NOISE_KK_PATTERN_1 - } else { - app.event_log(LogEvent::ServiceKK2Resend(&session), current_time); - PACKET_TYPE_NOISE_KK_PATTERN_2 - }; - if let Some((send, _)) = send_to(&session) { - let _ = session.send_control(&state, send, packet_type, noise_message); - } - } - retry_next - } - } - KeyConfirm { next_retry_time, timeout, .. } => { - if let Some(ts) = process_timer(next_retry_time, Application::RETRY_INTERVAL_MS, current_time) { - ts - } else { - if *timeout <= current_time { - app.event_log(LogEvent::ServiceKeyConfirmTimeout(&session), current_time); - next_retry_time.store(i64::MAX, Ordering::Relaxed); - drop(state); - session.expire_inner(&self.0, &mut session_queue); - } else { - app.event_log(LogEvent::ServiceKeyConfirmResend(&session), current_time); - if let Some((send, _)) = send_to(&session) { - let _ = session.send_control(&state, send, PACKET_TYPE_KEY_CONFIRM, &[]); - } - } - retry_next - } - } - Null => retry_next, - }; - session_queue.change_priority(queue_idx, Reverse(next_timer)); - } - drop(session_queue); - - self.0 - .unassociated_defrag_cache - .lock() - .unwrap() - .check_for_expiry(Application::INITIAL_OFFER_TIMEOUT_MS, current_time); - self.0.unassociated_handshake_states.service(current_time); - - next_service_time - } - /// 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 @@ -454,103 +123,27 @@ impl Context { /// peer, or None if we do not have one. /// * `local_identity_blob` - Payload to be sent to Bob that contains the information necessary /// for the upper protocol to authenticate and approve of Alice's identity. - /// * `current_time` - Current time in milliseconds. Does not have to be monotonic, nor synced - /// with the remote peer. Used to determine when this offer should be resent. pub fn open( &self, - app: &Application, + app: App, mut send: impl FnMut(&mut [u8]) -> bool, mut mtu: usize, - remote_static_key: Application::PublicKey, - application_data: Application::SessionData, - local_identity_blob: Application::LocalIdentityBlob, - current_time: i64, - ) -> Result>, OpenError> { + static_remote_key: App::PublicKey, + session_data: App::SessionData, + identity: &[u8], + ) -> Result>, OpenError> { mtu = mtu.max(MIN_TRANSPORT_MTU); - if local_identity_blob.as_ref().len() > MAX_IDENTITY_BLOB_SIZE { - return Err(OpenError::DataTooLarge); - } - let result = app.restore_by_identity(&remote_static_key, &application_data, current_time); - match result { - Ok(ratchet_states) => { - let sha512 = &mut Application::Hash::new(); - - let mut noise_kk_ss = Secret::new(); - if !self.0.static_keypair.agree(&remote_static_key, noise_kk_ss.as_mut()) { - return Err(OpenError::InvalidPublicKey); - } - let noise_kk_local_init_h = mix_hash(sha512, &INITIAL_H_REKEY, self.0.static_keypair.public_key_bytes()); - let noise_kk_local_init_h = mix_hash(sha512, &noise_kk_local_init_h, remote_static_key.as_bytes()); - let noise_kk_remote_init_h = mix_hash(sha512, &INITIAL_H_REKEY, remote_static_key.as_bytes()); - let noise_kk_remote_init_h = mix_hash(sha512, &noise_kk_remote_init_h, self.0.static_keypair.public_key_bytes()); - - let mut session_queue = self.0.session_queue.lock().unwrap(); - let mut session_map = self.0.session_map.write().unwrap(); - let local_key_id = generate_key_id(&session_map, &mut self.0.rng.lock().unwrap()); - // Begin Noise XKhfs+psk2. - let (offer, a2b_header_key, b2a_header_key) = NoiseXKAliceHandshake::::initialize( - local_key_id, - &remote_static_key, - &ratchet_states, - &mut self.0.rng.lock().unwrap(), - )?; - let handshake_state = Box::new(NoiseXKAliceHandshake { - next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), - timeout: current_time.saturating_add(Application::INITIAL_OFFER_TIMEOUT_MS), - local_key_id, - alice_identity_blob: local_identity_blob, - offer, - }); - if let NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_message, noise_message_len, message_id, .. } = &handshake_state.offer { - send_with_fragmentation( - &mut send, - mtu, - &mut noise_message.clone()[..*noise_message_len], - PACKET_TYPE_NOISE_XK_PATTERN_1, - None, - *message_id, - None::<&Application::PrpEnc>, - ); - } - - let queue_idx = session_queue.reserve_index(); - let session = Arc::new(Session { - context: Arc::downgrade(&self.0), - queue_idx, - application_data, - remote_static_key, - send_counter: AtomicU64::new(INIT_COUNTER), - session_has_expired: AtomicBool::new(false), - counter_antireplay_window: std::array::from_fn(|_| AtomicU64::new(0)), - state_machine_lock: Mutex::new(()), - state: RwLock::new(SessionMutableState { - ratchet_states: ratchet_states.clone(), - cipher_states: [None, None], - // Points at 1 until the first key is confirmed. - current_key: 1, - outgoing_offer: OfferStateMachine::NoiseXKPattern1or3(handshake_state), - }), - header_send_cipher: Application::PrpEnc::new(a2b_header_key.as_ref()), - header_receive_cipher: Application::PrpDec::new(b2a_header_key.as_ref()), - kex_receive_cipher: Mutex::new(None), - kex_send_cipher: Mutex::new(None), - noise_kk_ss: noise_kk_ss.clone(), - noise_kk_local_init_h, - noise_kk_remote_init_h, - defrag: std::array::from_fn(|_| Mutex::new(Fragged::new())), - was_bob: false, - }); - session_map.insert(local_key_id, (Arc::downgrade(&session), false)); - session_queue.push_reserved( - queue_idx, - Arc::downgrade(&session), - Reverse(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), - ); - - Ok(session) - } - Err(e) => Err(OpenError::RatchetIoError(e)), + if identity.len() > IDENTITY_MAX_SIZE { + return Err(OpenError::IdentityTooLarge); } + // Process zeta layer. + trans_to_a1( + app, + &self.0, + static_remote_key, + session_data, + identity, + ) } /// Receive, authenticate, decrypt, and process a physical wire packet. @@ -562,7 +155,7 @@ impl Context { /// The check_accept_session function is called at the end of negotiation for an incoming /// session with the caller's static public blob. It must return the P-384 static public key /// extracted from the supplied blob and application data. A return of Some() accepts the - /// session and will always result in a new session ReceiveResult being returned. + /// session and will always result in a new session ReceiveOk being returned. /// /// * `app` - Interface to application using ZSSP /// * `check_allow_incoming_session` - Function to call to check whether an unidentified new @@ -586,21 +179,22 @@ impl Context { /// to put in-flight. pub fn receive<'a, SendFn: FnMut(&mut [u8]) -> bool>( &self, - app: &Application, + app: &App, check_allow_incoming_session: impl FnOnce() -> IncomingSessionAction, - check_accept_session: impl FnOnce(&Application::PublicKey, &[u8], u64) -> (Option<(bool, Application::SessionData)>, bool), + check_accept_session: impl FnOnce(&App::PublicKey, &[u8], u64) -> (Option<(bool, App::SessionData)>, bool), mut send_unassociated_reply: impl FnMut(&mut [u8]) -> bool, mut send_unassociated_mtu: usize, - mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, + mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, remote_address: &impl Hash, data_buf: &'a mut [u8], - mut incoming_physical_packet_buf: Application::IncomingPacketBuffer, + mut incoming_fragment_buf: App::IncomingPacketBuffer, current_time: i64, - ) -> Result, ReceiveError> { + ) -> Result, ReceiveError> { + use crate::result::FaultType::*; + let ctx = &self.0; send_unassociated_mtu = send_unassociated_mtu.max(MIN_TRANSPORT_MTU); - let incoming_physical_packet: &mut [u8] = incoming_physical_packet_buf.as_mut(); - let incoming_physical_packet_len = incoming_physical_packet.len(); - if incoming_physical_packet_len < MIN_PACKET_SIZE { + let incoming_fragment: &mut [u8] = incoming_fragment_buf.as_mut(); + if incoming_fragment.len() < MIN_PACKET_SIZE { return Err(byzantine_fault!(FaultType::InvalidPacket, false)); } @@ -611,80 +205,66 @@ impl Context { let mut assembled_packet = Assembled::new(); // needs to outlive the block below let mut incoming = None; let (session, packet_type, fragments) = { - let local_key_id = incoming_physical_packet[0..SESSION_ID_SIZE].try_into().unwrap(); + let kid_recv = incoming_fragment[0..KID_SIZE].try_into().unwrap(); // `from_ne_bytes` because this id was generated locally. - if let Some(local_key_id) = NonZeroU32::new(u32::from_ne_bytes(local_key_id)) { + if let Some(kid_recv) = NonZeroU32::new(u32::from_ne_bytes(kid_recv)) { let session_map = self.0.session_map.read().unwrap(); - if let Some((Some(session), key_index)) = session_map.get(&local_key_id).map(|r| (r.0.upgrade(), r.1 as usize)) { + let session = ctx.session_map.read().unwrap().get(&kid_recv).map(|r| r.upgrade()); + if let Some(Some(session)) = session { drop(session_map); - session.header_receive_cipher.decrypt_in_place( - (&mut incoming_physical_packet[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]) + session.hk_recv.decrypt_in_place( + (&mut incoming_fragment[HEADER_AUTH_START..HEADER_AUTH_END]) .try_into() .unwrap(), ); - let (fragment_count, fragment_no, packet_type, incoming_counter, header_nonce) = parse_packet_header(incoming_physical_packet); - // Handle replay protection. - if PACKET_TYPE_RANGE_TRANSPORT.contains(&packet_type) { - // For DOS resistant reply-protection we need to check that the given counter is - // in the window of valid counters immediately. - // But for packets larger than 1 fragment we can't actually record the - // counter as received until we've authenticated the packet. - // So we check the counter window twice, and only update it the second time - // after the packet has been authenticated. - if !session.check_receive_window(incoming_counter) { - // This can occur naturally if packets arrive way out of order, or - // if they are duplicates. - // This can also be naturally triggered if Bob has just successfully - // received the first session key and is reject all of Alice's resends. - // This can also occur if a session was manually expired, but not - // dropped, and the remote party is still sending us data. - return Err(byzantine_fault!(FaultType::ExpiredCounter, true)); - } + + let (fragment_no, fragment_count, nonce) = parse_fragment_header(incoming_fragment)?; + let (packet_type, incoming_counter) = from_nonce(&nonce); + + {//vrfy if packet_type != PACKET_TYPE_DATA { - // This is a control packet. - if fragment_count != 1 || fragment_no > 0 { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + log!(app, ReceivedRawFragment(p, c, fragment_no, fragment_count)); + } + if packet_type == PACKET_TYPE_HANDSHAKE_RESPONSE { + if !matches!(&session.state.read().unwrap().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)); } - return receive_control_fragment( - self, - session, - app, - send_to, - packet_type, - incoming_counter, - incoming_physical_packet_buf.as_mut(), - current_time, - ); + if incoming_counter >= COUNTER_WINDOW_MAX_SKIP_AHEAD { + return Err(byzantine_fault!(ExpiredCounter, true)); + } + } else if PACKET_TYPE_USES_COUNTER_RANGE.contains(&packet_type) { + // For DOS resistant reply-protection we need to check that the given counter is + // in the window of valid counters immediately. + // But for packets larger than 1 fragment we can't actually record the + // counter as received until we've authenticated the packet. + // So we check the counter window twice, and only update it the second time + // after the packet has been authenticated. + if !session.window.check(incoming_counter) { + // This can occur naturally if packets arrive way out of order, or + // if they are duplicates. + // This can also be naturally triggered if Bob has just successfully + // received the first session key and is reject all of Alice's resends. + // This can also occur if a session was manually expired, but not + // dropped, and the remote party is still sending us data. + return Err(byzantine_fault!(ExpiredCounter, false)); + } + } else if packet_type == PACKET_TYPE_HANDSHAKE_COMPLETION { + // This can be triggered if Bob successfully received a session key and + // needs to reject all of Alice's resends of PACKET_TYPE_NOISE_XK_PATTERN_3. + return Err(byzantine_fault!(InvalidPacket, false)); + } else { + return Err(byzantine_fault!(InvalidPacket, true)); } - } else if packet_type == PACKET_TYPE_NOISE_XK_PATTERN_2 { - // We need to reject fragments marked with this type if they are sent out - // of sequence, since an attacker is able to replay them. - match &session.state.read().unwrap().outgoing_offer { - OfferStateMachine::NoiseXKPattern1or3(handshake_state) => match &handshake_state.offer { - NoiseXKAliceHandshakeState::NoiseXKPattern1 { .. } => { - if incoming_counter >= COUNTER_WINDOW_MAX_SKIP_AHEAD { - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - } - // This error can occur naturally if Bob's initial reply to Alice had a - // resend that was delayed massively and arrived out of order. - _ => return Err(byzantine_fault!(FaultType::OutOfSequence, true)), - }, - _ => return Err(byzantine_fault!(FaultType::OutOfSequence, false)), - }; - } else if packet_type == PACKET_TYPE_NOISE_XK_PATTERN_3 { - // This can be triggered if Bob successfully received a session key and - // needs to reject all of Alice's resends of PACKET_TYPE_NOISE_XK_PATTERN_3. - return Err(byzantine_fault!(FaultType::OutOfSequence, true)); - } else { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); } + // Handle defragmentation. let fragments = if fragment_count > 1 { let idx = incoming_counter as usize % session.defrag.len(); session.defrag[idx].lock().unwrap().assemble( - header_nonce, - incoming_physical_packet_buf, + &nonce, + incoming_fragment_buf, fragment_no, fragment_count, &mut assembled_packet, @@ -692,96 +272,100 @@ impl Context { if assembled_packet.is_empty() { // We have not yet authenticated the sender so we do not report // receiving a packet from them. - return Ok(ReceiveResult::Unassociated); + return Ok(ReceiveOk::Unassociated); } else { assembled_packet.as_ref() } } else { - std::array::from_ref(&incoming_physical_packet_buf) + std::slice::from_ref(&incoming_fragment_buf) }; - // Handle DATA in the fastest path when we have a session. - if packet_type == PACKET_TYPE_DATA { - let state = session.state.read().unwrap(); - // The error here can occur because the other party is using a brand new - // session key that we have not received yet. - let key = state.cipher_states[key_index] - .as_ref() - .ok_or(byzantine_fault!(FaultType::OutOfSequence, true))?; - let mut c = key.get_receive_cipher(incoming_counter); - c.set_iv(&create_message_nonce(packet_type, incoming_counter)); - let mut data_len = 0; + match packet_type { + PACKET_TYPE_DATA => { + let state = session.state.read().unwrap(); + // The error here can occur because the other party is using a brand new + // session key that we have not received yet. + let key = state.cipher_states[key_index] + .as_ref() + .ok_or(byzantine_fault!(FaultType::OutOfSequence, true))?; + let mut c = key.get_receive_cipher(incoming_counter); + c.set_iv(&create_message_nonce(packet_type, incoming_counter)); - // Decrypt fragments 0..N-1 where N is the number of fragments. - for f in fragments[..(fragments.len() - 1)].iter() { - let f: &[u8] = f.as_ref(); - debug_assert!(f.len() >= HEADER_SIZE); + let mut data_len = 0; + + // Decrypt fragments 0..N-1 where N is the number of fragments. + for f in fragments[..(fragments.len() - 1)].iter() { + let f: &[u8] = f.as_ref(); + debug_assert!(f.len() >= HEADER_SIZE); + let current_frag_data_start = data_len; + data_len += f.len() - HEADER_SIZE; + if data_len > data_buf.len() { + return Err(ReceiveError::DataBufferTooSmall); + } + c.decrypt(&f[HEADER_SIZE..], &mut data_buf[current_frag_data_start..data_len]); + } + + // Decrypt final fragment (or only fragment if not fragmented) let current_frag_data_start = data_len; - data_len += f.len() - HEADER_SIZE; + let last_fragment = fragments.last().unwrap().as_ref(); + if last_fragment.len() < (HEADER_SIZE + AES_GCM_TAG_SIZE) { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + data_len += last_fragment.len() - (HEADER_SIZE + AES_GCM_TAG_SIZE); if data_len > data_buf.len() { return Err(ReceiveError::DataBufferTooSmall); } - c.decrypt(&f[HEADER_SIZE..], &mut data_buf[current_frag_data_start..data_len]); - } + let payload_end = last_fragment.len() - AES_GCM_TAG_SIZE; + c.decrypt(&last_fragment[HEADER_SIZE..payload_end], &mut data_buf[current_frag_data_start..data_len]); - // Decrypt final fragment (or only fragment if not fragmented) - let current_frag_data_start = data_len; - let last_fragment = fragments.last().unwrap().as_ref(); - if last_fragment.len() < (HEADER_SIZE + AES_GCM_TAG_SIZE) { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - data_len += last_fragment.len() - (HEADER_SIZE + AES_GCM_TAG_SIZE); - if data_len > data_buf.len() { - return Err(ReceiveError::DataBufferTooSmall); - } - let payload_end = last_fragment.len() - AES_GCM_TAG_SIZE; - c.decrypt(&last_fragment[HEADER_SIZE..payload_end], &mut data_buf[current_frag_data_start..data_len]); + let aead_authentication_ok = c.finish_decrypt(&last_fragment[payload_end..].try_into().unwrap()); + drop(c); + drop(state); - let aead_authentication_ok = c.finish_decrypt(&last_fragment[payload_end..].try_into().unwrap()); - drop(c); - drop(state); - - if !aead_authentication_ok { - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + if !aead_authentication_ok { + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } + if !session.update_receive_window(incoming_counter) { + // This can be naturally triggered because Bob has just + // successfully received a session key and needs to reject + // all of Alice's resends. + // This can also occur naturally if some part of the outer + // system is duplicating the packets being sent to us. + // We are safely deduplicating them here. + return Err(byzantine_fault!(FaultType::ExpiredCounter, true)); + } + // Packet fully authenticated + return Ok(ReceiveOk::Session(session, SessionEvent::Data(&mut data_buf[..data_len]))); } - if !session.update_receive_window(incoming_counter) { - // This can be naturally triggered because Bob has just - // successfully received a session key and needs to reject - // all of Alice's resends. - // This can also occur naturally if some part of the outer - // system is duplicating the packets being sent to us. - // We are safely deduplicating them here. - return Err(byzantine_fault!(FaultType::ExpiredCounter, true)); + PACKET_TYPE_HANDSHAKE_RESPONSE => { + (Some(session), packet_type, fragments) } - // Packet fully authenticated - return Ok(ReceiveResult::Session(session, SessionEvent::Data(&mut data_buf[..data_len]))); - } else if packet_type == PACKET_TYPE_NOISE_XK_PATTERN_2 { - (Some(session), packet_type, fragments) - } else { - unreachable!() } } else { drop(session_map); // Check for and handle PACKET_TYPE_ALICE_NOISE_XK_PATTERN_3 - incoming = self.0.unassociated_handshake_states.get(local_key_id); + incoming = self.0.unassociated_handshake_states.get(kid_recv); if let Some(incoming) = incoming.as_ref() { - Application::PrpDec::new(incoming.header_receive_key.as_ref()).decrypt_in_place( - (&mut incoming_physical_packet[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]) + App::PrpDec::new(&incoming.hk_recv).decrypt_in_place( + (&mut incoming_fragment[HEADER_AUTH_START..HEADER_AUTH_END]) .try_into() .unwrap(), ); - let (fragment_count, fragment_no, packet_type, _, header_nonce) = parse_packet_header(incoming_physical_packet); - app.event_log( - LogEvent::ReceiveUnassociatedFragment(fragment_count, fragment_no, packet_type), - current_time, - ); - if packet_type != PACKET_TYPE_NOISE_XK_PATTERN_3 { - return Err(byzantine_fault!(FaultType::OutOfSequence, false)); + + let (fragment_no, fragment_count, nonce) = parse_fragment_header(incoming_fragment)?; + let (packet_type, incoming_counter) = from_nonce(&nonce); + + {//vrfy + log!(app, ReceivedRawFragment(packet_type, incoming_counter, frag_no, frag_count)); + if packet_type != PACKET_TYPE_HANDSHAKE_COMPLETION || incoming_counter != 0 { + return Err(byzantine_fault!(InvalidPacket, true)) + } } + let fragments = if fragment_count > 1 { - incoming.noise_pattern3_defrag.lock().unwrap().assemble( - header_nonce, - incoming_physical_packet_buf, + incoming.defrag.lock().unwrap().assemble( + &nonce, + incoming_fragment_buf, fragment_no, fragment_count, &mut assembled_packet, @@ -789,850 +373,59 @@ impl Context { if !assembled_packet.is_empty() { assembled_packet.as_ref() } else { - return Ok(ReceiveResult::Unassociated); + return Ok(ReceiveOk::Unassociated); } } else { - std::array::from_ref(&incoming_physical_packet_buf) + std::slice::from_ref(&incoming_fragment_buf) }; // We must guarantee that this incoming handshake is processed once and only // once. This prevents catastrophic nonce reuse caused by multithreading. - if self.0.unassociated_handshake_states.remove(local_key_id) { - (None, PACKET_TYPE_NOISE_XK_PATTERN_3, fragments) + if self.0.unassociated_handshake_states.remove(kid_recv) { + (None, PACKET_TYPE_HANDSHAKE_COMPLETION, fragments) } else { - return Ok(ReceiveResult::Unassociated); + return Ok(ReceiveOk::Unassociated); } } else { // This can occur naturally because either Bob's incoming_sessions cache got // full so Alice's incoming session was dropped, or the session this packet // was for was dropped by the application. - return Err(byzantine_fault!(FaultType::UnknownLocalKeyId, true)); + return Err(byzantine_fault!(UnknownLocalKeyId, true)); } } } else { - let (fragment_count, fragment_no, packet_type, _, header_nonce) = parse_packet_header(incoming_physical_packet); - app.event_log( - LogEvent::ReceiveUnassociatedFragment(fragment_count, fragment_no, packet_type), - current_time, - ); - if packet_type != PACKET_TYPE_NOISE_XK_PATTERN_1 && packet_type != PACKET_TYPE_BOB_DOS_CHALLENGE { - return Err(byzantine_fault!(FaultType::OutOfSequence, false)); + let (fragment_no, fragment_count, nonce) = parse_fragment_header(incoming_fragment)?; + let (packet_type, incoming_counter) = from_nonce(&nonce); + + {//vrfy + log!(app, ReceivedRawFragment(packet_type, incoming_counter, frag_no, frag_count)); + if packet_type != PACKET_TYPE_HANDSHAKE_HELLO && packet_type != PACKET_TYPE_CHALLENGE { + return Err(byzantine_fault!(InvalidPacket, true)) + } } + let fragments = if fragment_count > 1 { self.0.unassociated_defrag_cache.lock().unwrap().assemble( - header_nonce, + &nonce, remote_address, - incoming_physical_packet_len - HEADER_SIZE, - incoming_physical_packet_buf, + incoming_fragment.len() - HEADER_SIZE, + incoming_fragment_buf, fragment_no, fragment_count, - Application::RETRY_INTERVAL_MS, + App::SETTINGS.resend_time as i64, current_time, &mut assembled_packet, ); if !assembled_packet.is_empty() { assembled_packet.as_ref() } else { - return Ok(ReceiveResult::Unassociated); + return Ok(ReceiveOk::Unassociated); } } else { - std::array::from_ref(&incoming_physical_packet_buf) + std::array::from_ref(&incoming_fragment_buf) }; (None, packet_type, fragments) } }; - - debug_assert!(!fragments.is_empty()); - debug_assert!(incoming.is_none() || session.is_none()); - - let message = &mut [0u8; MAX_NOISE_HANDSHAKE_SIZE]; - let message_size = assemble_fragments_into::(fragments, message)?; - if message_size < MIN_PACKET_SIZE { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - - use OfferStateMachine::*; - match packet_type { - PACKET_TYPE_NOISE_XK_PATTERN_1 => { - // Alice (remote) --> Bob (local) - // -> e, es, e1 - app.event_log(LogEvent::ReceiveUncheckedXK1, current_time); - - if session.is_some() || incoming.is_some() { - return Err(byzantine_fault!(FaultType::OutOfSequence, false)); - } - if !(NoiseXKPattern1::MIN_SIZE..=NoiseXKPattern1::MAX_SIZE).contains(&message_size) { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - // The message id must be the first 8 bytes of the gcm tag. - // This forces the message id to be authenticated along with the entire message. - let p_auth_end = message_size - ChallengeResponse::SIZE; - if message[8..16] != message[p_auth_end - 8..p_auth_end] { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - let p_size = p_auth_end - NoiseXKPattern1::P_ENC_START - AES_GCM_TAG_SIZE; - let total_ratchet_fingerprints = p_size / RATCHET_SIZE; - if p_size % RATCHET_SIZE != 0 || total_ratchet_fingerprints > 2 { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - - let noise_pattern1: &NoiseXKPattern1 = byte_array_as_proto_buffer(message); - if let Some(remote_key_id) = NonZeroU32::new(u32::from_ne_bytes(noise_pattern1.alice_key_id)) { - let sha512 = &mut Application::Hash::new(); - // Let application filter incoming connection attempts by whatever criteria it wants. - // This should ideally prevent ZSSP from wasting time on DDOS attacks. - match check_allow_incoming_session() { - IncomingSessionAction::Allow => {} - IncomingSessionAction::Challenge => { - let response: &ChallengeResponse = byte_array_as_proto_buffer(&message[p_auth_end..message_size]); - let counter = u64::from_be_bytes(response.challenge_counter.try_into().unwrap()); - - sha512.reset(); - let mut hasher = ShaHasher(sha512); - let mut output = [0u8; HASHLEN]; - hasher.0.update(&response.challenge_counter); - remote_address.hash(&mut hasher); - hasher.0.update(&self.0.challenge_salt); - hasher.0.finish(&mut output); - let is_valid = self.check_challenge_window(counter) - && secure_eq(&output[..CHALLENGE_MAC_SIZE], &response.challenge_mac) - && verify_pow::(hasher.0, &message[p_auth_end..message_size]) - && self.update_challenge_window(counter); - app.event_log(LogEvent::ReceiveCheckXK1Challenge(is_valid), current_time); - if !is_valid { - // Alice failed the challenge so issue them a new challenge. - let mut challenge_buffer = [0u8; BobDOSChallenge::SIZE]; - let challenge: &mut BobDOSChallenge = byte_array_as_proto_buffer_mut(&mut challenge_buffer); - challenge.alice_key_id = remote_key_id.get().to_ne_bytes(); - // We attach a monotonically increasing counter value to the challenge - // so it cannot be replayed. - let counter = self.0.challenge_counter.fetch_add(1, Ordering::Relaxed); - challenge.challenge_counter = counter.to_be_bytes(); - - hasher.0.reset(); - hasher.0.update(&counter.to_be_bytes()); - remote_address.hash(&mut hasher); - hasher.0.update(&self.0.challenge_salt); - hasher.0.finish(&mut output); - challenge.challenge_mac.copy_from_slice(&output[..CHALLENGE_MAC_SIZE]); - challenge.prior_challenge_pow = response.challenge_pow; - // We haven't decrypted any of Alice's packet so we don't know the - // header protection cipher. - // For DOS resistance Alice will not accept unencrypted headers directly - // into their session defrag buffer, so we have to send them this reply - // through their incoming sessions cache. - send_with_fragmentation( - &mut send_unassociated_reply, - send_unassociated_mtu, - &mut challenge_buffer, - PACKET_TYPE_BOB_DOS_CHALLENGE, - None, - self.0.rng.lock().unwrap().next_u64(), - None::<&Application::PrpEnc>, - ); - return Ok(ReceiveResult::Unassociated); - } - // Alice succeeded at the challenge so continue to decryption. - } - IncomingSessionAction::Drop => return Ok(ReceiveResult::Rejected), - } - - // Noise process handshake prologue. - let noise_h = mix_hash( - sha512, - &INITIAL_H, - &message[NoiseXKPattern1::PROLOGUE_START..NoiseXKPattern1::PROLOGUE_END], - ); - let noise_h = mix_hash(sha512, &noise_h, self.0.static_keypair.public_key_bytes()); - // Noise process pattern1 e token. - let mut noise_ck = SymmetricState::new(INITIAL_H); - let hmac = &mut Application::HmacHash::new(); - let mut noise_es = Secret::new(); - let noise_e_pattern1 = from_bytes_agreement::(&noise_pattern1.noise_e, &self.0.static_keypair, noise_es.as_mut()) - .ok_or(byzantine_fault!(FaultType::FailedAuthentication, false))?; - let noise_h_e = mix_hash(sha512, &noise_h, &noise_pattern1.noise_e); - noise_ck.mix_key(hmac, &noise_pattern1.noise_e); - // Noise process pattern1 es token. - let noise_k_es = noise_ck.mix_key_initialize_key(hmac, noise_es.as_ref()); - drop(noise_es); - // Noise process pattern1 e1 token. - let (is_auth, noise_h_ee1) = decrypt_and_hash::( - sha512, - &noise_k_es, - &noise_h_e, - packet_type, - 0, - &mut message[NoiseXKPattern1::E1_ENC_START..NoiseXKPattern1::P_ENC_START], - ); - if !is_auth { - // This could occur naturally if Alice's ApplicationLayer is dynamically - // changing their mtu, which in bad network conditions could clobber their - // resent KEX packet. - // Or maybe Alice randomly generated the same temporary id twice in a row. - // Since these situations are super unlikely to occur we still mark this error - // as unnatural. - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - // Noise process pattern1 payload. - let (is_auth, noise_h_ee1p) = decrypt_and_hash::( - sha512, - &noise_k_es, - &noise_h_ee1, - packet_type, - 1, - &mut message[NoiseXKPattern1::P_ENC_START..p_auth_end], - ); - drop(noise_k_es); - if !is_auth { - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - let (header_b2a_key, header_a2b_key) = noise_ck.get_ask2(hmac, LABEL_HEADER_KEY, &noise_h_ee1p); - // Get ratchet key. - let noise_pattern1: &NoiseXKPattern1 = byte_array_as_proto_buffer(message); - let mut ratchet_state = RatchetState::Null; - for i in 0..total_ratchet_fingerprints { - match app.restore_by_fingerprint( - (&noise_pattern1.payload[i * RATCHET_SIZE..(i + 1) * RATCHET_SIZE]).try_into().unwrap(), - current_time, - ) { - Ok(RatchetState::Null) | Ok(RatchetState::Empty) => {} - Ok(rs) => { - ratchet_state = rs; - break; - } - Err(e) => return Err(ReceiveError::RatchetIoError(e)), - } - } - if ratchet_state.is_null() { - if app.hello_requires_recognized_ratchet(current_time) { - return Ok(ReceiveResult::Rejected); - } - ratchet_state = RatchetState::Empty; - } - - // Start of Noise XKhfs+psk2 pattern2. - let mut message2 = [0u8; NoiseXKPattern2::SIZE]; - let noise_pattern2: &mut NoiseXKPattern2 = byte_array_as_proto_buffer_mut(&mut message2); - // Noise process pattern2 e token. - let noise_e_pattern2_secret = Application::KeyPair::generate(&mut self.0.rng.lock().unwrap()); - noise_pattern2.noise_e = *noise_e_pattern2_secret.public_key_bytes(); - let noise_h_ee1pe = mix_hash(sha512, &noise_h_ee1p, &noise_pattern2.noise_e); - noise_ck.mix_key(hmac, &noise_pattern2.noise_e); - // Noise process pattern2 ee token. - let mut noise_ee = Secret::new(); - if !noise_e_pattern2_secret.agree(&noise_e_pattern1, noise_ee.as_mut()) { - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - let noise_k_esee = noise_ck.mix_key_initialize_key(hmac, noise_ee.as_ref()); - drop(noise_ee); - // Noise process pattern2 ekem1 token. - let (noise_ekem1, noise_ekem1_secret) = pqc_kyber::encapsulate(&noise_pattern1.noise_e1, self.0.rng.lock().unwrap().deref_mut()) - .map_err(|_| byzantine_fault!(FaultType::FailedAuthentication, false)) - .map(|(ct, ekem1)| (ct, Secret(ekem1)))?; - // Alice fully authenticated. - noise_pattern2.noise_ekem1 = noise_ekem1; - let noise_h_ee1peekem1 = encrypt_and_hash::( - sha512, - &noise_k_esee, - &noise_h_ee1pe, - PACKET_TYPE_NOISE_XK_PATTERN_2, - 0, - &mut message2[NoiseXKPattern2::EKEM1_ENC_START..NoiseXKPattern2::P_ENC_START], - ); - drop(noise_k_esee); - noise_ck.mix_key(hmac, noise_ekem1_secret.as_ref()); - drop(noise_ekem1_secret); - // Noise process pattern2 psk token. - let ratchet_key = ratchet_state.key().unwrap(); - let (temp_h, noise_k_eseeekem1psk) = noise_ck.mix_key_and_hash_initialize_key(hmac, ratchet_key); - let noise_h_ee1peekem1psk = mix_hash(sha512, &noise_h_ee1peekem1, &temp_h); - // Noise process pattern2 payload. - // We try to prevent the id we generate from colliding with another session but - // because we might have handshakes in flight it's impossible to 100% prevent. - // In those exceedingly rare cases we have to drop Alice's session and start over. - let local_key_id = generate_key_id(&self.0.session_map.read().unwrap(), &mut self.0.rng.lock().unwrap()); - let noise_pattern2: &mut NoiseXKPattern2 = byte_array_as_proto_buffer_mut(&mut message2); - noise_pattern2.bob_key_id = local_key_id.get().to_ne_bytes(); - - let noise_h_ee1peekem1pskp = encrypt_and_hash::( - sha512, - &noise_k_eseeekem1psk, - &noise_h_ee1peekem1psk, - PACKET_TYPE_NOISE_XK_PATTERN_2, - 0, - &mut message2[NoiseXKPattern2::P_ENC_START..NoiseXKPattern2::P_AUTH_END], - ); - - app.event_log(LogEvent::ReceiveValidXK1, current_time); - let handshake = Arc::new(NoiseXKBobHandshakeState { - local_key_id, - remote_key_id, - ratchet_state, - noise_h_ee1peekem1pskp, - noise_ck_eseeekem1psk: noise_ck.clone(), - noise_k_eseeekem1psk: noise_k_eseeekem1psk.clone(), - noise_e_secret: noise_e_pattern2_secret, - header_receive_key: header_a2b_key.clone(), - header_send_key: header_b2a_key.clone(), - noise_pattern3_defrag: Mutex::new(Fragged::new()), - }); - self.0.unassociated_handshake_states.insert(local_key_id, handshake, current_time); - - // We put a copy of the gcm tag in the header so Alice can tell this packet apart - // from any other pattern 1 packet we send, without having to make Bob maintain state. - let mut pattern2_id = 0u64.to_ne_bytes(); - pattern2_id[5] = message2[NoiseXKPattern2::P_AUTH_END - 3]; - pattern2_id[6] = message2[NoiseXKPattern2::P_AUTH_END - 2]; - pattern2_id[7] = message2[NoiseXKPattern2::P_AUTH_END - 1]; - send_with_fragmentation( - &mut send_unassociated_reply, - send_unassociated_mtu, - &mut message2, - PACKET_TYPE_NOISE_XK_PATTERN_2, - Some(remote_key_id), - u64::from_be_bytes(pattern2_id), - Some(&Application::PrpEnc::new(header_b2a_key.first_n::())), - ); - - return Ok(ReceiveResult::Unassociated); - } else { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - } - PACKET_TYPE_BOB_DOS_CHALLENGE => { - let message = &mut message[..message_size]; - app.event_log(LogEvent::ReceiveUncheckedDOSChallenge, current_time); - - // We expect Bob to only send this to us through our unassociated defrag cache. - if incoming.is_some() || session.is_some() { - return Err(byzantine_fault!(FaultType::OutOfSequence, false)); - } - if message.len() != BobDOSChallenge::SIZE { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - let challenge: &BobDOSChallenge = byte_array_as_proto_buffer(message); - - if let Some(local_key_id) = NonZeroU32::new(u32::from_ne_bytes(challenge.alice_key_id)) { - if let Some(session) = self.0.session_map.read().unwrap().get(&local_key_id).and_then(|s| s.0.upgrade()) { - // We don't need to hold the kex lock because we are not transitioning state. - let mut state = session.state.write().unwrap(); - if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { - if let NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_message, noise_message_len, .. } = &mut handshake_state.offer { - let response_raw = &mut noise_message[*noise_message_len - ChallengeResponse::SIZE..]; - - let response: &mut ChallengeResponse = byte_array_as_proto_buffer_mut(response_raw); - // Only people who know what Alice's prior pow was can convince us to - // compute a new pow. - if challenge.prior_challenge_pow != response.challenge_pow { - // This can occur if Bob sends us multiple challenges and they - // arrive OOO. - return Err(byzantine_fault!(FaultType::FailedAuthentication, true)); - } - response.challenge_counter.copy_from_slice(&challenge.challenge_counter); - response.challenge_mac.copy_from_slice(&challenge.challenge_mac); - let mut pow = self.0.rng.lock().unwrap().next_u64(); - let sha512 = &mut Application::Hash::new(); - loop { - let response: &mut ChallengeResponse = byte_array_as_proto_buffer_mut(response_raw); - response.challenge_pow.copy_from_slice(&pow.to_be_bytes()); - if verify_pow::(sha512, response_raw) { - break; - } - pow = pow.wrapping_add(1); - } - - app.event_log(LogEvent::ReceiveValidDOSChallenge(&session), current_time); - return Ok(ReceiveResult::Unassociated); - } else { - // This could happen if Bob challenges Alice, but their challenge packet - // gets massively delayed. - return Err(byzantine_fault!(FaultType::OutOfSequence, true)); - } - } else { - // This could happen if Bob challenges Alice, but their challenge packet - // gets massively delayed. - return Err(byzantine_fault!(FaultType::OutOfSequence, true)); - } - } else { - // This can occur naturally if Alice's session was dropped. - return Err(byzantine_fault!(FaultType::UnknownLocalKeyId, true)); - } - } else { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - } - PACKET_TYPE_NOISE_XK_PATTERN_2 => { - // Bob (remote) --> Alice (local) - // <- e, ee, ekem1, psk - let message = &mut message[..message_size]; - app.event_log(LogEvent::ReceiveUncheckedXK2, current_time); - - if incoming.is_some() { - return Err(byzantine_fault!(FaultType::OutOfSequence, false)); - } - if message.len() != NoiseXKPattern2::SIZE { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - - let session = session.ok_or(byzantine_fault!(FaultType::UnknownLocalKeyId, false))?; - let kex_lock = session.state_machine_lock.lock().unwrap(); - let state = session.state.read().unwrap(); - - if let NoiseXKPattern1or3(handshake_state) = &state.outgoing_offer { - if let NoiseXKAliceHandshakeState::NoiseXKPattern1 { - noise_h_ee1p, noise_e_secret, noise_e1_secret, noise_ck_es, .. - } = &handshake_state.offer - { - let noise_pattern2: &NoiseXKPattern2 = byte_array_as_proto_buffer(message); - // Authenticate header counter. - if noise_pattern2.header[13..16] != noise_pattern2.p_gcm_tag[13..16] { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - - // Noise process pattern2 e token. - let mut noise_ee = Secret::new(); - if let Some(noise_e_pattern2) = - from_bytes_agreement::(&noise_pattern2.noise_e, noise_e_secret, noise_ee.as_mut()) - { - let sha512 = &mut Application::Hash::new(); - let hmac = &mut Application::HmacHash::new(); - let mut noise_ck = noise_ck_es.clone(); - let noise_h_ee1pe = mix_hash(sha512, noise_h_ee1p, noise_e_pattern2.as_bytes()); - noise_ck.mix_key(hmac, noise_e_pattern2.as_bytes()); - // Noise process pattern2 ee token. - let noise_k_esee = noise_ck.mix_key_initialize_key(hmac, noise_ee.as_ref()); - drop(noise_ee); - // Noise process pattern2 ekem1 token. - let (is_auth, noise_h_ee1peekem1) = decrypt_and_hash::( - sha512, - &noise_k_esee, - &noise_h_ee1pe, - packet_type, - 0, - &mut message[NoiseXKPattern2::EKEM1_ENC_START..NoiseXKPattern2::P_ENC_START], - ); - let noise_pattern2: &NoiseXKPattern2 = byte_array_as_proto_buffer(message); - let noise_ekem1_secret = pqc_kyber::decapsulate(&noise_pattern2.noise_ekem1, noise_e1_secret.as_ref()).map(Secret); - if let Some(Ok(noise_ekem1_secret)) = is_auth.then_some(noise_ekem1_secret) { - noise_ck.mix_key(hmac, noise_ekem1_secret.as_ref()); - drop(noise_ekem1_secret); - - // We attempt to decrypt the payload at most three times. First two times with - // the ratchet key Alice remembers, and final time with a ratchet - // key of zero if Alice allows ratchet downgrades. - // The following code is not constant time, meaning we leak to an - // attacker whether or not we downgraded. - // We don't currently consider this sensitive enough information to hide. - let mut test_ratchet_key = |ratchet_key| -> Option<(NonZeroU32, SymmetricState, Secret, [u8; 64])> { - // Check for which ratchet key Bob wants to use. - let mut noise_ck = noise_ck.clone(); - let mut payload = [0u8; NoiseXKPattern2::P_AUTH_END - NoiseXKPattern2::P_ENC_START]; - payload.copy_from_slice(&message[NoiseXKPattern2::P_ENC_START..NoiseXKPattern2::P_AUTH_END]); - // Noise process pattern2 psk token. - let (temp_h, noise_k_eseeekem1psk) = noise_ck.mix_key_and_hash_initialize_key(hmac, ratchet_key); - let noise_h_ee1peekem1psk = mix_hash(sha512, &noise_h_ee1peekem1, &temp_h); - // Noise process pattern2 payload. - let (is_auth, noise_h_ee1peekem1pskp) = decrypt_and_hash::( - sha512, - &noise_k_eseeekem1psk, - &noise_h_ee1peekem1psk, - packet_type, - 0, - &mut payload, - ); - if is_auth { - let key_id = NonZeroU32::new(u32::from_ne_bytes( - (&payload[..NoiseXKPattern2::P_AUTH_START - NoiseXKPattern2::P_ENC_START]) - .try_into() - .unwrap(), - )); - key_id.map(|kid| (kid, noise_ck, noise_k_eseeekem1psk, noise_h_ee1peekem1pskp)) - } else { - None - } - }; - // Check first key. - let mut ratchet_i = 0; - let mut result = None; - let mut chain_len = 0; - if let Some(key) = state.ratchet_states[0].key() { - chain_len = state.ratchet_states[0].chain_len(); - result = test_ratchet_key(key); - } - // Check second key. - if result.is_none() { - ratchet_i = 1; - if let Some(key) = state.ratchet_states[1].key() { - chain_len = state.ratchet_states[1].chain_len(); - result = test_ratchet_key(key); - } - } - // Check zero key. - if result.is_none() && !app.initiator_disallows_downgrade(&session, current_time) { - chain_len = 0; - result = test_ratchet_key(&[0u8; RATCHET_SIZE]); - if result.is_some() { - // TODO: add some kind of warning callback or signal. - } - } - - if let Some((remote_key_id, mut noise_ck, noise_k_eseeekem1psk, noise_h_ee1peekem1pskp)) = result { - // Start of Noise XKhfs+psk2 pattern3. - let mut message3 = [0u8; NoiseXKPattern3::MAX_SIZE]; - // Noise process pattern3 s token. - let mut noise_se = Secret::new(); - if self.0.static_keypair.agree(&noise_e_pattern2, noise_se.as_mut()) { - let payload = handshake_state.alice_identity_blob.as_ref(); - // Packet fully authenticated. - let s_enc_start = HEADER_SIZE; - let s_auth_start = s_enc_start + P384_PUBLIC_KEY_SIZE; - let p_enc_start = s_auth_start + AES_GCM_TAG_SIZE; - let p_auth_start = p_enc_start + payload.len(); - let p_auth_end = p_auth_start + AES_GCM_TAG_SIZE; - let message3_len = p_auth_end; - - message3[s_enc_start..s_auth_start].copy_from_slice(self.0.static_keypair.public_key_bytes()); - let noise_h_ee1peekem1pskps = encrypt_and_hash::( - sha512, - &noise_k_eseeekem1psk, - &noise_h_ee1peekem1pskp, - PACKET_TYPE_NOISE_XK_PATTERN_3, - 1, - &mut message3[s_enc_start..p_enc_start], - ); - drop(noise_k_eseeekem1psk); - // Noise process pattern3 se token. - let noise_k_eseeekem1pskse = noise_ck.mix_key_initialize_key(hmac, noise_se.as_ref()); - drop(noise_se); - // Noise process pattern3 payload token. - message3[p_enc_start..p_auth_start].copy_from_slice(payload); - let noise_h_ee1peekem1pskpsp = encrypt_and_hash::( - sha512, - &noise_k_eseeekem1pskse, - &noise_h_ee1peekem1pskps, - PACKET_TYPE_NOISE_XK_PATTERN_3, - 0, - &mut message3[p_enc_start..p_auth_end], - ); - drop(noise_k_eseeekem1pskse); - // Alice finished Noise XKhfs+psk2 handshake. - // Transition offer state machine to the NoiseXKPattern3 state. - let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_ee1peekem1pskpsp); - let new_ratchet_state = RatchetState::new_nonempty(rk, rf, NonZeroU64::new(chain_len + 1).unwrap()); - - let ratchet_to_preserve = &state.ratchet_states[ratchet_i]; - let result = app.save_ratchet_state( - &session.remote_static_key, - &session.application_data, - [&state.ratchet_states[0], &state.ratchet_states[1]], - [&new_ratchet_state, ratchet_to_preserve], - current_time, - ); - if let Err(e) = result { - return Err(ReceiveError::RatchetIoError(e)); - } - - let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(hmac, LABEL_KEX_KEY, &noise_h_ee1peekem1pskpsp); - - let local_key_id = handshake_state.local_key_id; - drop(state); - let mut state = session.state.write().unwrap(); - session - .kex_send_cipher - .lock() - .unwrap() - .replace(Application::AeadEnc::new(kex_key_a2b.as_ref())); - session - .kex_receive_cipher - .lock() - .unwrap() - .replace(Application::AeadDec::new(kex_key_b2a.as_ref())); - state.ratchet_states[1] = state.ratchet_states[ratchet_i].clone(); - state.ratchet_states[0] = new_ratchet_state; - - state.cipher_states[0].replace(SessionKey::new( - hmac, - noise_ck, - local_key_id, - remote_key_id, - INIT_COUNTER, - false, - )); - debug_assert!(state.cipher_states[1].is_none()); - if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { - handshake_state.next_retry_time = - AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)); - handshake_state.timeout = current_time.saturating_add(Application::INITIAL_OFFER_TIMEOUT_MS); - handshake_state.offer = NoiseXKAliceHandshakeState::NoiseXKPattern3 { - noise_message: message3, - noise_message_len: p_auth_end, - }; - } - drop(state); - drop(kex_lock); - - if let Some((mut send, mut mtu)) = send_to(&session) { - mtu = mtu.max(MIN_TRANSPORT_MTU); - send_with_fragmentation( - &mut send, - mtu, - &mut message3[..message3_len], - PACKET_TYPE_NOISE_XK_PATTERN_3, - Some(remote_key_id), - 0, - Some(&session.header_send_cipher), - ); - } - app.event_log(LogEvent::ReceiveValidXK2(&session), current_time); - return Ok(ReceiveResult::Session(session, SessionEvent::Control)); - } - } - } - } - // Bob failed authentication so we must restart our offer according to Noise. - // We restart the offer instead of dropping the session to defend against DOS. - drop(state); - let mut state = session.state.write().unwrap(); - let ratchet_state = state.ratchet_states.clone(); - if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { - if !handshake_state.reinitialize( - &session, - &ratchet_state, - &mut self.0.session_map.write().unwrap(), - &mut self.0.rng.lock().unwrap(), - current_time, - ) { - session.expire() - } - } - drop(state); - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } else { - return Err(byzantine_fault!(FaultType::OutOfSequence, false)); - } - } else { - return Err(byzantine_fault!(FaultType::OutOfSequence, false)); - } - } - PACKET_TYPE_NOISE_XK_PATTERN_3 => { - // Alice (remote) --> Bob (local) - // -> s, se - let message = &mut message[..message_size]; - app.event_log(LogEvent::ReceiveUncheckedXK3, current_time); - - if session.is_some() { - return Err(byzantine_fault!(FaultType::OutOfSequence, false)); - } - if message.len() < NoiseXKPattern3::MIN_SIZE || message.len() > NoiseXKPattern3::MAX_SIZE { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - // The code above guarantees to us that each `incoming` handshake state that reaches - // this point will be strictly unique, even for the same remote peer. - // This property is strictly necessary to prevent catastrophic nonce reuse due to - // two session being created with the same set of keys. - let handshake_state = incoming.ok_or(byzantine_fault!(FaultType::UnknownLocalKeyId, false))?; - let s_enc_start = HEADER_SIZE; - - let s_auth_start = s_enc_start + P384_PUBLIC_KEY_SIZE; - let p_enc_start = s_auth_start + AES_GCM_TAG_SIZE; - let p_auth_end = message.len(); - let p_auth_start = p_auth_end - AES_GCM_TAG_SIZE; - - if !(p_enc_start <= p_auth_start) { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - // Do not read from the message before this point, otherwise an array out of bounds - // error is possible. - // Noise process pattern3 s token. - let sha512 = &mut Application::Hash::new(); - let hmac = &mut Application::HmacHash::new(); - let (is_auth, noise_h_ee1peekem1pskps) = decrypt_and_hash::( - sha512, - &handshake_state.noise_k_eseeekem1psk, - &handshake_state.noise_h_ee1peekem1pskp, - packet_type, - 1, - &mut message[s_enc_start..p_enc_start], - ); - if !is_auth { - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - // Noise process pattern3 se token. - let mut noise_se = Secret::new(); - if let Some(remote_s_public_key) = - from_bytes_agreement::(&message[s_enc_start..s_auth_start], &handshake_state.noise_e_secret, noise_se.as_mut()) - { - let mut noise_ck = handshake_state.noise_ck_eseeekem1psk.clone(); - let noise_k_eseeekem1pskse = noise_ck.mix_key_initialize_key(hmac, noise_se.as_ref()); - drop(noise_se); - // Noise process pattern3 payload. - let (is_auth, noise_h_ee1peekem1pskpsp) = decrypt_and_hash::( - sha512, - &noise_k_eseeekem1pskse, - &noise_h_ee1peekem1pskps, - packet_type, - 0, - &mut message[p_enc_start..p_auth_end], - ); - drop(noise_k_eseeekem1pskse); - if !is_auth { - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - // Bob finished Noise XKhfs+psk2 handshake. - let header_send_cipher = Application::PrpEnc::new(handshake_state.header_send_key.as_ref()); - let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(hmac, LABEL_KEX_KEY, &noise_h_ee1peekem1pskpsp); - let mut send_reject = || { - // 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 - // handshake is being dropped, so nonce reuse can't happen. - let (mut fragment, len) = encrypt_control( - &mut Application::AeadEnc::new(kex_key_b2a.as_ref()), - &header_send_cipher, - PACKET_TYPE_SESSION_REJECTED, - INIT_COUNTER, - handshake_state.remote_key_id.get(), - &[], - ); - send_unassociated_reply(&mut fragment[..len]); - }; - - let (responder_disallows_downgrade, responder_silently_rejects) = check_accept_session( - &remote_s_public_key, - &message[p_enc_start..p_auth_start], - handshake_state.ratchet_state.chain_len(), - ); - if let Some((responder_disallows_downgrade, application_data)) = responder_disallows_downgrade { - let result = app.restore_by_identity(&remote_s_public_key, &application_data, current_time); - match result { - Ok(true_ratchet_states) => { - let mut has_match = false; - for rs in &true_ratchet_states { - if !rs.is_null() { - has_match |= &handshake_state.ratchet_state == rs; - } - } - if !has_match { - if !responder_disallows_downgrade && handshake_state.ratchet_state.is_empty() { - // TODO: add some kind of warning callback or signal. - } else { - if !responder_silently_rejects { - send_reject(); - } - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - } - - let mut noise_kk_ss = Secret::new(); - if !self.0.static_keypair.agree(&remote_s_public_key, noise_kk_ss.as_mut()) { - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - let noise_kk_local_init_h = mix_hash(sha512, &INITIAL_H_REKEY, self.0.static_keypair.public_key_bytes()); - let noise_kk_local_init_h = mix_hash(sha512, &noise_kk_local_init_h, remote_s_public_key.as_bytes()); - let noise_kk_remote_init_h = mix_hash(sha512, &INITIAL_H_REKEY, remote_s_public_key.as_bytes()); - let noise_kk_remote_init_h = mix_hash(sha512, &noise_kk_remote_init_h, self.0.static_keypair.public_key_bytes()); - - let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_ee1peekem1pskpsp); - // We must make sure the ratchet key is saved before we transition. - let new_ratchet_state = - RatchetState::new_nonempty(rk, rf, NonZeroU64::new(handshake_state.ratchet_state.chain_len() + 1).unwrap()); - let result = app.save_ratchet_state( - &remote_s_public_key, - &application_data, - [&true_ratchet_states[0], &true_ratchet_states[1]], - [&new_ratchet_state, &RatchetState::Null], - current_time, - ); - if let Err(e) = result { - return Err(ReceiveError::RatchetIoError(e)); - } - - let mut session_queue = self.0.session_queue.lock().unwrap(); - let queue_idx = session_queue.reserve_index(); - let session = Arc::new(Session { - context: Arc::downgrade(&self.0), - queue_idx, - application_data, - remote_static_key: remote_s_public_key, - send_counter: AtomicU64::new(INIT_COUNTER), - session_has_expired: AtomicBool::new(false), - counter_antireplay_window: std::array::from_fn(|_| AtomicU64::new(0)), - state_machine_lock: Mutex::new(()), - state: RwLock::new(SessionMutableState { - ratchet_states: [new_ratchet_state.clone(), RatchetState::Null], - cipher_states: [ - Some(SessionKey::new( - hmac, - noise_ck, - handshake_state.local_key_id, - handshake_state.remote_key_id, - INIT_COUNTER, - true, - )), - None, - ], - current_key: 0, - outgoing_offer: KeyConfirm { - next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), - timeout: current_time.saturating_add(Application::EXPIRATION_TIMEOUT_MS), - }, - }), - header_receive_cipher: Application::PrpDec::new(handshake_state.header_receive_key.as_ref()), - header_send_cipher, - kex_send_cipher: Mutex::new(Some(Application::AeadEnc::new(kex_key_b2a.as_ref()))), - kex_receive_cipher: Mutex::new(Some(Application::AeadDec::new(kex_key_a2b.as_ref()))), - noise_kk_ss, - noise_kk_local_init_h, - noise_kk_remote_init_h, - defrag: std::array::from_fn(|_| Mutex::new(Fragged::new())), - was_bob: true, - }); - let timer = Reverse(current_time.saturating_add(Application::RETRY_INTERVAL_MS)); - session_queue.push_reserved(queue_idx, Arc::downgrade(&session), timer); - drop(session_queue); - // There is the miniscule possibility this key id is already - // in use, in which case we have to drop this session like - // nothing ever happened. - let mut session_map = self.0.session_map.write().unwrap(); - if let std::collections::hash_map::Entry::Vacant(e) = session_map.entry(handshake_state.local_key_id) { - e.insert((Arc::downgrade(&session), false)); - drop(session_map); - let _ = - session.send_control(&session.state.read().unwrap(), send_unassociated_reply, PACKET_TYPE_KEY_CONFIRM, &[]); - - app.event_log(LogEvent::ReceiveValidXK3(&session.application_data), current_time); - return Ok(ReceiveResult::Session(session, SessionEvent::NewSession)); - } else { - // This can occur if we accidentally generate a key id collision. - // There is an extremely short amount of time during which - // another session can steal this session's id, we'll have to - // restart the handshake in this case. - return Err(byzantine_fault!(FaultType::UnknownLocalKeyId, true)); - } - } - Err(e) => { - return Err(ReceiveError::RatchetIoError(e)); - } - } - } else { - if !responder_silently_rejects { - send_reject(); - } - return Ok(ReceiveResult::Rejected); - } - } else { - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - } - _ => return Err(byzantine_fault!(FaultType::InvalidPacket, false)), - } - } - /// Helper function for sending the empty string over the session. Useful for keep-alives. - /// - /// * `session` - The session to send to - /// * `send` - Function to call to send physical packet(s); the buffer passed to `send` is a - /// slice of `data` - /// * `current_time` - Current time in milliseconds - pub fn send_empty(&self, session: &Arc>, send: impl FnMut(&mut [u8]) -> bool, current_time: i64) -> Result<(), SendError> { - self.send(session, send, &mut [0u8; MIN_TRANSPORT_MTU], &[], current_time) } /// Send data over the session. /// @@ -1644,7 +437,7 @@ impl Context { /// * `current_time` - Current time in milliseconds pub fn send( &self, - session: &Arc>, + session: &Arc>, mut send: impl FnMut(&mut [u8]) -> bool, mtu_sized_buffer: &mut [u8], mut data: &[u8], @@ -1710,983 +503,229 @@ impl Context { } Ok(()) } - /// Update the challenge window, returning true if the challenge is still valid. - fn check_challenge_window(&self, counter: u64) -> bool { - let slot = &self.0.challenge_antireplay_window[(counter as usize) % self.0.challenge_antireplay_window.len()]; - let counter = counter.wrapping_add(1); - let prev_counter = slot.load(Ordering::Relaxed); - prev_counter < counter - } - /// Update the challenge window, returning true if the challenge is still valid. - fn update_challenge_window(&self, counter: u64) -> bool { - let slot = &self.0.challenge_antireplay_window[(counter as usize) % self.0.challenge_antireplay_window.len()]; - let counter = counter.wrapping_add(1); - let prev_counter = slot.fetch_max(counter, Ordering::Relaxed); - prev_counter < counter - } -} -/// Initiate the rekeying protocol. This session will now begin attempting to rekey this session -/// with its peer, if it was not already. -fn initiate_rekey( - context: &Arc>, - session: &Arc>, - send: impl FnOnce(&mut [u8]) -> bool, - current_time: i64, -) -> Result { - let mut message = [0u8; NoiseKKPattern1or2::SIZE]; - let kex_lock = session.state_machine_lock.lock().unwrap(); - let state = session.state.read().unwrap(); - // We may only attempt to rekey if we are not already doing so. - match &state.outgoing_offer { - OfferStateMachine::Normal { .. } => (), - _ => return Err(()), - } - let sha512 = &mut Application::Hash::new(); - let hmac = &mut Application::HmacHash::new(); - // Start of Noise KKpsk0 pattern1. - // Noise process pattern1 psk0 token. - let mut noise_ck = SymmetricState::new(INITIAL_H_REKEY); - let noise_temp_h = noise_ck.mix_key_and_hash(hmac, state.ratchet_states[0].key().unwrap()); - let noise_h_psk = mix_hash(sha512, &session.noise_kk_local_init_h, &noise_temp_h); - // Noise process pattern1 e token. - let noise_e_secret = Application::KeyPair::generate(&mut context.rng.lock().unwrap()); - let noise_h_pske = mix_hash(sha512, &noise_h_psk, noise_e_secret.public_key_bytes()); - noise_ck.mix_key(hmac, noise_e_secret.public_key_bytes()); + /// Perform periodic background service and cleanup tasks. + /// + /// 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 second or two are not + /// a problem. + /// + /// * `send_to` - Function to get a sender and an MTU to send something over an active session + /// * `current_time` - 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 + /// should rekey. + pub fn service bool>( + &self, + app: &App, + mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, + current_time: i64, + ) -> i64 { + let retry_next = current_time.saturating_add(App::RETRY_INTERVAL_MS); + let mut next_service_time = 2 * App::RETRY_INTERVAL_MS; - let noise_pattern1: &mut NoiseKKPattern1or2 = byte_array_as_proto_buffer_mut(&mut message); - noise_pattern1.noise_e = *noise_e_secret.public_key_bytes(); - // Noise process pattern1 es token. - let mut noise_es = Secret::new(); - if !noise_e_secret.agree(&session.remote_static_key, noise_es.as_mut()) { - return Err(()); - } - noise_ck.mix_key(hmac, noise_es.as_ref()); - drop(noise_es); - // Noise process pattern1 ss token. - let noise_k_pskesss = noise_ck.mix_key_initialize_key(hmac, session.noise_kk_ss.as_ref()); - // Noise process pattern1 payload token. - let mut session_map = context.session_map.write().unwrap(); - let new_key_id = generate_key_id(&session_map, &mut context.rng.lock().unwrap()); - let next_key_index = state.current_key ^ 1; - session_map.insert(new_key_id, (Arc::downgrade(session), next_key_index > 0)); - drop(session_map); - - let noise_pattern1: &mut NoiseKKPattern1or2 = byte_array_as_proto_buffer_mut(&mut message); - noise_pattern1.key_id = new_key_id.get().to_ne_bytes(); - let noise_h_pskep = encrypt_and_hash::( - sha512, - &noise_k_pskesss, - &noise_h_pske, - PACKET_TYPE_NOISE_KK_PATTERN_1, - 0, - &mut message[NoiseKKPattern1or2::ENC_START..NoiseKKPattern1or2::AUTH_END], - ); - drop(noise_k_pskesss); - - drop(state); - let mut state = session.state.write().unwrap(); - state.outgoing_offer = OfferStateMachine::NoiseKKPattern1 { - next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), - timeout: current_time.saturating_add(Application::EXPIRATION_TIMEOUT_MS), - new_key_id, - noise_e_secret, - noise_message: message.clone(), - noise_h_pskep, - noise_ck: noise_ck.clone(), - }; - drop(state); - drop(kex_lock); - let _ = session.send_control(&session.state.read().unwrap(), send, PACKET_TYPE_NOISE_KK_PATTERN_1, &message); - Ok(current_time.saturating_add(Application::RETRY_INTERVAL_MS)) -} -fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mut [u8]) -> bool>( - context: &Context, - session: Arc>, - app: &Application, - mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, - packet_type: u8, - counter: u64, - fragment: &mut [u8], - current_time: i64, -) -> Result, ReceiveError> { - let kex_lock = session.state_machine_lock.lock().unwrap(); - let state = session.state.read().unwrap(); - let mut c = session.kex_receive_cipher.lock().unwrap(); - let message = decrypt_control( - c.as_mut().ok_or(byzantine_fault!(FaultType::OutOfSequence, false))?, - packet_type, - counter, - fragment, - )?; - drop(c); - session.update_receive_window(counter); - use OfferStateMachine::*; - return match packet_type { - PACKET_TYPE_SESSION_REJECTED => { - if let NoiseXKPattern1or3(_) = &state.outgoing_offer { - drop(state); - let mut state = session.state.write().unwrap(); - state.outgoing_offer = OfferStateMachine::Null; - drop(state); - drop(kex_lock); - Ok(ReceiveResult::Session(session, SessionEvent::Rejected)) - } else { - // This can occur naturally because of control packet resends. - Err(byzantine_fault!(FaultType::OutOfSequence, true)) + let mut session_queue = self.0.session_queue.lock().unwrap(); + // This update system takes heavy advantage of the fact that sessions only need to be updated + // either roughly every second or roughly every hour. That big gap allows for minor optimizations. + // If the gap changes (unlikely) this code may need to be rewritten. + while let Some((session, timer, queue_idx)) = session_queue.peek() { + if timer.0 >= current_time { + next_service_time = next_service_time.min(timer.0 - current_time); + break; } - } - PACKET_TYPE_KEY_CONFIRM => { - drop(state); - app.event_log(LogEvent::ReceiveValidKeyConfirm(&session), current_time); - let mut state = session.state.write().unwrap(); - // We only want to stop sending NoiseKKPattern2 offers when the latest derived - // key is confirmed. And we only want to do that once. - let (used_latest_key, try_delete, ret) = match &state.outgoing_offer { - NoiseKKPattern2 { .. } => (true, true, SessionEvent::Control), - NoiseXKPattern1or3(handshake_state) => { - if let NoiseXKAliceHandshakeState::NoiseXKPattern3 { .. } = &handshake_state.offer { - (true, true, SessionEvent::Established) - } else { - (false, false, SessionEvent::Control) - } + let session = match session.upgrade() { + Some(s) => s, + _ => { + session_queue.remove(queue_idx); + continue; } - Null => (false, false, SessionEvent::Control), - _ => (true, false, SessionEvent::Control), }; - if try_delete { - let result = if !state.ratchet_states[1].is_null() { - app.save_ratchet_state( - &session.remote_static_key, - &session.application_data, - [&state.ratchet_states[0], &state.ratchet_states[1]], - [&state.ratchet_states[0], &RatchetState::Null], - current_time, - ) - } else { - Ok(()) - }; - if let Err(e) = result { - return Err(ReceiveError::RatchetIoError(e)); - } - if let NoiseKKPattern2 { kex_send_key, .. } = &state.outgoing_offer { - session - .kex_send_cipher - .lock() - .unwrap() - .replace(Application::AeadEnc::new(kex_send_key.as_ref())); - } - state.ratchet_states[1] = RatchetState::Null; - state.current_key ^= 1; - state.outgoing_offer = new_normal_state(context.0.rng.lock().unwrap().next_u64(), current_time); - } - drop(state); - drop(kex_lock); - if used_latest_key { - if let Some((send, _)) = send_to(&session) { - let _ = session.send_control(&session.state.read().unwrap(), send, PACKET_TYPE_ACK, &[]); - } - } - Ok(ReceiveResult::Session(session, ret)) - } - PACKET_TYPE_ACK => { - if let KeyConfirm { .. } = &state.outgoing_offer { - drop(state); - app.event_log(LogEvent::ReceiveValidAck(&session), current_time); - let mut state = session.state.write().unwrap(); - // Check if we should end any current offers and transition back to Normal state - state.outgoing_offer = new_normal_state(context.0.rng.lock().unwrap().next_u64(), current_time); - drop(kex_lock); - drop(state); - Ok(ReceiveResult::Session(session, SessionEvent::Control)) - } else { - // This can occur naturally because of control packet resends. - Err(byzantine_fault!(FaultType::OutOfSequence, true)) - } - } - PACKET_TYPE_NOISE_KK_PATTERN_1 => { - app.event_log(LogEvent::ReceiveUncheckedKK1, current_time); - let message = &mut message[..NoiseKKPattern1or2::SIZE]; - let noise_pattern1: &NoiseKKPattern1or2 = byte_array_as_proto_buffer(message); - // We need the following operation to be atomic with the change of offer type - let (should_rekey_as_bob, chosen_id) = match &state.outgoing_offer { - // Check rekey rate limits. - Normal { .. } => (true, None), - // In the following situation, both parties are in state NoiseKKPattern1, - // we need to deterministically allow only one of them to transition to - // NoiseKKPattern2. - NoiseKKPattern1 { new_key_id, .. } => (session.was_bob, Some(*new_key_id)), - _ => (false, None), - }; - if !should_rekey_as_bob { - // This can be triggered if both parties attempt rekeying simultaneously, or if the - // remote party sent us a duplicate rekey request. - // The code above handles this case and only lets one party through to rekeying. - drop(state); - drop(kex_lock); - return Ok(ReceiveResult::Session(session, SessionEvent::Control)); - } - // Noise process pattern1 psk0 token. - let sha512 = &mut Application::Hash::new(); - let hmac = &mut Application::HmacHash::new(); - let mut noise_ck = SymmetricState::new(INITIAL_H_REKEY); - let noise_temp_h = noise_ck.mix_key_and_hash(hmac, state.ratchet_states[0].key().unwrap()); - let noise_h_psk = mix_hash(sha512, &session.noise_kk_remote_init_h, &noise_temp_h); - // Noise process pattern1 e token. - // Get public key validation out of the way early - let mut noise_es = Secret::new(); - let mut noise_ee = Secret::new(); - let mut noise_se = Secret::new(); - if let Some(alice_e) = from_bytes_agreement::(&noise_pattern1.noise_e, &context.0.static_keypair, noise_es.as_mut()) { - let bob_e_secret = Application::KeyPair::generate(&mut context.0.rng.lock().unwrap()); - if bob_e_secret.agree(&alice_e, noise_ee.as_mut()) && bob_e_secret.agree(&session.remote_static_key, noise_se.as_mut()) { - let noise_h_pske = mix_hash(sha512, &noise_h_psk, alice_e.as_bytes()); - noise_ck.mix_key(hmac, alice_e.as_bytes()); - // Noise process pattern1 es token. - noise_ck.mix_key(hmac, noise_es.as_ref()); - drop(noise_es); - // Noise process pattern1 ss token. - let noise_k_pskesss = noise_ck.mix_key_initialize_key(hmac, session.noise_kk_ss.as_ref()); - - // Noise process pattern1 payload. - let (is_auth, noise_h_pskep) = decrypt_and_hash::( - sha512, - &noise_k_pskesss, - &noise_h_pske, - packet_type, - 0, - &mut message[NoiseKKPattern1or2::ENC_START..NoiseKKPattern1or2::AUTH_END], - ); - let noise_pattern1: &NoiseKKPattern1or2 = byte_array_as_proto_buffer(message); - if let (true, Some(remote_key_id)) = (is_auth, NonZeroU32::new(u32::from_ne_bytes(noise_pattern1.key_id))) { - // Alice fully authenticated. - // Start of Noise KKpsk0 pattern2. - // Noise process pattern2 e token. - let noise_h_pskepe = mix_hash(sha512, &noise_h_pskep, bob_e_secret.public_key_bytes()); - noise_ck.mix_key(hmac, bob_e_secret.public_key_bytes()); - // Noise process pattern2 ee token. - noise_ck.mix_key(hmac, noise_ee.as_ref()); - drop(noise_ee); - // Noise process pattern2 se token. - let noise_k_pskessseese = noise_ck.mix_key_initialize_key(hmac, noise_se.as_ref()); - drop(noise_se); - // Noise process pattern2 payload. - let mut message2 = [0u8; NoiseKKPattern1or2::SIZE]; - let noise_pattern2: &mut NoiseKKPattern1or2 = byte_array_as_proto_buffer_mut(&mut message2); - noise_pattern2.noise_e = *bob_e_secret.public_key_bytes(); - let mut session_map = context.0.session_map.write().unwrap(); - // If we already generated a new key id mapping reuse it. - let new_key_id = chosen_id.unwrap_or_else(|| generate_key_id(&session_map, &mut context.0.rng.lock().unwrap())); - noise_pattern2.key_id = new_key_id.get().to_ne_bytes(); - - let noise_h_pskepep = encrypt_and_hash::( - sha512, - &noise_k_pskessseese, - &noise_h_pskepe, - PACKET_TYPE_NOISE_KK_PATTERN_2, - 0, - &mut message2[NoiseKKPattern1or2::ENC_START..NoiseKKPattern1or2::AUTH_END], - ); - drop(noise_k_pskessseese); - // Bob finished Noise KKpsk0 handshake. - let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_pskepep); - let new_ratchet_state = RatchetState::new_nonempty(rk, rf, NonZeroU64::new(state.ratchet_states[0].chain_len() + 1).unwrap()); - let result = app.save_ratchet_state( - &session.remote_static_key, - &session.application_data, - [&state.ratchet_states[0], &state.ratchet_states[1]], - [&new_ratchet_state, &state.ratchet_states[0]], - current_time, - ); - if let Err(e) = result { - drop(state); - drop(kex_lock); - return Err(ReceiveError::RatchetIoError(e)); - } - let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(hmac, LABEL_KEX_KEY, &noise_h_pskepep); - // The new "Bob" doesn't know yet if Alice has received the new key, so the - // new key is recorded as the "alt" (key_index ^ 1) but the current key is - // not advanced yet. - let next_key_index = state.current_key ^ 1; - session_map.insert(new_key_id, (Arc::downgrade(&session), next_key_index > 0)); - if let Some(pre_id) = state.cipher_states[next_key_index].as_ref().map(|k| k.local_key_id) { - session_map.remove(&pre_id); - } - drop(session_map); + let state = session.state.read().unwrap(); + let next_timer = match &state.outgoing_offer { + Normal { timeout, .. } => { + if *timeout <= current_time { drop(state); - let mut state = session.state.write().unwrap(); - let current_counter = session.send_counter.load(Ordering::Relaxed); - session - .kex_receive_cipher - .lock() - .unwrap() - .replace(Application::AeadDec::new(kex_key_a2b.as_ref())); - state.ratchet_states[1] = state.ratchet_states[0].clone(); - state.ratchet_states[0] = new_ratchet_state.clone(); - - state.cipher_states[next_key_index].replace(SessionKey::new( - hmac, - noise_ck, - new_key_id, - remote_key_id, - current_counter, - true, - )); - let timer = current_time.saturating_add(Application::RETRY_INTERVAL_MS); - state.outgoing_offer = NoiseKKPattern2 { - next_retry_time: AtomicI64::new(timer), - timeout: current_time.saturating_add(Application::EXPIRATION_TIMEOUT_MS), - noise_message: message2, - kex_send_key: kex_key_b2a.clone(), - }; - drop(state); - drop(kex_lock); - context.0.session_queue.lock().unwrap().change_priority(session.queue_idx, Reverse(timer)); - if let Some((send, _)) = send_to(&session) { - let _ = session.send_control(&session.state.read().unwrap(), send, PACKET_TYPE_NOISE_KK_PATTERN_2, &message2); + let result = initiate_rekey(&self.0, &session, send, current_time); + if result.is_ok() { + app.event_log(LogEvent::ServiceKKStart(&session), current_time); + } + result.unwrap_or(retry_next) + } else { + retry_next } - app.event_log(LogEvent::ReceiveValidKK1(&session), current_time); - return Ok(ReceiveResult::Session(session, SessionEvent::Control)); + } else { + *timeout } } - } - Err(byzantine_fault!(FaultType::FailedAuthentication, false)) - } - PACKET_TYPE_NOISE_KK_PATTERN_2 => { - app.event_log(LogEvent::ReceiveUncheckedKK2, current_time); - let message = &mut message[..NoiseKKPattern1or2::SIZE]; - let noise_pattern2: &NoiseKKPattern1or2 = byte_array_as_proto_buffer(message); - - if let NoiseKKPattern1 { new_key_id, noise_e_secret, noise_ck, noise_h_pskep, .. } = &state.outgoing_offer { - // Noise process pattern2 e token. - let mut noise_ee = Secret::new(); - let mut noise_se = Secret::new(); - if let Some(bob_e) = from_bytes_agreement::(&noise_pattern2.noise_e, noise_e_secret, noise_ee.as_mut()) { - if context.0.static_keypair.agree(&bob_e, noise_se.as_mut()) { - let sha512 = &mut Application::Hash::new(); - let hmac = &mut Application::HmacHash::new(); - let mut noise_ck = noise_ck.clone(); - let noise_h_pskepe = mix_hash(sha512, noise_h_pskep, bob_e.as_bytes()); - noise_ck.mix_key(hmac, bob_e.as_bytes()); - // Noise process pattern2 ee token. - noise_ck.mix_key(hmac, noise_ee.as_ref()); - drop(noise_ee); - // Noise process pattern2 se token. - let noise_k_pskessseese = noise_ck.mix_key_initialize_key(hmac, noise_se.as_ref()); - drop(noise_se); - // Noise process pattern2 payload. - let (is_auth, noise_h_pskepep) = decrypt_and_hash::( - sha512, - &noise_k_pskessseese, - &noise_h_pskepe, - packet_type, - 0, - &mut message[NoiseKKPattern1or2::ENC_START..NoiseKKPattern1or2::AUTH_END], - ); - let noise_pattern2: &NoiseKKPattern1or2 = byte_array_as_proto_buffer(message); - if let (true, Some(remote_key_id)) = (is_auth, NonZeroU32::new(u32::from_ne_bytes(noise_pattern2.key_id))) { - // Bob fully authenticated. - // Alice finished Noise KKpsk0 handshake. - let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_pskepep); - let new_ratchet_state = - RatchetState::new_nonempty(rk, rf, NonZeroU64::new(state.ratchet_states[0].chain_len() + 1).unwrap()); - let result = app.save_ratchet_state( - &session.remote_static_key, - &session.application_data, - [&state.ratchet_states[0], &state.ratchet_states[1]], - [&new_ratchet_state, &RatchetState::Null], - current_time, - ); - if let Err(e) = result { - drop(state); - drop(kex_lock); - return Err(ReceiveError::RatchetIoError(e)); - } - let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(hmac, LABEL_KEX_KEY, &noise_h_pskepep); - - let new_key_id = *new_key_id; + // If there's an outstanding attempt to open a session, retransmit this + // periodically in case the initial packet doesn't make it. + NoiseXKPattern1or3(handshake_state) => { + if let Some(ts) = process_timer(&handshake_state.next_retry_time, App::RETRY_INTERVAL_MS, current_time) { + ts + } else { + // We have to eventually time out NoiseXKPattern3 because of unreliable network conditions. + if handshake_state.timeout <= current_time { drop(state); + let _kex_lock = session.state_machine_lock.lock().unwrap(); let mut state = session.state.write().unwrap(); - let next_key_index = state.current_key ^ 1; - state.current_key = next_key_index; - if let Some(key) = state.cipher_states[next_key_index].as_ref() { - context.0.session_map.write().unwrap().remove(&key.local_key_id); + let ratchet_state = state.ratchet_states.clone(); + // Since we dropped the lock we must re-check if we are in the correct state. + if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { + if handshake_state.timeout <= current_time { + app.event_log(LogEvent::ServiceXKTimeout(&session), current_time); + handshake_state.reinitialize( + &session, + &ratchet_state, + &mut self.0.session_map.write().unwrap(), + &mut self.0.rng.lock().unwrap(), + current_time, + ); + } } - session - .kex_receive_cipher - .lock() - .unwrap() - .replace(Application::AeadDec::new(kex_key_b2a.as_ref())); - session - .kex_send_cipher - .lock() - .unwrap() - .replace(Application::AeadEnc::new(kex_key_a2b.as_ref())); - state.ratchet_states[1] = RatchetState::Null; - state.ratchet_states[0] = new_ratchet_state.clone(); - - state.cipher_states[next_key_index].replace(SessionKey::new( - hmac, - noise_ck, - new_key_id, - remote_key_id, - session.send_counter.load(Ordering::Relaxed), - false, - )); - state.outgoing_offer = KeyConfirm { - next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), - timeout: current_time.saturating_add(Application::EXPIRATION_TIMEOUT_MS), - }; - drop(state); - drop(kex_lock); - // Let Bob know we got the key. - if let Some((send, _)) = send_to(&session) { - let _ = session.send_control(&session.state.read().unwrap(), send, PACKET_TYPE_KEY_CONFIRM, &[]); + } else if let Some((mut send, mut mtu)) = send_to(&session) { + mtu = mtu.max(MIN_TRANSPORT_MTU); + match &handshake_state.offer { + NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_message, noise_message_len, message_id, .. } => { + app.event_log(LogEvent::ServiceXK1Resend(&session), current_time); + // We are in state NoiseXKPattern1 so resend noise_pattern1. + send_with_fragmentation( + &mut send, + mtu, + &mut noise_message.clone()[..*noise_message_len], + PACKET_TYPE_NOISE_XK_PATTERN_1, + None, + *message_id, + None::<&App::PrpEnc>, + ); + } + NoiseXKAliceHandshakeState::NoiseXKPattern3 { noise_message, noise_message_len, .. } => { + app.event_log(LogEvent::ServiceXK3Resend(&session), current_time); + send_with_fragmentation( + &mut send, + mtu, + &mut noise_message.clone()[..*noise_message_len], + PACKET_TYPE_NOISE_XK_PATTERN_3, + state.cipher_states[0].as_ref().map(|k| k.remote_key_id), + 0, + Some(&session.header_send_cipher), + ); + } } - app.event_log(LogEvent::ReceiveValidKK2(&session), current_time); - return Ok(ReceiveResult::Session(session, SessionEvent::Control)); } + retry_next } } - // Bob failed authentication so according to Noise we must terminate this - // handshake. - // This should not happen in practice since this packet will have already passed - // authentication under the current key. - session.expire(); - Err(byzantine_fault!(FaultType::FailedAuthentication, false)) - } else { - drop(state); - drop(kex_lock); - Ok(ReceiveResult::Session(session, SessionEvent::Control)) - } + NoiseKKPattern1 { next_retry_time, timeout, noise_message, .. } | NoiseKKPattern2 { next_retry_time, timeout, noise_message, .. } => { + if let Some(ts) = process_timer(next_retry_time, App::RETRY_INTERVAL_MS, current_time) { + ts + } else { + if *timeout <= current_time { + app.event_log(LogEvent::ServiceKKTimeout(&session), current_time); + next_retry_time.store(i64::MAX, Ordering::Relaxed); + drop(state); + session.expire_inner(&self.0, &mut session_queue); + } else { + let packet_type = if let NoiseKKPattern1 { .. } = &state.outgoing_offer { + app.event_log(LogEvent::ServiceKK1Resend(&session), current_time); + PACKET_TYPE_NOISE_KK_PATTERN_1 + } else { + app.event_log(LogEvent::ServiceKK2Resend(&session), current_time); + PACKET_TYPE_NOISE_KK_PATTERN_2 + }; + if let Some((send, _)) = send_to(&session) { + let _ = session.send_control(&state, send, packet_type, noise_message); + } + } + retry_next + } + } + KeyConfirm { next_retry_time, timeout, .. } => { + if let Some(ts) = process_timer(next_retry_time, App::RETRY_INTERVAL_MS, current_time) { + ts + } else { + if *timeout <= current_time { + app.event_log(LogEvent::ServiceKeyConfirmTimeout(&session), current_time); + next_retry_time.store(i64::MAX, Ordering::Relaxed); + drop(state); + session.expire_inner(&self.0, &mut session_queue); + } else { + app.event_log(LogEvent::ServiceKeyConfirmResend(&session), current_time); + if let Some((send, _)) = send_to(&session) { + let _ = session.send_control(&state, send, PACKET_TYPE_KEY_CONFIRM, &[]); + } + } + retry_next + } + } + Null => retry_next, + }; + session_queue.change_priority(queue_idx, Reverse(next_timer)); } - _ => Err(byzantine_fault!(FaultType::InvalidPacket, false)), - }; + drop(session_queue); + + self.0 + .unassociated_defrag_cache + .lock() + .unwrap() + .check_for_expiry(App::INITIAL_OFFER_TIMEOUT_MS, current_time); + self.0.unassociated_handshake_states.service(current_time); + + next_service_time + } } impl Session { - /// This can only fail with `MaxKeyLifetimeExceeded` or `SessionNotEstablished`. - fn send_control( - &self, - state: &SessionMutableState, - send: impl FnOnce(&mut [u8]) -> bool, - packet_type: u8, - packet: &[u8], - ) -> Result<(), SendError> { - let key = state.cipher_states[state.current_key].as_ref().ok_or(SendError::SessionNotEstablished)?; - let counter = self.get_next_outgoing_counter()?; - let mut c = self.kex_send_cipher.lock().unwrap(); - let (mut fragment, len) = encrypt_control( - c.as_mut().ok_or(SendError::SessionNotEstablished)?, - &self.header_send_cipher, - packet_type, - counter, - key.remote_key_id.get(), - packet, - ); - send(&mut fragment[..len]); - Ok(()) - } - /// Check whether this session is established. - pub fn established(&self) -> bool { - let state = self.state.read().unwrap(); - !matches!(&state.outgoing_offer, OfferStateMachine::NoiseXKPattern1or3(_) | OfferStateMachine::Null) - } - /// The static public key of the remote peer. - pub fn remote_s_public_key(&self) -> &Application::PublicKey { - &self.remote_static_key - } - /// The current ratchet state of this session. - /// The returned values are sensitive and should be securely erased before being dropped. - pub fn ratchet_states(&self) -> [RatchetState; 2] { - let state = self.state.read().unwrap(); - state.ratchet_states.clone() - } + /// + ///// The current ratchet state of this session. + ///// The returned values are sensitive and should be securely erased before being dropped. + //pub fn ratchet_states(&self) -> [RatchetState; 2] { + // let state = self.state.read().unwrap(); + // state.ratchet_states.clone() + //} /// The current ratchet count of this session. - pub fn ratchet_count(&self) -> u64 { - self.state.read().unwrap().ratchet_states[0].chain_len() - } + //pub fn ratchet_count(&self) -> u64 { + // self.state.read().unwrap(). + //} /// 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. - pub fn expire(&self) { - if let Some(context) = self.context.upgrade() { - self.expire_inner(&context, &mut context.session_queue.lock().unwrap()); - } + //pub fn expire(&self) { + // if let Some(context) = self.context.upgrade() { + // self.expire_inner(&context, &mut context.session_queue.lock().unwrap()); + // } + //} + //fn expire_inner( + // &self, + // context: &Arc>, + // session_queue: &mut IndexedBinaryHeap>, Reverse>, + //) { + // // Prevent this session from being updated. + // session_queue.remove(self.queue_idx); + // self.session_has_expired.store(true, Ordering::Relaxed); + // let _kex_lock = self.state_machine_lock.lock().unwrap(); + // let mut state = self.state.write().unwrap(); + // let mut session_map = context.session_map.write().unwrap(); + // for key in &state.cipher_states { + // if let Some(pre_id) = key.as_ref().map(|k| k.local_key_id) { + // session_map.remove(&pre_id); + // } + // } + // use OfferStateMachine::*; + // match &state.outgoing_offer { + // NoiseXKPattern1or3(handshake_state) => session_map.remove(&handshake_state.local_key_id), + // NoiseKKPattern1 { new_key_id, .. } => session_map.remove(new_key_id), + // _ => None, + // }; + // state.outgoing_offer = OfferStateMachine::Null; + //} + /// Check whether this session is established. + pub fn established(&self) -> bool { + let state = self.state.read().unwrap(); + !matches!(&state.beta, ZetaAutomata::A1(_) | ZetaAutomata::A3 {..} | ZetaAutomata::Null) } - fn expire_inner( - &self, - context: &Arc>, - session_queue: &mut IndexedBinaryHeap>, Reverse>, - ) { - // Prevent this session from being updated. - session_queue.remove(self.queue_idx); - self.session_has_expired.store(true, Ordering::Relaxed); - let _kex_lock = self.state_machine_lock.lock().unwrap(); - let mut state = self.state.write().unwrap(); - let mut session_map = context.session_map.write().unwrap(); - for key in &state.cipher_states { - if let Some(pre_id) = key.as_ref().map(|k| k.local_key_id) { - session_map.remove(&pre_id); - } - } - use OfferStateMachine::*; - match &state.outgoing_offer { - NoiseXKPattern1or3(handshake_state) => session_map.remove(&handshake_state.local_key_id), - NoiseKKPattern1 { new_key_id, .. } => session_map.remove(new_key_id), - _ => None, - }; - state.outgoing_offer = OfferStateMachine::Null; - } - - /// Get the next outgoing counter value. - fn get_next_outgoing_counter(&self) -> Result { - if self.session_has_expired.load(Ordering::Relaxed) { - Err(SendError::SessionExpired) - } else { - let counter = self.send_counter.fetch_add(1, Ordering::Relaxed); - if counter > THREAD_SAFE_COUNTER_HARD_EXPIRE { - // Because this thread sets the flag itself it will never be able to increment the - // counter again. - // For that reason the other atomic orderings can be `Relaxed`. - self.session_has_expired.store(true, Ordering::SeqCst) - } - Ok(counter) - } - } - /// Check the receive window without mutating state. - fn check_receive_window(&self, counter: u64) -> bool { - let slot = &self.counter_antireplay_window[(counter as usize) % self.counter_antireplay_window.len()]; - let counter = counter.wrapping_add(1); - let prev_counter = slot.load(Ordering::Relaxed); - prev_counter < counter && counter.wrapping_sub(prev_counter) <= COUNTER_WINDOW_MAX_SKIP_AHEAD - } - /// Update the receive window, returning true if the packet is still valid. - /// This should only be called after the packet is authenticated. - fn update_receive_window(&self, counter: u64) -> bool { - let slot = &self.counter_antireplay_window[(counter as usize) % self.counter_antireplay_window.len()]; - let counter = counter.wrapping_add(1); - let prev_counter = slot.fetch_max(counter, Ordering::Relaxed); - prev_counter < counter && counter.wrapping_sub(prev_counter) <= COUNTER_WINDOW_MAX_SKIP_AHEAD + /// The static public key of the remote peer. + pub fn remote_static_key(&self) -> &Application::PublicKey { + &self.s_remote } } -impl Drop for Session { - fn drop(&mut self) { - if let Some(context) = self.context.upgrade() { - self.expire_inner(&context, &mut context.session_queue.lock().unwrap()); - } - } -} - -impl NoiseXKAliceHandshake { - /// Can only fail with `OpenError::InvalidPublicKey` because of remote_s_public_key. - /// Corresponds to Noise `Initialize`. - fn initialize( - local_key_id: NonZeroU32, - remote_s_public_key: &Application::PublicKey, - ratchet_state: &[RatchetState; 2], - rng: &mut Application::Rng, - ) -> Result< - ( - NoiseXKAliceHandshakeState, - Secret, - Secret, - ), - OpenError, - > { - let mut message = [0u8; NoiseXKPattern1::MAX_SIZE]; - let sha512 = &mut Application::Hash::new(); - let hmac = &mut Application::HmacHash::new(); - // Start of Noise XKhfs+psk2 pattern1. - let noise_pattern1: &mut NoiseXKPattern1 = byte_array_as_proto_buffer_mut(&mut message); - let noise_e_secret = Application::KeyPair::generate(rng); - let noise_e1_secret = pqc_kyber::keypair(rng); - noise_pattern1.alice_key_id = local_key_id.get().to_ne_bytes(); - noise_pattern1.noise_e = *noise_e_secret.public_key_bytes(); - noise_pattern1.noise_e1 = noise_e1_secret.public; - // Noise process prologue. - let noise_h = mix_hash( - sha512, - &INITIAL_H, - &message[NoiseXKPattern1::PROLOGUE_START..NoiseXKPattern1::PROLOGUE_END], - ); - let noise_h = mix_hash(sha512, &noise_h, remote_s_public_key.as_bytes()); - // Noise process pattern1 e token. - let mut noise_ck = SymmetricState::new(INITIAL_H); - let noise_h_e = mix_hash(sha512, &noise_h, noise_e_secret.public_key_bytes()); - noise_ck.mix_key(hmac, noise_e_secret.public_key_bytes()); - // Noise process pattern1 es token. - let mut noise_es = Secret::new(); - if !noise_e_secret.agree(remote_s_public_key, noise_es.as_mut()) { - return Err(OpenError::InvalidPublicKey); - } - let noise_k_es = noise_ck.mix_key_initialize_key(hmac, noise_es.as_ref()); - drop(noise_es); - // Noise process pattern1 e1 token. - let noise_h_ee1 = encrypt_and_hash::( - sha512, - &noise_k_es, - &noise_h_e, - PACKET_TYPE_NOISE_XK_PATTERN_1, - 0, - &mut message[NoiseXKPattern1::E1_ENC_START..NoiseXKPattern1::P_ENC_START], - ); - // Noise process pattern1 payload. - let noise_pattern1: &mut NoiseXKPattern1 = byte_array_as_proto_buffer_mut(&mut message); - let mut idx = 0; - for rs in ratchet_state { - if let Some(rf) = rs.fingerprint() { - let next_idx = idx + RATCHET_SIZE; - noise_pattern1.payload[idx..next_idx].copy_from_slice(rf); - idx = next_idx; - } - } - let p_auth_end = NoiseXKPattern1::P_ENC_START + idx + AES_GCM_TAG_SIZE; - let noise_message_len = p_auth_end + ChallengeResponse::SIZE; - - let noise_h_ee1p = encrypt_and_hash::( - sha512, - &noise_k_es, - &noise_h_ee1, - PACKET_TYPE_NOISE_XK_PATTERN_1, - 1, - &mut message[NoiseXKPattern1::P_ENC_START..p_auth_end], - ); - drop(noise_k_es); - let (header_b2a_key, header_a2b_key) = noise_ck.get_ask2(hmac, LABEL_HEADER_KEY, &noise_h_ee1p); - let message_id = u64::from_be_bytes(message[p_auth_end - 8..p_auth_end].try_into().unwrap()); - - message[noise_message_len - CHALLENGE_POW_SIZE..noise_message_len].copy_from_slice(&rng.next_u64().to_ne_bytes()); - Ok(( - NoiseXKAliceHandshakeState::NoiseXKPattern1 { - noise_h_ee1p, - noise_e_secret, - noise_e1_secret: Secret(noise_e1_secret.secret), - noise_ck_es: noise_ck, - noise_message_len, - noise_message: message, - message_id, - }, - header_a2b_key, - header_b2a_key, - )) - } - /// Should not fail unless Bob's public key is adversarial. - fn reinitialize( - &mut self, - session: &Arc>, - ratchet_state: &[RatchetState; 2], - session_map: &mut HashMap>, bool)>, - rng: &mut Application::Rng, - current_time: i64, - ) -> bool { - let local_key_id = generate_key_id(session_map, rng); - if let Ok((offer, a2b_header_key, b2a_header_key)) = Self::initialize(local_key_id, &session.remote_static_key, ratchet_state, rng) { - self.timeout = current_time.saturating_add(Application::INITIAL_OFFER_TIMEOUT_MS); - session_map.remove(&self.local_key_id); - session_map.insert(local_key_id, (Arc::downgrade(session), false)); - self.local_key_id = local_key_id; - self.offer = offer; - session.header_send_cipher.reset(a2b_header_key.as_ref()); - session.header_receive_cipher.reset(b2a_header_key.as_ref()); - true - } else { - false - } - } -} - -/// Create the normal state of the offer state machine, with the correct timestamps. -fn new_normal_state(rand: u64, current_time: i64) -> OfferStateMachine { - OfferStateMachine::Normal { - timeout: current_time - .saturating_add(Application::REKEY_AFTER_TIME_MS) - .saturating_sub(rand as i64 % Application::REKEY_AFTER_TIME_MAX_JITTER_MS), - } -} -/// Get a timestamp of when this timer should trigger next, or None if it should trigger now. -fn process_timer(timer: &AtomicI64, wait_time: i64, current_time: i64) -> Option { - let ts = timer.load(Ordering::Relaxed); - if ts <= current_time && timer.fetch_max(ts.saturating_add(wait_time), Ordering::Relaxed) == ts { - None - } else { - Some(ts) - } -} - -/// Corresponds to Noise `EncryptAndHash`. -fn encrypt_and_hash( - sha512: &mut Application::Hash, - noise_k: &Secret, - noise_h: &[u8; HASHLEN], - packet_type: u8, - noise_k_uses: u64, - message: &mut [u8], -) -> [u8; HASHLEN] { - let auth_start = message.len() - AES_GCM_TAG_SIZE; - let mut gcm = Application::AeadEnc::new(noise_k.as_ref()); - // Encrypt and add authentication tag. - gcm.set_iv(&create_message_nonce(packet_type, INIT_COUNTER + noise_k_uses)); - gcm.set_aad(noise_h); - if auth_start > 0 { - gcm.encrypt_in_place(&mut message[..auth_start]); - } - gcm.finish_encrypt((&mut message[auth_start..]).try_into().unwrap()); - mix_hash(sha512, noise_h, message) -} -/// Corresponds to Noise `DecryptAndHash`. -fn decrypt_and_hash( - sha512: &mut Application::Hash, - noise_k: &Secret, - noise_h: &[u8; HASHLEN], - packet_type: u8, - noise_k_uses: u64, - message: &mut [u8], -) -> (bool, [u8; HASHLEN]) { - let auth_start = message.len() - AES_GCM_TAG_SIZE; - let noise_h_c = mix_hash(sha512, noise_h, message); - let mut gcm = Application::AeadDec::new(noise_k.as_ref()); - gcm.set_iv(&create_message_nonce(packet_type, INIT_COUNTER + noise_k_uses)); - gcm.set_aad(noise_h); - if auth_start > 0 { - gcm.decrypt_in_place(&mut message[..auth_start]); - } - (gcm.finish_decrypt((&message[auth_start..]).try_into().unwrap()), noise_h_c) -} -/// Encrypt a standardized control packet. -fn encrypt_control( - c: &mut impl AesGcmEnc, - header_cipher: &impl AesEnc, - packet_type: u8, - counter: u64, - remote_key_id: u32, - packet: &[u8], -) -> ([u8; CONTROL_PACKET_MAX_SIZE], usize) { - let mut fragment = [0u8; CONTROL_PACKET_MAX_SIZE]; - let fragment_len = packet.len() + HEADER_SIZE + AES_GCM_TAG_SIZE; - - c.set_iv(&create_message_nonce(packet_type, counter)); - if !packet.is_empty() { - fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE].copy_from_slice(packet); - c.encrypt_in_place(&mut fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE]); - } - c.finish_encrypt((&mut fragment[fragment_len - AES_GCM_TAG_SIZE..fragment_len]).try_into().unwrap()); - set_packet_header(&mut fragment, 1, 0, packet_type, remote_key_id, counter); - header_cipher.encrypt_in_place((&mut fragment[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]).try_into().unwrap()); - (fragment, fragment_len) -} -fn decrypt_control<'a, IoError>( - c: &mut impl AesGcmDec, - packet_type: u8, - counter: u64, - fragment: &'a mut [u8], -) -> Result<&'a mut [u8], ReceiveError> { - let fragment_len = fragment.len(); - if !(CONTROL_PACKET_MIN_SIZE..=CONTROL_PACKET_MAX_SIZE).contains(&fragment_len) { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - c.set_iv(&create_message_nonce(packet_type, counter)); - c.decrypt_in_place(&mut fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE]); - if !c.finish_decrypt((&fragment[fragment_len - AES_GCM_TAG_SIZE..fragment_len]).try_into().unwrap()) { - // This can occur naturally if one of the remote peers resent a - // control packet that got delayed and arrived out of order. - return Err(byzantine_fault!(FaultType::FailedAuthentication, true)); - } - Ok(&mut fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE]) -} - -fn set_packet_header(packet: &mut [u8], fragment_count: u8, fragment_no: u8, packet_type: u8, remote_key_id: u32, counter_or_id: u64) { - debug_assert!(packet.len() >= MIN_PACKET_SIZE); - debug_assert!(fragment_count > 0); - debug_assert!(fragment_count <= MAX_FRAGMENTS as u8); - debug_assert!(fragment_no < MAX_FRAGMENTS as u8); - debug_assert_eq!((packet_type << 1) >> 1, packet_type); - // [0..4] recipient key id - // -- start AES(ck_es * h_e_e1_p) encrypted block -- - // [4] fragment count (1..255) - // [5] fragment number (0..254) - // [6] reserved zero - // -- start of AES-GCM Nonce -- - // [7] packet type - // [8..16] 64-bit counter or packet id (big endian) - packet[4..16].copy_from_slice(&create_message_nonce(packet_type, counter_or_id)); - packet[0..4].copy_from_slice(&remote_key_id.to_ne_bytes()); - packet[4] = fragment_count; - packet[5] = fragment_no; - packet[6] = 0; -} -/// 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. -fn create_message_nonce(packet_type: u8, counter: u64) -> [u8; AES_GCM_IV_SIZE] { - let mut ret = [0u8; AES_GCM_IV_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 -} -/// returns `(fragment_count, fragment_no, packet_type, counter, header_nonce)`. -fn parse_packet_header(packet: &[u8]) -> (u8, u8, u8, u64, [u8; 10]) { - let header_nonce = packet[6..16].try_into().unwrap(); - let counter = packet[8..16].try_into().unwrap(); - // We intentionally ignore the version number for future revisions. - (packet[4], packet[5], packet[7], u64::from_be_bytes(counter), header_nonce) -} - -/// Break a packet into fragments and send them all. -/// -/// The contents of packet[] are mangled during this operation, so it should be discarded after. -/// This is only used for key exchange and control packets. For data packets this is done inline -/// for better performance with encryption and fragmentation happening at the same time. -fn send_with_fragmentation( - send: &mut impl FnMut(&mut [u8]) -> bool, - mtu: usize, - packet: &mut [u8], - packet_type: u8, - remote_key_id: Option, - counter_or_id: u64, - header_cipher: Option<&impl AesEnc>, -) -> bool { - let packet_len = packet.len(); - let fragment_count = (packet_len.saturating_add(mtu - 1)) / mtu; // integer ceiling divide - debug_assert!(fragment_count <= MAX_FRAGMENTS); - let mut fragment_start = 0; - let mut fragment_end = packet_len.min(mtu); - let mut fragment_no = 0; - loop { - let fragment = &mut packet[fragment_start..fragment_end]; - set_packet_header( - fragment, - fragment_count as u8, - fragment_no as u8, - packet_type, - remote_key_id.map_or(0, |n| n.get()), - counter_or_id, - ); - if let Some(hcc) = header_cipher { - hcc.encrypt_in_place((&mut fragment[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]).try_into().unwrap()); - } - if !send(fragment) { - return false; - } - fragment_no += 1; - if fragment_no < fragment_count { - fragment_start = fragment_end - HEADER_SIZE; - fragment_end = (fragment_start.saturating_add(mtu)).min(packet_len); - } else { - break; - } - } - true -} - -/// Assemble a series of fragments into a buffer and return the length of the assembled packet in -/// bytes. -/// -/// This is also only used for key exchange and control packets. For data packets decryption and -/// assembly happen in one pass for better performance. -fn assemble_fragments_into(fragments: &[A::IncomingPacketBuffer], d: &mut [u8]) -> Result> { - let mut l = 0; - for i in 0..fragments.len() { - let mut ff = fragments[i].as_ref(); - if i > 0 { - ff = &ff[HEADER_SIZE..]; - } - let j = l + ff.len(); - if j > d.len() { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - d[l..j].copy_from_slice(ff); - l = j; - } - Ok(l) -} -/// Generate a random local key id that is currently unused. -fn generate_key_id( - session_map: &HashMap>, bool)>, - rng: &mut Application::Rng, -) -> NonZeroU32 { - loop { - if let Some(local_key_id) = NonZeroU32::new(rng.next_u32()) { - if !session_map.contains_key(&local_key_id) { - return local_key_id; - } - } - } -} - -impl SessionKey { - fn new( - hmac: &mut Application::HmacHash, - ck: SymmetricState, - local_key_id: NonZeroU32, - remote_key_id: NonZeroU32, - current_counter: u64, - is_bob: bool, - ) -> Self { - let (b2a, a2b) = ck.split(hmac); - let (receive_key, send_key) = if is_bob { - (&a2b, &b2a) - } else { - (&b2a, &a2b) - }; - let send_cipher_pool = std::array::from_fn(|_| Mutex::new(Application::AeadEnc::new(send_key.as_ref()))); - let receive_cipher_pool = std::array::from_fn(|_| Mutex::new(Application::AeadDec::new(receive_key.as_ref()))); - Self { - local_key_id, - remote_key_id, - send_cipher_pool, - receive_cipher_pool, - rekey_at_counter: current_counter.saturating_add(Application::REKEY_AFTER_USES), - expire_at_counter: current_counter.saturating_add(Application::EXPIRE_AFTER_USES), - } - } - - fn get_send_cipher(&self, counter: u64) -> Result, SendError> { - if counter < self.expire_at_counter { - Ok(self.send_cipher_pool[(counter as usize) % self.send_cipher_pool.len()].lock().unwrap()) - } else { - Err(SendError::SessionExpired) - } - } - - fn get_receive_cipher(&self, counter: u64) -> MutexGuard { - let idx = (counter as usize) % self.receive_cipher_pool.len(); - self.receive_cipher_pool[idx].lock().unwrap() - } -} - -/// MixHash to update 'h' during negotiation. -fn mix_hash(hasher: &mut impl Sha512, h: &[u8; HASHLEN], m: &[u8]) -> [u8; HASHLEN] { - let mut output = [0u8; HASHLEN]; - hasher.reset(); - hasher.update(h); - hasher.update(m); - hasher.finish(&mut output); - output -} -/// Check if the proof of work attached to the first message contains the correct number of leading -/// zeros. -fn verify_pow(hasher: &mut Application::Hash, response: &[u8]) -> bool { - if Application::PROOF_OF_WORK_BIT_DIFFICULTY == 0 { - return true; - } - hasher.reset(); - hasher.update(response); - let mut output = [0u8; HASHLEN]; - hasher.finish(&mut output); - let n = u32::from_be_bytes(output[..4].try_into().unwrap()); - n.leading_zeros() >= Application::PROOF_OF_WORK_BIT_DIFFICULTY -} -fn from_bytes_agreement( - public: &[u8], - private: &Application::KeyPair, - output: &mut [u8; P384_ECDH_SHARED_SECRET_SIZE], -) -> Option { - Application::PublicKey::from_bytes(public.try_into().unwrap()).and_then(|e| private.agree(&e, output).then_some(e)) -} From 8ba0697337efdf4e5eee9e9e498c217944c40268 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 7 Aug 2023 20:04:07 -0400 Subject: [PATCH 17/50] first compile --- src/crypto/aes.rs | 2 +- src/frag_cache.rs | 26 +- src/fragged.rs | 37 +-- src/proto.rs | 2 + src/result.rs | 10 +- src/symmetric_state.rs | 2 +- src/zeta.rs | 738 ++++++++++++++++++++++++++--------------- src/zssp.rs | 607 ++++++++++++++++++--------------- 8 files changed, 827 insertions(+), 597 deletions(-) diff --git a/src/crypto/aes.rs b/src/crypto/aes.rs index baec450..2e5f36a 100644 --- a/src/crypto/aes.rs +++ b/src/crypto/aes.rs @@ -47,7 +47,7 @@ pub trait AesGcmEncContext { } pub trait AesGcmDecContext { - fn decrypt(&mut self, input: &[u8], output: &mut [u8]); + fn decrypt_in_place(&mut self, data: &mut [u8]); #[must_use] fn finish(&mut self, tag: &[u8; AES_GCM_TAG_SIZE]) -> bool; diff --git a/src/frag_cache.rs b/src/frag_cache.rs index f0c9c27..fab7a78 100644 --- a/src/frag_cache.rs +++ b/src/frag_cache.rs @@ -152,24 +152,12 @@ impl UnassociatedFragCache { self.frags[frag_idx].write(fragment); if entry.fragment_have == 1u64.wrapping_shl(fragment_count as u32) - 1 { - ret_assembled.empty(); - ret_assembled.1 = fragment_count as usize; + debug_assert!(ret_assembled.is_empty()); let start_idx = entry.frags_idx as usize; - // This is a ring buffer copy into ret_assembled. - // The fragments are moved into the `ret_assembled` container and returned. - // That container will drop them when it is dropped. - if start_idx + ret_assembled.1 <= self.frags.len() { - // Copy does not occur at the buffer's boundary - unsafe { - std::ptr::copy_nonoverlapping(&self.frags[start_idx], &mut ret_assembled.0[0], ret_assembled.1); - } - } else { - // Copy does occur at the buffer's boundary - let first_chunk_size = self.frags.len() - start_idx; - let second_chunk_size = ret_assembled.1 - first_chunk_size; - unsafe { - std::ptr::copy_nonoverlapping(&self.frags[start_idx], &mut ret_assembled.0[0], first_chunk_size); - std::ptr::copy_nonoverlapping(&self.frags[0], &mut ret_assembled.0[first_chunk_size], second_chunk_size); + unsafe { + for i in start_idx..start_idx + fragment_count { + + ret_assembled.push(self.frags[i % self.frags.len()].assume_init_read()) } } self.invalidate::(idx); @@ -267,7 +255,7 @@ fn test_cache() { } in_progress.push((i, fragment_count as u8, packet)); } else { - assembled.empty(); + assembled.clear(); let drop = xorshift64_random() as usize % (2 * fragment_count); for j in 0..fragment_count { if drop != j { @@ -297,7 +285,7 @@ fn test_cache() { for _ in 0..((xorshift64_random() as usize % packet.len()) + 1) { let (no, fragment) = packet.swap_remove(xorshift64_random() as usize % packet.len()); - assembled.empty(); + assembled.clear(); let mut nonce = [0; 12]; nonce[..4].copy_from_slice(&id.to_be_bytes()); cache.assemble(&nonce, 0, fragment.len(), fragment, no as usize, fragment_count as usize, 1000, time, &mut assembled); diff --git a/src/fragged.rs b/src/fragged.rs index 145b5d8..5cb1a49 100644 --- a/src/fragged.rs +++ b/src/fragged.rs @@ -6,40 +6,13 @@ * https://www.zerotier.com/ */ +use arrayvec::ArrayVec; use std::mem::{needs_drop, zeroed, MaybeUninit}; -use std::ptr::slice_from_raw_parts; use crate::crypto::aes::AES_GCM_IV_SIZE; use crate::proto::{MAX_FRAGMENTS, NONCE_SIZE_DIFF}; -pub(crate) struct Assembled(pub(crate) [MaybeUninit; MAX_FRAGMENTS], pub(crate) usize); - -impl Assembled { - pub(crate) fn new() -> Self { - Self(unsafe { MaybeUninit::<[MaybeUninit<_>; MAX_FRAGMENTS]>::uninit().assume_init() }, 0) - } - pub(crate) fn is_empty(&self) -> bool { - self.1 == 0 - } - pub(crate) fn empty(&mut self) { - for i in 0..self.1 { - unsafe { - self.0.get_unchecked_mut(i).assume_init_drop(); - } - } - self.1 = 0; - } -} -impl AsRef<[Fragment]> for Assembled { - fn as_ref(&self) -> &[Fragment] { - unsafe { &*slice_from_raw_parts(self.0.as_ptr().cast::(), self.1) } - } -} -impl Drop for Assembled { - fn drop(&mut self) { - self.empty() - } -} +pub type Assembled = ArrayVec; /// Fast packet defragmenter pub struct Fragged { @@ -94,10 +67,10 @@ impl Fragged { // Setting 'have' to 0 resets the state of this object, and the fragments // are effectively moved into the Assembled<> container and returned. That // container will drop them when it is dropped. - ret_assembled.empty(); - ret_assembled.1 = fragment_count as usize; unsafe { - std::ptr::copy_nonoverlapping(&self.frags[0], &mut ret_assembled.0[0], ret_assembled.1); + for i in 0..fragment_count { + ret_assembled.push(self.frags[i].assume_init_read()); + } } } } diff --git a/src/proto.rs b/src/proto.rs index 3f11bb1..c11f88f 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -23,6 +23,8 @@ pub(crate) const POW_START: usize = COUNTER_SIZE + MAC_SIZE; pub(crate) const CHALLENGE_SIZE: usize = COUNTER_SIZE + MAC_SIZE + POW_SIZE; pub(crate) const DIFFICULTY: u32 = 13; +pub(crate) const HEADERED_CHALLENGE_SIZE: usize = CHALLENGE_SIZE + HEADER_SIZE + KID_SIZE; + /* Fragmentation constants */ /* Header: diff --git a/src/result.rs b/src/result.rs index 4710492..c070bc9 100644 --- a/src/result.rs +++ b/src/result.rs @@ -61,8 +61,8 @@ pub enum FaultType { } /// An error that occurred during the receipt of a given packet. -#[derive(Debug, PartialEq, Eq, Clone, Hash)] -pub enum ReceiveError { +#[derive(Debug)] +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 +104,9 @@ 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), + + IoError(std::io::Error), } macro_rules! byzantine_fault { @@ -158,7 +160,7 @@ pub enum SessionEvent { /// This return value cannot occur after a session is fully established. Rejected, /// The received packet was valid and a data payload was decoded and authenticated. - Data(Vec), + Data, /// The received packet was some authentic protocol control packet. No action needs to be taken. Control, } diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index f2e2a22..591fdb4 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -152,7 +152,7 @@ impl SymmetricState { } /// Corresponds to Noise `EncryptAndHash`. #[must_use] - pub fn encrypt_and_hash_in_place(&mut self, hash: &mut App::Hash, iv: [u8; AES_GCM_IV_SIZE], data: &mut [u8]) -> [u8; AES_GCM_TAG_SIZE] { + pub fn encrypt_and_hash_in_place(&mut self, hash: &mut App::Hash, iv: [u8; AES_GCM_IV_SIZE], data: &mut [u8]) -> [u8; AES_GCM_TAG_SIZE] { let tag = App::Aead::encrypt_in_place(&self.k, &iv, &self.h, data); hash.update(&self.h); hash.update(data); diff --git a/src/zeta.rs b/src/zeta.rs index 9203426..8475cf0 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -2,10 +2,11 @@ use arrayvec::ArrayVec; use rand_core::RngCore; use std::cmp::Reverse; use std::collections::HashMap; +use std::io::Write; use std::num::NonZeroU32; use std::ops::{Deref, DerefMut}; -use std::sync::atomic::{AtomicU64, Ordering, AtomicBool}; -use std::sync::{Arc, Mutex, Weak, RwLock}; +use std::sync::atomic::{AtomicU64, Ordering, AtomicBool, AtomicI64}; +use std::sync::{Arc, Mutex, Weak, RwLock, MutexGuard, RwLockWriteGuard}; use zeroize::Zeroizing; use crate::antireplay::Window; @@ -21,7 +22,7 @@ use crate::crypto::kyber1024::{Kyber1024PrivateKey, KYBER_PUBLIC_KEY_SIZE, KYBER use crate::indexed_heap::BinaryHeapIndex; use crate::proto::*; use crate::ratchet_state::RatchetState; -use crate::result::{byzantine_fault, FaultType, OpenError, ReceiveError, SendError}; +use crate::result::{byzantine_fault, FaultType, OpenError, ReceiveError, SendError, ReceiveOk}; use crate::symmetric_state::SymmetricState; use crate::fragged::Fragged; #[cfg(feature = "logging")] @@ -68,8 +69,8 @@ fn get_counter(session: &Session, state: &MutableSta } /// Corresponds to the Zeta State Machine found in Section 4.1. -pub(crate) struct Session { - //ctx: Weak>, +pub struct Session { + 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. @@ -85,7 +86,9 @@ pub(crate) struct Session { pub(crate) hk_send: App::PrpEnc, pub(crate) hk_recv: App::PrpDec, + /// `session_queue -> state_machine_lock -> state -> session_map` state_machine_lock: Mutex<()>, + /// `session_queue -> state_machine_lock -> state -> session_map` pub(crate) state: RwLock>, /// Pre-computed rekeying value. @@ -99,7 +102,7 @@ pub(crate) struct MutableState { key_index: bool, keys: [DuplexKey; 2], - resend_timer: i64, + resend_timer: AtomicI64, timeout_timer: i64, pub beta: ZetaAutomata, } @@ -190,7 +193,7 @@ impl SymmetricState { self.mix_key(hmac, &pub_key); e_secret } - fn read_e(&mut self, hash: &mut App::Hash, hmac: &mut App::HmacHash, i: &mut usize, packet: &[u8]) -> Option { + fn read_e(&mut self, hash: &mut App::Hash, hmac: &mut App::HmacHash, i: &mut usize, packet: &[u8]) -> Option { let j = *i + P384_PUBLIC_KEY_SIZE; let pub_key = &packet[*i..j]; self.mix_hash(hash, pub_key); @@ -227,9 +230,6 @@ impl MutableState { fn key_mut(&mut self, is_next: bool) -> &mut DuplexKey { &mut self.keys[(self.key_index ^ is_next) as usize] } - pub(crate) fn next_timer(&self) -> i64 { - self.timeout_timer.min(self.resend_timer) - } } fn set_header(packet: &mut [u8], kid_send: u32, nonce: &[u8; AES_GCM_IV_SIZE]) { @@ -265,7 +265,8 @@ fn create_a1_state( let (e1_secret, e1_public) = App::Kem::generate(rng.lock().unwrap().deref_mut()); x1.extend(e1_public); x1.extend([0u8; AES_GCM_IV_SIZE]); - x1.extend(noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, 0), &mut x1[i..])); + let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, 0), &mut x1[i..]); + x1.extend(tag); // Process message pattern 1 payload. let i = x1.len(); if let Some(rf) = ratchet_state1.fingerprint() { @@ -274,7 +275,8 @@ fn create_a1_state( if let Some(Some(rf)) = ratchet_state2.map(|rs| rs.fingerprint()) { x1.try_extend_from_slice(rf).unwrap(); } - x1.extend(noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, 1), &mut x1[i..])); + let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, 1), &mut x1[i..]); + x1.extend(tag); let c = u64::from_be_bytes(x1[x1.len() - 8..].try_into().unwrap()); @@ -325,7 +327,9 @@ pub(crate) fn trans_to_a1( let current_time = app.time(); let queue_idx = session_queue.reserve_index(); - let mut session = Arc::new(Session { + let resend_timer = current_time + App::SETTINGS.resend_time as i64; + let session = Arc::new(Session { + ctx: Arc::downgrade(ctx), session_data, was_bob: false, queue_idx, @@ -340,7 +344,7 @@ pub(crate) fn trans_to_a1( key_creation_counter: 0, key_index: true, keys: [DuplexKey::default(), DuplexKey::default()], - resend_timer: current_time + App::SETTINGS.resend_time as i64, + resend_timer: AtomicI64::new(resend_timer), timeout_timer: current_time + App::SETTINGS.initial_offer_timeout as i64, beta: ZetaAutomata::A1(a1), }), @@ -349,14 +353,16 @@ pub(crate) fn trans_to_a1( hk_send: App::PrpEnc::new((&hk_send[..AES_256_KEY_SIZE]).try_into().unwrap()), hk_recv: App::PrpDec::new((&hk_recv[..AES_256_KEY_SIZE]).try_into().unwrap()), }); - let mut state = session.state.write().unwrap(); - state.key_mut(true).recv.kid = Some(kid_recv); + { + let mut state = session.state.write().unwrap(); + state.key_mut(true).recv.kid = Some(kid_recv); + } session_map.insert(kid_recv, Arc::downgrade(&session)); session_queue.push_reserved( queue_idx, Arc::downgrade(&session), - Reverse(state.next_timer()), + Reverse(resend_timer), ); send(&mut x1, None); @@ -364,12 +370,12 @@ pub(crate) fn trans_to_a1( Ok(session) } /// Corresponds to Algorithm 13 found in Section 5. -pub(crate) fn respond_to_challenge(session: &mut Session, rng: &Mutex, challenge: &[u8; CHALLENGE_SIZE]) { +pub(crate) fn respond_to_challenge(ctx: &Arc>, session: &Session, challenge: &[u8; CHALLENGE_SIZE]) { let mut state = session.state.write().unwrap(); if let ZetaAutomata::A1(a1) = &mut state.beta { let response_start = a1.x1.len() - CHALLENGE_SIZE; respond_to_challenge_in_place::( - rng.lock().unwrap().deref_mut(), + ctx.rng.lock().unwrap().deref_mut(), challenge, (&mut a1.x1[response_start..]).try_into().unwrap(), ); @@ -379,8 +385,7 @@ pub(crate) fn respond_to_challenge(session: &mut Session< pub(crate) fn received_x1_trans( app: &App, ctx: &ContextInner, - remote_address: &impl std::hash::Hash, - n: [u8; AES_GCM_IV_SIZE], + n: &[u8; AES_GCM_IV_SIZE], x1: &mut [u8], send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Result<(), ReceiveError> { @@ -393,12 +398,6 @@ pub(crate) fn received_x1_trans( return Err(byzantine_fault!(InvalidPacket, true)); } - if let Err(challenge) = ctx.challenge.process_hello::(remote_address, (&x1[x1.len() - CHALLENGE_SIZE..]).try_into().unwrap()) { - /// - - return Err(byzantine_fault!(FailedAuth, false)); - } - if &n[AES_GCM_IV_SIZE - 8..] != &x1[x1.len() - 8..] { return Err(byzantine_fault!(FailedAuth, true)); } @@ -442,7 +441,7 @@ pub(crate) fn received_x1_trans( ratchet_state = Some(rs); break; } - Err(e) => return Err(ReceiveError::RatchetIoError(e)), + Err(e) => return Err(ReceiveError::StorageError(e)), } i += RATCHET_SIZE; } @@ -470,7 +469,8 @@ pub(crate) fn received_x1_trans( let mut ekem1_secret = Zeroizing::new([0u8; KYBER_PLAINTEXT_SIZE]); let ekem1 = App::Kem::encapsulate(ctx.rng.lock().unwrap().deref_mut(), (&x1[e1_start..e1_end]).try_into().unwrap(), &mut ekem1_secret).ok_or(byzantine_fault!(FailedAuth, true))?; x2.extend(ekem1); - x2.extend(noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), &mut x2[i..])); + let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), &mut x2[i..]); + x2.extend(tag); noise.mix_key(hmac, ekem1_secret.as_ref()); } // Process message pattern 2 psk2 token. @@ -480,7 +480,8 @@ pub(crate) fn received_x1_trans( let i = x2.len(); x2.extend(kid_recv.get().to_be_bytes()); - x2.extend(noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), &mut x2[i..])); + let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), &mut x2[i..]); + x2.extend(tag); let i = x2.len(); let mut c = 0u64.to_be_bytes(); @@ -515,8 +516,8 @@ pub(crate) fn received_x2_trans( ctx: &Arc>, session: &Arc>, kid: NonZeroU32, - n: [u8; AES_GCM_IV_SIZE], - mut x2: &[u8], + n: &[u8; AES_GCM_IV_SIZE], + x2: &mut [u8], send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Result<(), ReceiveError> { use FaultType::*; @@ -534,122 +535,128 @@ pub(crate) fn received_x2_trans( if Some(kid) != state.key_ref(true).recv.kid { return Err(byzantine_fault!(UnknownLocalKeyId, true)); } - let (_, c) = from_nonce(&n); + let (_, c) = from_nonce(n); if c >= COUNTER_WINDOW_MAX_SKIP_AHEAD || &n[AES_GCM_IV_SIZE - 3..] != &x2[x2.len() - 3..] { return Err(byzantine_fault!(FailedAuth, true)); } let mut result = (|| { - if let ZetaAutomata::A1(a1) = &state.beta { - let mut noise = a1.noise.clone(); - let mut i = 0; - // Process message pattern 2 e token. - let e_remote = noise.read_e(hash, hmac, &mut i, &x2).ok_or(byzantine_fault!(FailedAuth, true))?; - // Process message pattern 2 ee token. - noise.mix_dh(hmac, &a1.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; - let tag = x2[j..k].try_into().unwrap(); - if !noise.decrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), &mut x2[i..j], tag) { - return Err(byzantine_fault!(FailedAuth, true)); + let a1 = if let ZetaAutomata::A1(a1) = &state.beta { + a1 + } else { + return Err(byzantine_fault!(FailedAuth, true)); + }; + let mut noise = a1.noise.clone(); + let mut i = 0; + // Process message pattern 2 e token. + let e_remote = noise.read_e(hash, hmac, &mut i, &x2).ok_or(byzantine_fault!(FailedAuth, true))?; + // Process message pattern 2 ee token. + noise.mix_dh(hmac, &a1.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; + let tag = x2[j..k].try_into().unwrap(); + if !noise.decrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), &mut x2[i..j], tag) { + return Err(byzantine_fault!(FailedAuth, true)); + } + let mut ekem1_secret = Zeroizing::new([0u8; KYBER_PLAINTEXT_SIZE]); + if !a1.e1_secret.decapsulate((&x2[i..j]).try_into().unwrap(), &mut ekem1_secret) { + return Err(byzantine_fault!(FailedAuth, true)); + } + noise.mix_key(hmac, ekem1_secret.as_ref()); + drop(ekem1_secret); + i = j; + // We attempt to decrypt the payload at most three times. First two times with + // the ratchet key Alice remembers, and final time with a ratchet + // key of zero if Alice allows ratchet downgrades. + // The following code is not constant time, meaning we leak to an + // attacker whether or not we downgraded. + // We don't currently consider this sensitive enough information to hide. + let j = i + KID_SIZE; + let k = j + AES_GCM_TAG_SIZE; + 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 mut test_ratchet_key = |ratchet_key| -> Option<(NonZeroU32, SymmetricState)> { + let mut noise = noise.clone(); + let mut payload = payload.clone(); + // Process message pattern 2 psk token. + noise.mix_key_and_hash(hash, hmac, ratchet_key); + // Process message pattern 2 payload. + if !noise.decrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), &mut payload, tag) { + return None; } - let mut ekem1_secret = Zeroizing::new([0u8; KYBER_PLAINTEXT_SIZE]); - if !a1.e1_secret.decapsulate((&x2[i..j]).try_into().unwrap(), &mut ekem1_secret) { - return Err(byzantine_fault!(FailedAuth, true)); + NonZeroU32::new(u32::from_be_bytes(payload)).map(|kid2| (kid2, noise)) + }; + // Check first key. + let mut ratchet_i = 1; + let mut chain_len = state.ratchet_state1.chain_len; + let mut result = test_ratchet_key(state.ratchet_state1.key.as_ref()); + // Check second key. + if result.is_none() { + ratchet_i = 2; + if let Some(rs) = state.ratchet_state2.as_ref() { + chain_len = rs.chain_len; + result = test_ratchet_key(rs.key.as_ref()); } - noise.mix_key(hmac, ekem1_secret.as_ref()); - drop(ekem1_secret); - i = j; - // We attempt to decrypt the payload at most three times. First two times with - // the ratchet key Alice remembers, and final time with a ratchet - // key of zero if Alice allows ratchet downgrades. - // The following code is not constant time, meaning we leak to an - // attacker whether or not we downgraded. - // We don't currently consider this sensitive enough information to hide. - let j = i + KID_SIZE; - let k = j + AES_GCM_TAG_SIZE; - 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 mut test_ratchet_key = |ratchet_key| -> Option<(NonZeroU32, SymmetricState)> { - let mut noise = noise.clone(); - let mut payload = payload.clone(); - // Process message pattern 2 psk token. - noise.mix_key_and_hash(hash, hmac, ratchet_key); - // Process message pattern 2 payload. - if !noise.decrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), &mut payload, tag) { - return None; - } - NonZeroU32::new(u32::from_be_bytes(payload)).map(|kid2| (kid2, noise)) - }; - // Check first key. - let mut ratchet_i = 1; - let mut chain_len = state.ratchet_state1.chain_len; - let mut result = test_ratchet_key(state.ratchet_state1.key.as_ref()); - // Check second key. - if result.is_none() { - ratchet_i = 2; - if let Some(rs) = state.ratchet_state2.as_ref() { - chain_len = rs.chain_len; - result = test_ratchet_key(rs.key.as_ref()); - } - } - // Check zero key. - if result.is_none() && !app.initiator_disallows_downgrade(session) { - chain_len = 0; - result = test_ratchet_key(&[0u8; RATCHET_SIZE]); - if result.is_some() { - // TODO: add some kind of warning callback or signal. - } + } + // Check zero key. + if result.is_none() && !app.initiator_disallows_downgrade(session) { + chain_len = 0; + result = test_ratchet_key(&[0u8; RATCHET_SIZE]); + if result.is_some() { + // TODO: add some kind of warning callback or signal. } + } - let (kid_send, mut noise) = result.ok_or(byzantine_fault!(FailedAuth, true))?; + let (kid_send, mut noise) = result.ok_or(byzantine_fault!(FailedAuth, true))?; - let mut x3 = ArrayVec::::new(); - x3.extend([0u8; HEADER_SIZE]); - // Process message pattern 3 s token. - let i = x3.len(); - x3.extend(ctx.s_secret.public_key_bytes()); - x3.extend(noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 1), &mut x3[i..])); - // Process message pattern 3 se token. - noise.mix_dh(hmac, &ctx.s_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; - // Process message pattern 3 payload. - let i = x3.len(); - x3.try_extend_from_slice(&a1.identity).unwrap(); - x3.extend(noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 0), &mut x3[i..])); + let mut x3 = ArrayVec::::new(); + x3.extend([0u8; HEADER_SIZE]); + // Process message pattern 3 s token. + let i = x3.len(); + x3.extend(ctx.s_secret.public_key_bytes()); + let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 1), &mut x3[i..]); + x3.extend(tag); + // Process message pattern 3 se token. + noise.mix_dh(hmac, &ctx.s_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + // Process message pattern 3 payload. + let i = x3.len(); + x3.try_extend_from_slice(&a1.identity).unwrap(); + let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 0), &mut x3[i..]); + x3.extend(tag); - let new_ratchet_state = create_ratchet_state(hmac, &mut noise, chain_len); + let new_ratchet_state = create_ratchet_state(hmac, &mut noise, chain_len); - let (ratchet_to_preserve, ratchet_to_delete) = if ratchet_i == 1 { - (Some(&state.ratchet_state1), state.ratchet_state2.as_ref()) - } else { - (state.ratchet_state2.as_ref(), Some(&state.ratchet_state1)) - }; - let result = app.save_ratchet_state( - &session.s_remote, - &session.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)); - } + let (ratchet_to_preserve, ratchet_to_delete) = if ratchet_i == 1 { + (Some(&state.ratchet_state1), state.ratchet_state2.as_ref()) + } else { + (state.ratchet_state2.as_ref(), Some(&state.ratchet_state1)) + }; + let result = app.save_ratchet_state( + &session.s_remote, + &session.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::StorageError(e)); + } - let mut kek_recv = Zeroizing::new([0u8; HASHLEN]); - let mut kek_send = Zeroizing::new([0u8; HASHLEN]); - let mut nk_recv = Zeroizing::new([0u8; HASHLEN]); - let mut nk_send = Zeroizing::new([0u8; HASHLEN]); - noise.get_ask(hmac, LABEL_KEX_KEY, &mut kek_recv, &mut kek_send); - noise.split(hmac, &mut nk_recv, &mut nk_send); - let nonce = to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 0); + let mut kek_recv = Zeroizing::new([0u8; HASHLEN]); + let mut kek_send = Zeroizing::new([0u8; HASHLEN]); + let mut nk_recv = Zeroizing::new([0u8; HASHLEN]); + let mut nk_send = Zeroizing::new([0u8; HASHLEN]); + noise.get_ask(hmac, LABEL_KEX_KEY, &mut kek_recv, &mut kek_send); + noise.split(hmac, &mut nk_recv, &mut nk_send); + let nonce = to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 0); - //let identity = identity.clone(); - drop(state); + drop(state); + let resend_timer = { let mut state = session.state.write().unwrap(); state.key_mut(true).send.kid = Some(kid_send); @@ -660,18 +667,28 @@ pub(crate) fn received_x2_trans( state.ratchet_state1 = new_ratchet_state.clone(); let current_time = app.time(); state.key_creation_counter = session.send_counter.load(Ordering::Relaxed); - state.resend_timer = current_time + App::SETTINGS.resend_time as i64; + let resend_timer = current_time + App::SETTINGS.resend_time as i64; + state.resend_timer = AtomicI64::new(resend_timer); state.timeout_timer = current_time + App::SETTINGS.initial_offer_timeout as i64; + let a1 = if let ZetaAutomata::A1(a1) = &state.beta { + a1 + } else { + // This return is unreachable. + return Err(byzantine_fault!(FailedAuth, true)); + }; state.beta = ZetaAutomata::A3 { identity: a1.identity.clone(), x3: x3.clone(), kid_send: kid_send.get(), nonce }; + resend_timer + }; + drop(kex_lock); + ctx.session_queue.lock().unwrap().change_priority(session.queue_idx, Reverse(resend_timer)); - Ok(x3) - } else { - Err(byzantine_fault!(FailedAuth, true)) - } + Ok(x3) })(); - match &mut result { - Err(ReceiveError::ByzantineFault { .. }) => process_timers(app, ctx, session, app.time(), true, false, send), - Ok(mut packet) => send(&mut packet, Some(&session.hk_send)), + match result { + Err(ReceiveError::ByzantineFault { .. }) => { + process_timers(app, ctx, session, app.time(), true, false, send); + } + Ok(ref mut packet) => send(packet, Some(&session.hk_send)), _ => {} } result.map(|_| ()) @@ -680,9 +697,9 @@ pub(crate) fn received_x2_trans( pub(crate) fn received_x3_trans( app: &App, ctx: &Arc>, - zeta: StateB2, + zeta: Arc>, kid: NonZeroU32, - mut x3: Vec, + x3: &mut [u8], send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Result>, ReceiveError> { use FaultType::*; @@ -693,8 +710,8 @@ pub(crate) fn received_x3_trans( if kid != zeta.kid_recv { return Err(byzantine_fault!(UnknownLocalKeyId, true)); } - let mut hash = &mut App::Hash::new(); - let mut hmac = &mut App::HmacHash::new(); + let hash = &mut App::Hash::new(); + let hmac = &mut App::HmacHash::new(); let mut noise = zeta.noise.clone(); let mut i = 0; @@ -758,11 +775,12 @@ pub(crate) fn received_x3_trans( if !ctx.s_secret.agree(&s_remote, &mut noise_kk_ss) { return Err(byzantine_fault!(FailedAuth, true)); } + + let new_ratchet_state = create_ratchet_state(hmac, &mut noise, zeta.ratchet_state.chain_len); let mut nk_recv = Zeroizing::new([0u8; HASHLEN]); let mut nk_send = Zeroizing::new([0u8; HASHLEN]); noise.split(hmac, &mut nk_send, &mut nk_recv); - let new_ratchet_state = create_ratchet_state(hmac, &mut noise, zeta.ratchet_state.chain_len); // We must make sure the ratchet key is saved before we transition. let result = app.save_ratchet_state( &s_remote, @@ -776,7 +794,7 @@ pub(crate) fn received_x3_trans( }, ); if let Err(e) = result { - return Err(ReceiveError::RatchetIoError(e)); + return Err(ReceiveError::StorageError(e)); } let (session, current_time) = { @@ -791,7 +809,9 @@ pub(crate) fn received_x3_trans( let mut session_queue = ctx.session_queue.lock().unwrap(); let queue_idx = session_queue.reserve_index(); let current_time = app.time(); + let resend_timer = current_time + App::SETTINGS.resend_time as i64; let session = Arc::new(Session { + ctx: Arc::downgrade(ctx), session_data, was_bob: true, s_remote, @@ -804,7 +824,7 @@ pub(crate) fn received_x3_trans( key_creation_counter: c + 1, key_index: false, keys: [DuplexKey::default(), DuplexKey::default()], - resend_timer: current_time + App::SETTINGS.resend_time as i64, + resend_timer: AtomicI64::new(resend_timer), timeout_timer: current_time + App::SETTINGS.rekey_timeout as i64, beta: ZetaAutomata::S1, }), @@ -815,23 +835,25 @@ pub(crate) fn received_x3_trans( hk_send: App::PrpEnc::new(&zeta.hk_send), hk_recv: App::PrpDec::new(&zeta.hk_recv), }); + { + let mut state = session.state.write().unwrap(); + state.key_mut(false).replace_nk(&nk_send, &nk_recv); + state.key_mut(false).recv.kid = Some(zeta.kid_recv); + state.key_mut(false).recv.replace_kek(&kek_recv); + state.key_mut(false).send.kid = Some(zeta.kid_send); + state.key_mut(false).send.replace_kek(&kek_send); + } - let mut state = session.state.write().unwrap(); - state.key_mut(false).replace_nk(&nk_send, &nk_recv); - state.key_mut(false).recv.kid = Some(zeta.kid_recv); - state.key_mut(false).recv.replace_kek(&kek_recv); - state.key_mut(false).send.kid = Some(zeta.kid_send); - state.key_mut(false).send.replace_kek(&kek_send); - - session_queue.push_reserved(queue_idx, Arc::downgrade(&session), Reverse(state.next_timer())); + session_queue.push_reserved(queue_idx, Arc::downgrade(&session), Reverse(resend_timer)); entry.insert(Arc::downgrade(&session)); + (session, current_time) }; process_timers(app, ctx, &session, current_time, false, true, send); Ok(session) } - Err(e) => Err(ReceiveError::RatchetIoError(e)), + Err(e) => Err(ReceiveError::StorageError(e)), } } else { if !responder_silently_rejects { @@ -895,28 +917,31 @@ pub(crate) fn received_c1_trans( }, ); if let Err(e) = result { - return Err(ReceiveError::RatchetIoError(e)); + return Err(ReceiveError::StorageError(e)); } } drop(state); - let mut state_mut = session.state.write().unwrap(); - - state_mut.ratchet_state2 = None; - state_mut.key_index ^= true; - state_mut.timeout_timer = app.time() - + App::SETTINGS - .rekey_after_time - .saturating_sub(ctx.rng.lock().unwrap().next_u64() % App::SETTINGS.rekey_time_max_jitter) as i64; - state_mut.resend_timer = i64::MAX; - state_mut.beta = ZetaAutomata::S2; - drop(state); + let timeout_timer = { + let mut state = session.state.write().unwrap(); + state.ratchet_state2 = None; + state.key_index ^= true; + state.timeout_timer = app.time() + + App::SETTINGS + .rekey_after_time + .saturating_sub(ctx.rng.lock().unwrap().next_u64() % App::SETTINGS.rekey_time_max_jitter) as i64; + state.resend_timer = AtomicI64::new(i64::MAX); + state.beta = ZetaAutomata::S2; + state.timeout_timer + }; + drop(kex_lock); + ctx.session_queue.lock().unwrap().change_priority(session.queue_idx, Reverse(timeout_timer)); state = session.state.read().unwrap(); } } let mut c2 = ArrayVec::::new(); c2.extend([0u8; HEADER_SIZE]); - let (c, should_rekey) = get_counter(session, &state).ok_or(byzantine_fault!(ExpiredCounter, false))?; + let (c, _) = get_counter(session, &state).ok_or(byzantine_fault!(ExpiredCounter, false))?; let nonce = to_nonce(PACKET_TYPE_ACK, c); let latest_confirmed_key = state.key_ref(false).send.kek.as_ref().ok_or(byzantine_fault!(OutOfSequence, true))?; c2.extend(App::Aead::encrypt_in_place(latest_confirmed_key, &nonce, &[], &mut [])); @@ -963,14 +988,18 @@ pub(crate) fn received_c2_trans( return Err(byzantine_fault!(ExpiredCounter, true)); } drop(state); - let mut state = session.state.write().unwrap(); - - state.timeout_timer = app.time() - + App::SETTINGS - .rekey_after_time - .saturating_sub(ctx.rng.lock().unwrap().next_u64() % App::SETTINGS.rekey_time_max_jitter) as i64; - state.resend_timer = i64::MAX; - state.beta = ZetaAutomata::S2; + let timeout_timer = { + let mut state = session.state.write().unwrap(); + state.timeout_timer = app.time() + + App::SETTINGS + .rekey_after_time + .saturating_sub(ctx.rng.lock().unwrap().next_u64() % App::SETTINGS.rekey_time_max_jitter) as i64; + state.resend_timer = AtomicI64::new(i64::MAX); + state.beta = ZetaAutomata::S2; + state.timeout_timer + }; + drop(kex_lock); + ctx.session_queue.lock().unwrap().change_priority(session.queue_idx, Reverse(timeout_timer)); Ok(()) } /// Corresponds to the trivial Transition Algorithm described for processing D packets found in @@ -1004,8 +1033,8 @@ pub(crate) fn received_d_trans( if !session.window.update(c) { return Err(byzantine_fault!(ExpiredCounter, true)); } - /// - //zeta.expire(); + drop(state); + session.expire_inner(kex_lock, session.state.write().unwrap()); Ok(()) } /// Corresponds to the timer rules of the Zeta State Machine found in Section 4.1 - Definition 3. @@ -1017,13 +1046,13 @@ pub(crate) fn process_timers( force_timeout: bool, force_resend: bool, send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), -) { +) -> Option { let kex_lock = session.state_machine_lock.lock().unwrap(); let mut state = session.state.read().unwrap(); if force_timeout || state.timeout_timer <= current_time { // Corresponds to the timeout timer Transition Algorithm described in Section 4.1 - Definition 3. match &state.beta { - ZetaAutomata::Null => {} + ZetaAutomata::Null => None, ZetaAutomata::A1(_) | ZetaAutomata::A3 { .. } => { let identity = match &state.beta { ZetaAutomata::A1(a1) => &a1.identity, @@ -1037,8 +1066,8 @@ pub(crate) fn process_timers( } let new_kid_recv = remap(ctx, session, &state); - let mut hash = &mut App::Hash::new(); - let mut hmac = &mut App::HmacHash::new(); + let hash = &mut App::Hash::new(); + let hmac = &mut App::HmacHash::new(); if let Some(a1) = create_a1_state( hash, hmac, @@ -1055,20 +1084,24 @@ pub(crate) fn process_timers( let mut x1 = a1.x1.clone(); drop(state); - { + let resend_timer = { let mut state = session.state.write().unwrap(); session.hk_recv.reset((&hk_recv[..AES_256_KEY_SIZE]).try_into().unwrap()); session.hk_send.reset((&hk_send[..AES_256_KEY_SIZE]).try_into().unwrap()); *state.key_mut(true) = DuplexKey::default(); state.key_mut(true).recv.kid = Some(new_kid_recv); - state.resend_timer = current_time + App::SETTINGS.resend_time as i64; + let resend_timer = current_time + App::SETTINGS.resend_time as i64; + state.resend_timer = AtomicI64::new(resend_timer); state.timeout_timer = current_time + App::SETTINGS.initial_offer_timeout as i64; state.beta = ZetaAutomata::A1(a1); - } + resend_timer + }; + drop(kex_lock); send(&mut x1, None); + Some(resend_timer) } else { - //zeta.expire(); + None } } ZetaAutomata::S2 => { @@ -1093,84 +1126,97 @@ pub(crate) fn process_timers( let e_secret = noise.write_e(hash, hmac, &ctx.rng, &mut k1); // Process message pattern 1 es token. if noise.mix_dh(hmac, &e_secret, &session.s_remote).is_none() { - //zeta.expire(); - return; + return None; } // Process message pattern 1 ss token. noise.mix_key(hmac, session.noise_kk_ss.as_ref()); // Process message pattern 1 payload. let i = k1.len(); k1.extend(new_kid_recv.get().to_be_bytes()); - k1.extend(noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_REKEY_INIT, 0), &mut k1[i..])); + let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_REKEY_INIT, 0), &mut k1[i..]); + k1.extend(tag); drop(state); - { + let resend_timer = { let mut state = session.state.write().unwrap(); state.key_mut(true).recv.kid = Some(new_kid_recv); state.timeout_timer = current_time + App::SETTINGS.rekey_timeout as i64; - state.resend_timer = current_time + App::SETTINGS.resend_time as i64; + let resend_timer = current_time + App::SETTINGS.resend_time as i64; + state.resend_timer = AtomicI64::new(resend_timer); state.beta = ZetaAutomata::R1 { noise, e_secret, k1: k1.clone() }; - } + resend_timer + }; + drop(kex_lock); let state = session.state.read().unwrap(); - if let Some((c, should_rekey)) = get_counter(session, &state) { + if let Some((c, _)) = get_counter(session, &state) { let nonce = to_nonce(PACKET_TYPE_REKEY_INIT, c); - k1.extend(App::Aead::encrypt_in_place(state.key_ref(false).send.kek.as_ref().unwrap(), &nonce, &[], &mut k1)); + let tag = App::Aead::encrypt_in_place(state.key_ref(false).send.kek.as_ref().unwrap(), &nonce, &[], &mut k1); + k1.extend(tag); set_header(&mut k1, state.key_ref(false).send.kid.unwrap().get(), &nonce); send(&mut k1, Some(&session.hk_send)); } + Some(resend_timer) } ZetaAutomata::S1 { .. } => { log!(app, TimeoutKeyConfirm(session)); - //zeta.expire(); + None } ZetaAutomata::R1 { .. } => { log!(app, TimeoutK1(session)); - //zeta.expire(); + None } ZetaAutomata::R2 { .. } => { log!(app, TimeoutK2(session)); - //zeta.expire(); + None } } - } else if force_resend || state.resend_timer <= current_time { - // Corresponds to the resend timer rules found in Section 4.1 - Definition 3. - state.resend_timer = current_time + App::SETTINGS.resend_time as i64; + } else { + let ts = state.resend_timer.load(Ordering::Relaxed); + let resend_next = current_time + App::SETTINGS.resend_time as i64; + if force_resend || (ts <= current_time && state.resend_timer.fetch_max(resend_next, Ordering::Relaxed) == ts) { + // Corresponds to the resend timer rules found in Section 4.1 - Definition 3. - let (packet_type, mut control_payload) = match &state.beta { - ZetaAutomata::Null => return, - ZetaAutomata::A1(a1) => { - log!(app, ResentX1(session)); - return send(&mut a1.x1.clone(), None); - } - ZetaAutomata::A3 { x3, .. } => { - log!(app, ResentX3(session)); - return send(&mut x3.clone(), Some(&session.hk_send)); - } - ZetaAutomata::S1 => { - log!(app, ResentKeyConfirm(session)); - let mut c1 = ArrayVec::new(); - c1.extend([0u8; HEADER_SIZE]); - (PACKET_TYPE_KEY_CONFIRM, c1) - } - ZetaAutomata::S2 => return, - ZetaAutomata::R1 { k1, .. } => { - log!(app, ResentK1(session)); - (PACKET_TYPE_REKEY_INIT, k1.clone()) - } - ZetaAutomata::R2 { k2, .. } => { - log!(app, ResentK2(session)); - (PACKET_TYPE_REKEY_COMPLETE, k2.clone()) - } - }; - if let Some((c, should_rekey)) = get_counter(session, &state) { - let nonce = to_nonce(packet_type, c); - let tag = App::Aead::encrypt_in_place(state.key_ref(false).send.kek.as_ref().unwrap(), &nonce, &[], &mut control_payload); - control_payload.extend(tag); - set_header(&mut control_payload, state.key_ref(false).send.kid.unwrap().get(), &nonce); + let (packet_type, mut control_payload) = match &state.beta { + ZetaAutomata::Null => return None, + ZetaAutomata::A1(a1) => { + log!(app, ResentX1(session)); + send(&mut a1.x1.clone(), None); + return Some(resend_next); + } + ZetaAutomata::A3 { x3, .. } => { + log!(app, ResentX3(session)); + send(&mut x3.clone(), Some(&session.hk_send)); + return Some(resend_next); + } + ZetaAutomata::S1 => { + log!(app, ResentKeyConfirm(session)); + let mut c1 = ArrayVec::new(); + c1.extend([0u8; HEADER_SIZE]); + (PACKET_TYPE_KEY_CONFIRM, c1) + } + ZetaAutomata::S2 => return Some(state.timeout_timer), + ZetaAutomata::R1 { k1, .. } => { + log!(app, ResentK1(session)); + (PACKET_TYPE_REKEY_INIT, k1.clone()) + } + ZetaAutomata::R2 { k2, .. } => { + log!(app, ResentK2(session)); + (PACKET_TYPE_REKEY_COMPLETE, k2.clone()) + } + }; + if let Some((c, _)) = get_counter(session, &state) { + let nonce = to_nonce(packet_type, c); + let tag = App::Aead::encrypt_in_place(state.key_ref(false).send.kek.as_ref().unwrap(), &nonce, &[], &mut control_payload); + control_payload.extend(tag); + set_header(&mut control_payload, state.key_ref(false).send.kid.unwrap().get(), &nonce); - send(&mut control_payload, Some(&session.hk_send)); + send(&mut control_payload, Some(&session.hk_send)); + } + Some(resend_next) + } else { + Some(ts) } } } @@ -1190,7 +1236,6 @@ pub(crate) fn received_k1_trans( app: &App, ctx: &Arc>, session: &Arc>, - s_secret: &App::KeyPair, kid: NonZeroU32, n: &[u8; AES_GCM_IV_SIZE], k1: &mut [u8], @@ -1225,7 +1270,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(state.key_ref(false).recv.kek.as_ref().unwrap(), n, &[], &mut k1[..i], tag) { + if !App::Aead::decrypt_in_place(state.key_ref(false).recv.kek.as_ref().unwrap(), n, &[], &mut k1[..i], &tag) { return Err(byzantine_fault!(FailedAuth, true)); } let (_, c) = from_nonce(n); @@ -1236,17 +1281,17 @@ pub(crate) fn received_k1_trans( let result = (|| { let mut i = 0; let mut noise = SymmetricState::::initialize(PROTOCOL_NAME_NOISE_KK); - let mut hash = &mut App::Hash::new(); - let mut hmac = &mut App::HmacHash::new(); + let hash = &mut App::Hash::new(); + let hmac = &mut App::HmacHash::new(); // Noise process prologue. noise.mix_hash(hash, &session.s_remote.to_bytes()); - noise.mix_hash(hash, &s_secret.public_key_bytes()); + noise.mix_hash(hash, &ctx.s_secret.public_key_bytes()); // Process message pattern 1 psk0 token. noise.mix_key_and_hash(hash, hmac, state.ratchet_state1.key.as_ref()); // Process message pattern 1 e token. let e_remote = noise.read_e(hash, hmac, &mut i, &k1).ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 1 es token. - noise.mix_dh(hmac, s_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise.mix_dh(hmac, &ctx.s_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 1 ss token. noise.mix_key(hmac, session.noise_kk_ss.as_ref()); // Process message pattern 1 payload. @@ -1265,12 +1310,13 @@ pub(crate) fn received_k1_trans( // Process message pattern 2 ee token. noise.mix_dh(hmac, &e_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 se token. - noise.mix_dh(hmac, &s_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise.mix_dh(hmac, &ctx.s_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 payload. let i = k2.len(); let new_kid_recv = remap(ctx, session, &state); k2.extend(new_kid_recv.get().to_be_bytes()); - k2.extend(noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_REKEY_COMPLETE, 0), &mut k2[i..])); + let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_REKEY_COMPLETE, 0), &mut k2[i..]); + k2.extend(tag); let new_ratchet_state = create_ratchet_state(hmac, &noise, state.ratchet_state1.chain_len); let result = app.save_ratchet_state( @@ -1285,7 +1331,7 @@ pub(crate) fn received_k1_trans( }, ); if let Err(e) = result { - return Err(ReceiveError::RatchetIoError(e)); + return Err(ReceiveError::StorageError(e)); } let mut kek_recv = Zeroizing::new([0u8; HASHLEN]); @@ -1296,7 +1342,7 @@ pub(crate) fn received_k1_trans( noise.split(hmac, &mut nk_send, &mut nk_recv); drop(state); - { + let resend_timer = { let mut state = session.state.write().unwrap(); state.key_mut(true).replace_nk(&nk_send, &nk_recv); state.key_mut(true).send.kid = Some(kid_send); @@ -1307,24 +1353,28 @@ pub(crate) fn received_k1_trans( state.ratchet_state1 = new_ratchet_state.clone(); let current_time = app.time(); state.key_creation_counter = session.send_counter.load(Ordering::Relaxed); + let resend_timer = current_time + App::SETTINGS.resend_time as i64; state.timeout_timer = current_time + App::SETTINGS.rekey_timeout as i64; - state.resend_timer = current_time + App::SETTINGS.resend_time as i64; + state.resend_timer = AtomicI64::new(resend_timer); state.beta = ZetaAutomata::R2 { k2: k2.clone() }; - } - let mut state = session.state.read().unwrap(); + resend_timer + }; + drop(kex_lock); + ctx.session_queue.lock().unwrap().change_priority(session.queue_idx, Reverse(resend_timer)); + let state = session.state.read().unwrap(); - /// - let (c, should_rekey) = get_counter(session, &state).ok_or(byzantine_fault!(ExpiredCounter, false))?; + let (c, _) = get_counter(session, &state).ok_or(byzantine_fault!(ExpiredCounter, false))?; let nonce = to_nonce(PACKET_TYPE_REKEY_COMPLETE, c); - k2.extend(App::Aead::encrypt_in_place(state.key_ref(false).send.kek.as_ref().unwrap(), &nonce, &[], &mut k2)); + let tag = App::Aead::encrypt_in_place(state.key_ref(false).send.kek.as_ref().unwrap(), &nonce, &[], &mut k2); + k2.extend(tag); set_header(&mut k2, state.key_ref(false).send.kid.unwrap().get(), &nonce); send(&mut k2, Some(&session.hk_send)); Ok(()) })(); - /// + if matches!(result, Err(ReceiveError::ByzantineFault { .. })) { - //zeta.expire(); + session.expire(); } result } @@ -1335,7 +1385,7 @@ pub(crate) fn received_k2_trans( session: &Arc>, kid: NonZeroU32, n: &[u8; AES_GCM_IV_SIZE], - mut k2: &mut [u8], + k2: &mut [u8], send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Result<(), ReceiveError> { use FaultType::*; @@ -1358,7 +1408,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(state.key_ref(false).recv.kek.as_ref().unwrap(), n, &[], &mut k2[..i], tag) { + if !App::Aead::decrypt_in_place(state.key_ref(false).recv.kek.as_ref().unwrap(), n, &[], &mut k2[..i], &tag) { return Err(byzantine_fault!(FailedAuth, true)); } let (_, c) = from_nonce(n); @@ -1369,8 +1419,8 @@ pub(crate) fn received_k2_trans( if let ZetaAutomata::R1 { noise, e_secret, .. } = &state.beta { let mut noise = noise.clone(); let mut i = 0; - let mut hash = &mut App::Hash::new(); - let mut hmac = &mut App::HmacHash::new(); + let hash = &mut App::Hash::new(); + let hmac = &mut App::HmacHash::new(); // Process message pattern 2 e token. let e_remote = noise.read_e(hash, hmac, &mut i, &k2).ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 ee token. @@ -1399,7 +1449,7 @@ pub(crate) fn received_k2_trans( }, ); if let Err(e) = result { - return Err(ReceiveError::RatchetIoError(e)); + return Err(ReceiveError::StorageError(e)); } let mut kek_recv = Zeroizing::new([0u8; HASHLEN]); let mut kek_send = Zeroizing::new([0u8; HASHLEN]); @@ -1409,7 +1459,7 @@ pub(crate) fn received_k2_trans( noise.split(hmac, &mut nk_recv, &mut nk_send); drop(state); - let current_time = { + let (current_time, resend_timer) = { let mut state = session.state.write().unwrap(); state.key_mut(true).replace_nk(&nk_send, &nk_recv); state.key_mut(true).send.kid = Some(kid_send); @@ -1419,11 +1469,14 @@ pub(crate) fn received_k2_trans( state.key_index ^= true; let current_time = app.time(); state.key_creation_counter = session.send_counter.load(Ordering::Relaxed); + let resend_timer = current_time + App::SETTINGS.resend_time as i64; state.timeout_timer = current_time + App::SETTINGS.rekey_timeout as i64; - state.resend_timer = current_time + App::SETTINGS.resend_time as i64; + state.resend_timer = AtomicI64::new(resend_timer); state.beta = ZetaAutomata::S1; - current_time + (current_time, resend_timer) }; + drop(kex_lock); + ctx.session_queue.lock().unwrap().change_priority(session.queue_idx, Reverse(resend_timer)); process_timers(app, ctx, session, current_time, false, true, send); Ok(()) @@ -1431,23 +1484,156 @@ pub(crate) fn received_k2_trans( unreachable!() } })(); + if matches!(result, Err(ReceiveError::ByzantineFault { .. })) { - //zeta.expire(); + session.expire(); } result } +/// Corresponds to Algorithm 10 found in Section 4.3. +pub(crate) fn receive_payload_in_place( + app: &App, + ctx: &Arc>, + session: &Arc>, + kid: NonZeroU32, + n: &[u8; AES_GCM_IV_SIZE], + fragments: &mut [App::IncomingPacketBuffer], + mut output_buffer: impl Write, +) -> Result<(), ReceiveError> { + use FaultType::*; -//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. -// pub fn expire(&mut self) { -// self.0.lock().unwrap().expire(); -// } -//} + let kex_lock = session.state_machine_lock.lock().unwrap(); + let state = session.state.read().unwrap(); + let is_other = if Some(kid) == state.key_ref(true).recv.kid { + true + } else if Some(kid) == state.key_ref(false).recv.kid { + false + } else { + return Err(byzantine_fault!(OutOfSequence, true)); + }; -//impl Drop for Session { -// fn drop(&mut self) { -// self.expire(); -// } -//} + let mut cipher = state.key_ref(is_other).nk + .as_ref() + .ok_or(byzantine_fault!(OutOfSequence, true))?.start_dec(n); + + // NOTE: This only works because we check the size of every received fragment in the receive + // function, otherwise this could panic. + let mut i = 0; + while i + 1 < fragments.len() { + let fragment = &mut fragments[i].as_mut()[HEADER_SIZE..]; + cipher.decrypt_in_place(fragment); + i += 1; + } + let fragment = &mut fragments[i].as_mut()[HEADER_SIZE..]; + let tag_idx = fragment.len() - AES_GCM_IV_SIZE; + cipher.decrypt_in_place(&mut fragment[..tag_idx]); + if cipher.finish((&fragment[tag_idx..]).try_into().unwrap()) { + return Err(byzantine_fault!(FailedAuth, true)); + } + + let (_, c) = from_nonce(n); + if !session.window.update(c) { + // This error is marked as not happening naturally, but it could occur if something about + // the transport protocol is duplicating packets. + return Err(byzantine_fault!(ExpiredCounter, true)); + } + + drop(cipher); + for fragment in fragments { + let result = output_buffer.write(&fragment.as_ref()[HEADER_SIZE..]); + if let Err(e) = result { + return Err(ReceiveError::IoError(e)); + } + } + + Ok(()) +} + + +impl Drop for Session { + fn drop(&mut self) { + self.expire(); + } +} +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. + pub fn expire(&self) { + self.expire_inner(self.state_machine_lock.lock().unwrap(), self.state.write().unwrap()); + } + pub(crate) fn expire_inner(&self, kex_lock: MutexGuard<'_, ()>, mut state: RwLockWriteGuard<'_, MutableState>) { + let mut kids_to_remove = None; + if !matches!(&state.beta, ZetaAutomata::Null) { + self.session_has_expired.store(true, Ordering::Relaxed); + kids_to_remove = Some([state.keys[0].recv.kid, state.keys[1].recv.kid]); + state.keys = [DuplexKey::default(), DuplexKey::default()]; + state.resend_timer = AtomicI64::new(i64::MAX); + state.timeout_timer = i64::MAX; + state.beta = ZetaAutomata::Null; + } + drop(state); + drop(kex_lock); + if let Some(kids_to_remove) = kids_to_remove { + if let Some(ctx) = self.ctx.upgrade() { + ctx.session_queue.lock().unwrap().remove(self.queue_idx); + let mut session_map = ctx.session_map.write().unwrap(); + for kid_recv in kids_to_remove.iter().flatten() { + session_map.remove(kid_recv); + } + } + } + } + /// + ///// The current ratchet state of this session. + ///// The returned values are sensitive and should be securely erased before being dropped. + //pub fn ratchet_states(&self) -> [RatchetState; 2] { + // let state = self.state.read().unwrap(); + // state.ratchet_states.clone() + //} + /// The current ratchet count of this session. + //pub fn ratchet_count(&self) -> u64 { + // self.state.read().unwrap(). + //} + /// 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. + //pub fn expire(&self) { + // if let Some(context) = self.context.upgrade() { + // self.expire_inner(&context, &mut context.session_queue.lock().unwrap()); + // } + //} + //fn expire_inner( + // &self, + // context: &Arc>, + // session_queue: &mut IndexedBinaryHeap>, Reverse>, + //) { + // // Prevent this session from being updated. + // session_queue.remove(self.queue_idx); + // self.session_has_expired.store(true, Ordering::Relaxed); + // let _kex_lock = self.state_machine_lock.lock().unwrap(); + // let mut state = self.state.write().unwrap(); + // let mut session_map = context.session_map.write().unwrap(); + // for key in &state.cipher_states { + // if let Some(pre_id) = key.as_ref().map(|k| k.local_key_id) { + // session_map.remove(&pre_id); + // } + // } + // use OfferStateMachine::*; + // match &state.outgoing_offer { + // NoiseXKPattern1or3(handshake_state) => session_map.remove(&handshake_state.local_key_id), + // NoiseKKPattern1 { new_key_id, .. } => session_map.remove(new_key_id), + // _ => None, + // }; + // state.outgoing_offer = OfferStateMachine::Null; + //} + /// Check whether this session is established. + pub fn established(&self) -> bool { + let state = self.state.read().unwrap(); + !matches!(&state.beta, ZetaAutomata::A1(_) | ZetaAutomata::A3 {..} | ZetaAutomata::Null) + } + /// The static public key of the remote peer. + pub fn remote_static_key(&self) -> &App::PublicKey { + &self.s_remote + } +} diff --git a/src/zssp.rs b/src/zssp.rs index ef49568..5af962f 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -10,6 +10,7 @@ use std::cmp::Reverse; use std::collections::HashMap; +use std::io::Write; use std::hash::Hash; use std::num::{NonZeroU32, NonZeroU64}; use std::ops::DerefMut; @@ -60,16 +61,17 @@ impl Clone for Context { } pub(crate) type SessionMap = RwLock>>>; -pub(crate) struct ContextInner { +pub(crate) type SessionQueue = IndexedBinaryHeap>, Reverse>; +pub struct ContextInner { pub rng: Mutex, pub(crate) s_secret: App::KeyPair, - pub(crate) session_queue: Mutex>, Reverse>>, + /// `session_queue -> state_machine_lock -> state -> session_map` + pub(crate) session_queue: Mutex>, + /// `session_queue -> state_machine_lock -> state -> session_map` pub(crate) session_map: SessionMap, pub(crate) unassociated_defrag_cache: Mutex>, pub(crate) unassociated_handshake_states: UnassociatedHandshakeCache, - //pub(crate) b2_map: Mutex>>, - //hello_defrag: Mutex, pub(crate) challenge: ChallengeContext, } @@ -80,7 +82,7 @@ pub enum IncomingSessionAction { Drop, } -fn parse_fragment_header(incoming_fragment: &[u8]) -> Result<(usize, usize, [u8; AES_GCM_IV_SIZE]), ReceiveError> { +fn parse_fragment_header(incoming_fragment: &[u8]) -> Result<(usize, usize, [u8; AES_GCM_IV_SIZE]), ReceiveError> { let fragment_no = incoming_fragment[FRAGMENT_NO_IDX] as usize; let fragment_count = incoming_fragment[FRAGMENT_COUNT_IDX] as usize; if fragment_no >= fragment_count || fragment_count > MAX_FRAGMENTS { @@ -92,6 +94,43 @@ fn parse_fragment_header(incoming_fragment: &[u8]) -> Res } +/// Fragments and sends the packet, destroying it in the process. +/// +/// Corresponds to the fragmentation algorithm described in Section 6. +fn send_with_fragmentation( + mut send: impl FnMut(&mut [u8]) -> bool, + mtu: usize, + headered_packet: &mut [u8], + hk_send: Option<&PrpEnc>, +) -> bool { + let payload_len = headered_packet.len() - HEADER_SIZE; + let payload_mtu = mtu - HEADER_SIZE; + debug_assert!(payload_mtu >= 4); + let fragment_count = payload_len.saturating_add(payload_mtu - 1) / payload_mtu; // Ceiling div. + let fragment_base_size = payload_len / fragment_count; + let fragment_size_remainder = payload_len % fragment_count; + + let mut i = HEADER_SIZE; + for fragment_no in 0..fragment_count { + let j = i + fragment_base_size + (fragment_no < fragment_size_remainder) as usize; + let fragment = &mut headered_packet[i - HEADER_SIZE..j]; + + fragment[FRAGMENT_NO_IDX] = fragment_no as u8; + fragment[FRAGMENT_COUNT_IDX] = fragment_count as u8; + + if let Some(hk_send) = hk_send { + hk_send.encrypt_in_place( + (&mut fragment[HEADER_AUTH_START..HEADER_AUTH_END]).try_into().unwrap(), + ); + } + if !send(fragment) { + return false; + } + i = j; + } + true +} + impl Context { /// Create a new session context. pub fn new(static_secret_key: App::KeyPair, mut rng: App::Rng) -> Self { @@ -143,6 +182,9 @@ impl Context { static_remote_key, session_data, identity, + |packet, hk_send| { + send_with_fragmentation(send, mtu, packet, hk_send); + } ) } @@ -186,9 +228,8 @@ impl Context { mut send_unassociated_mtu: usize, mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, remote_address: &impl Hash, - data_buf: &'a mut [u8], mut incoming_fragment_buf: App::IncomingPacketBuffer, - current_time: i64, + output_buffer: impl Write, ) -> Result, ReceiveError> { use crate::result::FaultType::*; let ctx = &self.0; @@ -198,21 +239,175 @@ impl Context { return Err(byzantine_fault!(FaultType::InvalidPacket, false)); } - // The first section parses the header and looks up relevant state information. If it's a DATA - // or NOP packet it gets handled right here, otherwise we pull out a set of variables and - // continue to the logic that handles KEX and session control packets. + let mut fragment_buffer = Assembled::new(); - let mut assembled_packet = Assembled::new(); // needs to outlive the block below - let mut incoming = None; - let (session, packet_type, fragments) = { - let kid_recv = incoming_fragment[0..KID_SIZE].try_into().unwrap(); - // `from_ne_bytes` because this id was generated locally. - if let Some(kid_recv) = NonZeroU32::new(u32::from_ne_bytes(kid_recv)) { - let session_map = self.0.session_map.read().unwrap(); - let session = ctx.session_map.read().unwrap().get(&kid_recv).map(|r| r.upgrade()); - if let Some(Some(session)) = session { - drop(session_map); - session.hk_recv.decrypt_in_place( + let kid_recv = incoming_fragment[0..KID_SIZE].try_into().unwrap(); + if let Some(kid_recv) = NonZeroU32::new(u32::from_be_bytes(kid_recv)) { + let session_map = self.0.session_map.read().unwrap(); + let session = ctx.session_map.read().unwrap().get(&kid_recv).map(|r| r.upgrade()); + if let Some(Some(session)) = session { + drop(session_map); + session.hk_recv.decrypt_in_place( + (&mut incoming_fragment[HEADER_AUTH_START..HEADER_AUTH_END]) + .try_into() + .unwrap(), + ); + + let (fragment_no, fragment_count, nonce) = parse_fragment_header(incoming_fragment)?; + let (packet_type, incoming_counter) = from_nonce(&nonce); + + {//vrfy + if packet_type != PACKET_TYPE_DATA { + log!(app, ReceivedRawFragment(p, c, fragment_no, fragment_count)); + } + if packet_type == PACKET_TYPE_HANDSHAKE_RESPONSE { + if !matches!(&session.state.read().unwrap().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 incoming_counter >= COUNTER_WINDOW_MAX_SKIP_AHEAD { + return Err(byzantine_fault!(ExpiredCounter, true)); + } + } else if PACKET_TYPE_USES_COUNTER_RANGE.contains(&packet_type) { + // For DOS resistant reply-protection we need to check that the given counter is + // in the window of valid counters immediately. + // But for packets larger than 1 fragment we can't actually record the + // counter as received until we've authenticated the packet. + // So we check the counter window twice, and only update it the second time + // after the packet has been authenticated. + if !session.window.check(incoming_counter) { + // This can occur naturally if packets arrive way out of order, or + // if they are duplicates. + // This can also be naturally triggered if Bob has just successfully + // received the first session key and is reject all of Alice's resends. + // This can also occur if a session was manually expired, but not + // dropped, and the remote party is still sending us data. + return Err(byzantine_fault!(ExpiredCounter, false)); + } + } else if packet_type == PACKET_TYPE_HANDSHAKE_COMPLETION { + // This can be triggered if Bob successfully received a session key and + // needs to reject all of Alice's resends of PACKET_TYPE_NOISE_XK_PATTERN_3. + return Err(byzantine_fault!(InvalidPacket, false)); + } else { + return Err(byzantine_fault!(InvalidPacket, true)); + } + } + + // Handle defragmentation. + let ret = if packet_type == PACKET_TYPE_DATA { + let fragments = if fragment_count > 1 { + let idx = incoming_counter as usize % session.defrag.len(); + session.defrag[idx].lock().unwrap().assemble( + &nonce, + incoming_fragment_buf, + fragment_no, + fragment_count, + &mut fragment_buffer, + ); + if fragment_buffer.is_empty() { + return Ok(ReceiveOk::Unassociated); + } else { + // We have not yet authenticated the sender so we do not report + // receiving a packet from them. + fragment_buffer.as_mut() + } + } else { + std::slice::from_mut(&mut incoming_fragment_buf) + }; + receive_payload_in_place(app, ctx, &session, kid_recv, &nonce, fragments, output_buffer)?; + SessionEvent::Data + } else { + let mut buffer = ArrayVec::::new(); + let assembled_packet = if fragment_count > 1 { + let idx = incoming_counter as usize % session.defrag.len(); + session.defrag[idx].lock().unwrap().assemble( + &nonce, + incoming_fragment_buf, + fragment_no, + fragment_count, + &mut fragment_buffer, + ); + if fragment_buffer.is_empty() { + return Ok(ReceiveOk::Unassociated); + } else { + for fragment in fragment_buffer.as_ref() { + buffer.try_extend_from_slice(&fragment.as_ref()[HEADER_SIZE..]).map_err(|_| byzantine_fault!(InvalidPacket, true))?; + } + // We have not yet authenticated the sender so we do not report + // receiving a packet from them. + buffer.as_mut() + } + } else { + &mut incoming_fragment_buf.as_mut()[HEADER_SIZE..] + }; + + let send_associated = |packet: &mut [u8], hk_send: Option<&App::PrpEnc>| { + if let Some((send_fragment, mut mtu)) = send_to(&session) { + mtu = mtu.max(MIN_TRANSPORT_MTU); + send_with_fragmentation(send_fragment, mtu, packet, hk_send); + } + }; + match packet_type { + PACKET_TYPE_HANDSHAKE_RESPONSE => { + log!(app, ReceivedRawX2); + received_x2_trans( + app, + ctx, + &session, + kid_recv, + &nonce, + assembled_packet, + send_associated, + )?; + log!(app, X2IsAuthSentX3(&session)); + SessionEvent::Control + } + PACKET_TYPE_KEY_CONFIRM => { + log!(app, ReceivedRawKeyConfirm); + let result = + received_c1_trans(app, ctx, &session, kid_recv, &nonce, assembled_packet,send_associated)?; + log!(app, KeyConfirmIsAuthSentAck(&session)); + if result { + SessionEvent::Established + } else { + SessionEvent::Control + } + } + PACKET_TYPE_ACK => { + log!(app, ReceivedRawAck); + received_c2_trans(app, ctx, &session, kid_recv, &nonce, assembled_packet)?; + log!(app, AckIsAuth(&session)); + SessionEvent::Control + } + PACKET_TYPE_REKEY_INIT => { + log!(app, ReceivedRawK1); + received_k1_trans(app, ctx, &session, kid_recv, &nonce, assembled_packet, send_associated)?; + log!(app, K1IsAuthSentK2(&session)); + SessionEvent::Control + } + PACKET_TYPE_REKEY_COMPLETE => { + log!(app, ReceivedRawK2); + received_k2_trans(app, ctx, &session, kid_recv, &nonce, assembled_packet, send_associated)?; + log!(app, K2IsAuthSentKeyConfirm(&session)); + SessionEvent::Control + } + PACKET_TYPE_SESSION_REJECTED => { + log!(app, ReceivedRawD); + received_d_trans(app, ctx, &session, kid_recv, &nonce, assembled_packet)?; + log!(app, DIsAuthClosedSession(&session)); + SessionEvent::Rejected + } + _ => return Err(byzantine_fault!(InvalidPacket, true)), // This is unreachable. + } + }; + Ok(ReceiveOk::Session(session, ret)) + } else { + drop(session_map); + // Check for and handle PACKET_TYPE_ALICE_NOISE_XK_PATTERN_3 + let zeta = self.0.unassociated_handshake_states.get(kid_recv); + if let Some(zeta) = zeta { + App::PrpDec::new(&zeta.hk_recv).decrypt_in_place( (&mut incoming_fragment[HEADER_AUTH_START..HEADER_AUTH_END]) .try_into() .unwrap(), @@ -222,211 +417,150 @@ impl Context { let (packet_type, incoming_counter) = from_nonce(&nonce); {//vrfy - if packet_type != PACKET_TYPE_DATA { - log!(app, ReceivedRawFragment(p, c, fragment_no, fragment_count)); - } - if packet_type == PACKET_TYPE_HANDSHAKE_RESPONSE { - if !matches!(&session.state.read().unwrap().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 incoming_counter >= COUNTER_WINDOW_MAX_SKIP_AHEAD { - return Err(byzantine_fault!(ExpiredCounter, true)); - } - } else if PACKET_TYPE_USES_COUNTER_RANGE.contains(&packet_type) { - // For DOS resistant reply-protection we need to check that the given counter is - // in the window of valid counters immediately. - // But for packets larger than 1 fragment we can't actually record the - // counter as received until we've authenticated the packet. - // So we check the counter window twice, and only update it the second time - // after the packet has been authenticated. - if !session.window.check(incoming_counter) { - // This can occur naturally if packets arrive way out of order, or - // if they are duplicates. - // This can also be naturally triggered if Bob has just successfully - // received the first session key and is reject all of Alice's resends. - // This can also occur if a session was manually expired, but not - // dropped, and the remote party is still sending us data. - return Err(byzantine_fault!(ExpiredCounter, false)); - } - } else if packet_type == PACKET_TYPE_HANDSHAKE_COMPLETION { - // This can be triggered if Bob successfully received a session key and - // needs to reject all of Alice's resends of PACKET_TYPE_NOISE_XK_PATTERN_3. - return Err(byzantine_fault!(InvalidPacket, false)); - } else { - return Err(byzantine_fault!(InvalidPacket, true)); + log!(app, ReceivedRawFragment(packet_type, incoming_counter, frag_no, frag_count)); + if packet_type != PACKET_TYPE_HANDSHAKE_COMPLETION || incoming_counter != 0 { + return Err(byzantine_fault!(InvalidPacket, true)) } } - // Handle defragmentation. - let fragments = if fragment_count > 1 { - let idx = incoming_counter as usize % session.defrag.len(); - session.defrag[idx].lock().unwrap().assemble( + let mut buffer = ArrayVec::::new(); + let assembled_packet = if fragment_count > 1 { + zeta.defrag.lock().unwrap().assemble( &nonce, incoming_fragment_buf, fragment_no, fragment_count, - &mut assembled_packet, + &mut fragment_buffer ); - if assembled_packet.is_empty() { - // We have not yet authenticated the sender so we do not report - // receiving a packet from them. + if fragment_buffer.is_empty() { return Ok(ReceiveOk::Unassociated); } else { - assembled_packet.as_ref() + for fragment in fragment_buffer.as_ref() { + buffer.try_extend_from_slice(&fragment.as_ref()[HEADER_SIZE..]).map_err(|_| byzantine_fault!(InvalidPacket, true))?; + } + buffer.as_mut() } } else { - std::slice::from_ref(&incoming_fragment_buf) + &mut incoming_fragment_buf.as_mut()[HEADER_SIZE..] }; - - match packet_type { - PACKET_TYPE_DATA => { - let state = session.state.read().unwrap(); - // The error here can occur because the other party is using a brand new - // session key that we have not received yet. - let key = state.cipher_states[key_index] - .as_ref() - .ok_or(byzantine_fault!(FaultType::OutOfSequence, true))?; - let mut c = key.get_receive_cipher(incoming_counter); - c.set_iv(&create_message_nonce(packet_type, incoming_counter)); - - let mut data_len = 0; - - // Decrypt fragments 0..N-1 where N is the number of fragments. - for f in fragments[..(fragments.len() - 1)].iter() { - let f: &[u8] = f.as_ref(); - debug_assert!(f.len() >= HEADER_SIZE); - let current_frag_data_start = data_len; - data_len += f.len() - HEADER_SIZE; - if data_len > data_buf.len() { - return Err(ReceiveError::DataBufferTooSmall); - } - c.decrypt(&f[HEADER_SIZE..], &mut data_buf[current_frag_data_start..data_len]); - } - - // Decrypt final fragment (or only fragment if not fragmented) - let current_frag_data_start = data_len; - let last_fragment = fragments.last().unwrap().as_ref(); - if last_fragment.len() < (HEADER_SIZE + AES_GCM_TAG_SIZE) { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - data_len += last_fragment.len() - (HEADER_SIZE + AES_GCM_TAG_SIZE); - if data_len > data_buf.len() { - return Err(ReceiveError::DataBufferTooSmall); - } - let payload_end = last_fragment.len() - AES_GCM_TAG_SIZE; - c.decrypt(&last_fragment[HEADER_SIZE..payload_end], &mut data_buf[current_frag_data_start..data_len]); - - let aead_authentication_ok = c.finish_decrypt(&last_fragment[payload_end..].try_into().unwrap()); - drop(c); - drop(state); - - if !aead_authentication_ok { - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - if !session.update_receive_window(incoming_counter) { - // This can be naturally triggered because Bob has just - // successfully received a session key and needs to reject - // all of Alice's resends. - // This can also occur naturally if some part of the outer - // system is duplicating the packets being sent to us. - // We are safely deduplicating them here. - return Err(byzantine_fault!(FaultType::ExpiredCounter, true)); - } - // Packet fully authenticated - return Ok(ReceiveOk::Session(session, SessionEvent::Data(&mut data_buf[..data_len]))); - } - PACKET_TYPE_HANDSHAKE_RESPONSE => { - (Some(session), packet_type, fragments) - } - } - } else { - drop(session_map); - // Check for and handle PACKET_TYPE_ALICE_NOISE_XK_PATTERN_3 - incoming = self.0.unassociated_handshake_states.get(kid_recv); - if let Some(incoming) = incoming.as_ref() { - App::PrpDec::new(&incoming.hk_recv).decrypt_in_place( - (&mut incoming_fragment[HEADER_AUTH_START..HEADER_AUTH_END]) - .try_into() - .unwrap(), - ); - - let (fragment_no, fragment_count, nonce) = parse_fragment_header(incoming_fragment)?; - let (packet_type, incoming_counter) = from_nonce(&nonce); - - {//vrfy - log!(app, ReceivedRawFragment(packet_type, incoming_counter, frag_no, frag_count)); - if packet_type != PACKET_TYPE_HANDSHAKE_COMPLETION || incoming_counter != 0 { - return Err(byzantine_fault!(InvalidPacket, true)) - } - } - - let fragments = if fragment_count > 1 { - incoming.defrag.lock().unwrap().assemble( - &nonce, - incoming_fragment_buf, - fragment_no, - fragment_count, - &mut assembled_packet, - ); - if !assembled_packet.is_empty() { - assembled_packet.as_ref() - } else { - return Ok(ReceiveOk::Unassociated); - } - } else { - std::slice::from_ref(&incoming_fragment_buf) - }; - // We must guarantee that this incoming handshake is processed once and only - // once. This prevents catastrophic nonce reuse caused by multithreading. - if self.0.unassociated_handshake_states.remove(kid_recv) { - (None, PACKET_TYPE_HANDSHAKE_COMPLETION, fragments) - } else { - return Ok(ReceiveOk::Unassociated); - } - } else { - // This can occur naturally because either Bob's incoming_sessions cache got - // full so Alice's incoming session was dropped, or the session this packet - // was for was dropped by the application. - return Err(byzantine_fault!(UnknownLocalKeyId, true)); - } - } - } else { - let (fragment_no, fragment_count, nonce) = parse_fragment_header(incoming_fragment)?; - let (packet_type, incoming_counter) = from_nonce(&nonce); - - {//vrfy - log!(app, ReceivedRawFragment(packet_type, incoming_counter, frag_no, frag_count)); - if packet_type != PACKET_TYPE_HANDSHAKE_HELLO && packet_type != PACKET_TYPE_CHALLENGE { - return Err(byzantine_fault!(InvalidPacket, true)) - } - } - - let fragments = if fragment_count > 1 { - self.0.unassociated_defrag_cache.lock().unwrap().assemble( - &nonce, - remote_address, - incoming_fragment.len() - HEADER_SIZE, - incoming_fragment_buf, - fragment_no, - fragment_count, - App::SETTINGS.resend_time as i64, - current_time, - &mut assembled_packet, - ); - if !assembled_packet.is_empty() { - assembled_packet.as_ref() - } else { + // We must guarantee that this incoming handshake is processed once and only + // once. This prevents catastrophic nonce reuse caused by multithreading. + if !self.0.unassociated_handshake_states.remove(kid_recv) { return Ok(ReceiveOk::Unassociated); } + + log!(app, ReceivedRawX3); + let session = received_x3_trans(app, ctx, zeta, kid_recv, assembled_packet, |packet, hk_send| { + send_with_fragmentation( + send_unassociated_reply, + send_unassociated_mtu, + packet, + hk_send, + ); + })?; + log!(app, X3IsAuthSentKeyConfirm(&session)); + Ok(ReceiveOk::Session(session, SessionEvent::NewSession)) } else { - std::array::from_ref(&incoming_fragment_buf) - }; - (None, packet_type, fragments) + // This can occur naturally because either Bob's incoming_sessions cache got + // full so Alice's incoming session was dropped, or the session this packet + // was for was dropped by the application. + return Err(byzantine_fault!(UnknownLocalKeyId, true)); + } } - }; + } else { + let (fragment_no, fragment_count, nonce) = parse_fragment_header(incoming_fragment)?; + let (packet_type, _c) = from_nonce(&nonce); + + {//vrfy + log!(app, ReceivedRawFragment(packet_type, _c, frag_no, frag_count)); + if packet_type != PACKET_TYPE_HANDSHAKE_HELLO && packet_type != PACKET_TYPE_CHALLENGE { + return Err(byzantine_fault!(InvalidPacket, true)) + } + } + + let mut buffer = ArrayVec::::new(); + let assembled_packet = if fragment_count > 1 { + self.0.unassociated_defrag_cache.lock().unwrap().assemble( + &nonce, + remote_address, + incoming_fragment.len() - HEADER_SIZE, + incoming_fragment_buf, + fragment_no, + fragment_count, + App::SETTINGS.resend_time as i64, + app.time(), + &mut fragment_buffer + ); + if fragment_buffer.is_empty() { + return Ok(ReceiveOk::Unassociated); + } else { + for fragment in fragment_buffer.as_ref() { + buffer.try_extend_from_slice(&fragment.as_ref()[HEADER_SIZE..]).map_err(|_| byzantine_fault!(InvalidPacket, true))?; + } + buffer.as_mut() + } + } else { + &mut incoming_fragment_buf.as_mut()[HEADER_SIZE..] + }; + + if packet_type == PACKET_TYPE_HANDSHAKE_HELLO { + log!(app, ReceivedRawX1); + + if !(HANDSHAKE_HELLO_CHALLENGE_MIN_SIZE..=HANDSHAKE_HELLO_CHALLENGE_MAX_SIZE).contains(&assembled_packet.len()) { + return Err(byzantine_fault!(InvalidPacket, true)); + } + // Process recv challenge layer. + let challenge_start = assembled_packet.len() - CHALLENGE_SIZE; + let result = ctx.challenge.process_hello::(remote_address, (&assembled_packet[challenge_start..]).try_into().unwrap()); + if let Err(challenge) = result { + log!(app, X1FailedChallengeSentNewChallenge); + let mut challenge_packet = ArrayVec::::new(); + challenge_packet.extend([0u8; HEADER_SIZE]); + challenge_packet.try_extend_from_slice(&assembled_packet[..KID_SIZE]).unwrap(); + challenge_packet.extend(challenge); + let nonce = to_nonce(PACKET_TYPE_CHALLENGE, ctx.rng.lock().unwrap().next_u64()); + challenge_packet[FRAGMENT_COUNT_IDX] = 1; + challenge_packet[PACKET_NONCE_START..HEADER_SIZE].copy_from_slice(&nonce); + + send_unassociated_reply(&mut challenge_packet); + // If we issue a challenge the first hello packet will always fail. + return Err(byzantine_fault!(FailedAuth, false)); + } else { + log!(app, X1SucceededChallenge); + } + + // Process recv zeta layer. + received_x1_trans(app, ctx, &nonce, assembled_packet, |packet, hk_send| { + send_with_fragmentation( + send_unassociated_reply, + send_unassociated_mtu, + packet, + hk_send, + ); + })?; + log!(app, X1IsAuthSentX2); + + Ok(ReceiveOk::Unassociated) + } else if packet_type == PACKET_TYPE_CHALLENGE { + log!(app, ReceivedRawChallenge); + // Process recv challenge layer. + 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.read().unwrap().get(&kid_recv).map(|r| r.upgrade()) { + respond_to_challenge(ctx, &session, &assembled_packet[KID_SIZE..].try_into().unwrap()); + log!(app, ChallengeIsAuth(&session)); + return Ok(ReceiveOk::Unassociated); + } + } + Err(byzantine_fault!(UnknownLocalKeyId, true)) + } else { + Err(byzantine_fault!(InvalidPacket, true)) + } + } } + /* /// Send data over the session. /// /// * `session` - The session to send to @@ -672,60 +806,5 @@ impl Context { self.0.unassociated_handshake_states.service(current_time); next_service_time - } -} - -impl Session { - /// - ///// The current ratchet state of this session. - ///// The returned values are sensitive and should be securely erased before being dropped. - //pub fn ratchet_states(&self) -> [RatchetState; 2] { - // let state = self.state.read().unwrap(); - // state.ratchet_states.clone() - //} - /// The current ratchet count of this session. - //pub fn ratchet_count(&self) -> u64 { - // self.state.read().unwrap(). - //} - /// 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. - //pub fn expire(&self) { - // if let Some(context) = self.context.upgrade() { - // self.expire_inner(&context, &mut context.session_queue.lock().unwrap()); - // } - //} - //fn expire_inner( - // &self, - // context: &Arc>, - // session_queue: &mut IndexedBinaryHeap>, Reverse>, - //) { - // // Prevent this session from being updated. - // session_queue.remove(self.queue_idx); - // self.session_has_expired.store(true, Ordering::Relaxed); - // let _kex_lock = self.state_machine_lock.lock().unwrap(); - // let mut state = self.state.write().unwrap(); - // let mut session_map = context.session_map.write().unwrap(); - // for key in &state.cipher_states { - // if let Some(pre_id) = key.as_ref().map(|k| k.local_key_id) { - // session_map.remove(&pre_id); - // } - // } - // use OfferStateMachine::*; - // match &state.outgoing_offer { - // NoiseXKPattern1or3(handshake_state) => session_map.remove(&handshake_state.local_key_id), - // NoiseKKPattern1 { new_key_id, .. } => session_map.remove(new_key_id), - // _ => None, - // }; - // state.outgoing_offer = OfferStateMachine::Null; - //} - /// Check whether this session is established. - pub fn established(&self) -> bool { - let state = self.state.read().unwrap(); - !matches!(&state.beta, ZetaAutomata::A1(_) | ZetaAutomata::A3 {..} | ZetaAutomata::Null) - } - /// The static public key of the remote peer. - pub fn remote_static_key(&self) -> &Application::PublicKey { - &self.s_remote - } + } */ } From ff80eeaf685fde99cfd037bc58013fbb88143968 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 7 Aug 2023 20:04:42 -0400 Subject: [PATCH 18/50] cargo fmt --- src/antireplay.rs | 2 +- src/applicationlayer.rs | 9 +- src/challenge.rs | 12 +- src/crypto/aes.rs | 24 +++- src/crypto/kyber1024.rs | 12 +- src/crypto/mod.rs | 2 +- src/crypto/sha512.rs | 1 - src/frag_cache.rs | 49 +++++-- src/indexed_heap.rs | 6 +- src/lib.rs | 10 +- src/log_event.rs | 11 +- src/proto.rs | 18 ++- src/symmetric_state.rs | 45 ++++-- src/zeta.rs | 307 +++++++++++++++++++++++++++++++--------- src/zssp.rs | 101 +++++++------ 15 files changed, 446 insertions(+), 163 deletions(-) diff --git a/src/antireplay.rs b/src/antireplay.rs index 80cd707..81bc5a0 100644 --- a/src/antireplay.rs +++ b/src/antireplay.rs @@ -1,6 +1,6 @@ use std::sync::atomic::{AtomicU64, Ordering}; -pub struct Window ([AtomicU64; L]); +pub struct Window([AtomicU64; L]); impl Window { pub fn new() -> Self { diff --git a/src/applicationlayer.rs b/src/applicationlayer.rs index 2dcfc96..4d7fcfc 100644 --- a/src/applicationlayer.rs +++ b/src/applicationlayer.rs @@ -11,10 +11,10 @@ use crate::crypto::aes::{AesDec, AesEnc, HighThroughputAesGcmPool, LowThroughput use crate::crypto::kyber1024::Kyber1024PrivateKey; use crate::crypto::p384::{P384KeyPair, P384PublicKey}; use crate::crypto::rand_core::{CryptoRng, RngCore}; -use crate::crypto::sha512::{HmacSha512, HashSha512}; -use crate::RatchetState; +use crate::crypto::sha512::{HashSha512, HmacSha512}; use crate::proto::RATCHET_SIZE; use crate::zeta::Session; +use crate::RatchetState; //use crate::{log_event::LogEvent, Session}; /// A container for a vast majority of the dynamic settings within ZSSP, including all time-based settings. @@ -210,7 +210,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 c47af7b..11e4578 100644 --- a/src/challenge.rs +++ b/src/challenge.rs @@ -4,12 +4,15 @@ use std::sync::atomic::{AtomicU64, Ordering}; use rand_core::{CryptoRng, RngCore}; use crate::antireplay::Window; -use crate::crypto::{secure_eq, sha512::{HashSha512, SHA512_HASH_SIZE}}; +use crate::crypto::{ + secure_eq, + sha512::{HashSha512, SHA512_HASH_SIZE}, +}; use crate::proto::*; pub struct ChallengeContext { counter: AtomicU64, - antireplay_window: Window, + antireplay_window: Window, salt: [u8; SALT_SIZE], } @@ -57,7 +60,10 @@ impl ChallengeContext { ) -> Result<(), [u8; CHALLENGE_SIZE]> { let c = u64::from_be_bytes(response[..COUNTER_SIZE].try_into().unwrap()); let mut work_buf = [0u8; SHA512_HASH_SIZE]; - if self.antireplay_window.check(c) && secure_eq(&response[COUNTER_SIZE..POW_START], &self.create_mac::(c, addr)) && verify_pow::(response, &mut work_buf) { + if self.antireplay_window.check(c) + && secure_eq(&response[COUNTER_SIZE..POW_START], &self.create_mac::(c, addr)) + && verify_pow::(response, &mut work_buf) + { self.antireplay_window.update(c); Ok(()) } else { diff --git a/src/crypto/aes.rs b/src/crypto/aes.rs index 2e5f36a..9a4e242 100644 --- a/src/crypto/aes.rs +++ b/src/crypto/aes.rs @@ -39,7 +39,6 @@ pub trait AesDec: Send + Sync { fn decrypt_in_place(&self, block: &mut [u8; AES_256_BLOCK_SIZE]); } - pub trait AesGcmEncContext { fn encrypt(&mut self, input: &[u8], output: &mut [u8]); @@ -54,8 +53,12 @@ pub trait AesGcmDecContext { } pub trait HighThroughputAesGcmPool: Send + Sync { - type EncContext<'a>: AesGcmEncContext where Self: 'a; - type DecContext<'a>: AesGcmDecContext where Self: 'a; + type EncContext<'a>: AesGcmEncContext + where + Self: 'a; + type DecContext<'a>: AesGcmDecContext + where + Self: 'a; fn new(encrypt_key: &[u8; AES_256_KEY_SIZE], decrypt_key: &[u8; AES_256_KEY_SIZE]) -> Self; @@ -64,7 +67,18 @@ pub trait HighThroughputAesGcmPool: Send + Sync { } pub trait LowThroughputAesGcm { - fn encrypt_in_place(key: &[u8; AES_256_KEY_SIZE], iv: &[u8; AES_GCM_IV_SIZE], aad: &[u8], data: &mut [u8]) -> [u8; AES_GCM_TAG_SIZE]; + fn encrypt_in_place( + key: &[u8; AES_256_KEY_SIZE], + iv: &[u8; AES_GCM_IV_SIZE], + aad: &[u8], + data: &mut [u8], + ) -> [u8; AES_GCM_TAG_SIZE]; #[must_use] - fn decrypt_in_place(key: &[u8; AES_256_KEY_SIZE], iv: &[u8; AES_GCM_IV_SIZE], aad: &[u8], data: &mut [u8], tag: &[u8; AES_GCM_TAG_SIZE]) -> bool; + fn decrypt_in_place( + key: &[u8; AES_256_KEY_SIZE], + iv: &[u8; AES_GCM_IV_SIZE], + aad: &[u8], + data: &mut [u8], + tag: &[u8; AES_GCM_TAG_SIZE], + ) -> bool; } diff --git a/src/crypto/kyber1024.rs b/src/crypto/kyber1024.rs index 5cccc30..74c9571 100644 --- a/src/crypto/kyber1024.rs +++ b/src/crypto/kyber1024.rs @@ -25,12 +25,20 @@ pub trait Kyber1024PrivateKey: Sized + Send + Sync { /// **CRITICAL**: This must return `None` if the given `public_key` is invalid in any way /// according to the Kyber1024 spec. #[must_use] - fn encapsulate(rng: &mut Rng, public_key: &[u8; KYBER_PUBLIC_KEY_SIZE], plaintext_out: &mut [u8; KYBER_PLAINTEXT_SIZE]) -> Option<[u8; KYBER_CIPHERTEXT_SIZE]>; + fn encapsulate( + rng: &mut Rng, + public_key: &[u8; KYBER_PUBLIC_KEY_SIZE], + plaintext_out: &mut [u8; KYBER_PLAINTEXT_SIZE], + ) -> Option<[u8; KYBER_CIPHERTEXT_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. /// /// **CRITICAL**: This must return `None` if the given `ciphertext` is invalid in any way /// according to the Kyber1024 spec. #[must_use] - fn decapsulate(&self, ciphertext: &[u8; KYBER_CIPHERTEXT_SIZE], plaintext_out: &mut [u8; KYBER_PLAINTEXT_SIZE]) -> bool; + fn decapsulate( + &self, + ciphertext: &[u8; KYBER_CIPHERTEXT_SIZE], + plaintext_out: &mut [u8; KYBER_PLAINTEXT_SIZE], + ) -> bool; } diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 22dcb9a..f31b7f6 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -1,9 +1,9 @@ // (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. pub mod aes; +pub mod kyber1024; pub mod p384; pub mod sha512; -pub mod kyber1024; // We re-export our dependencies so it is less of a headache for the implementor to use the same // exact version of them. diff --git a/src/crypto/sha512.rs b/src/crypto/sha512.rs index bfb4b0d..94178c1 100644 --- a/src/crypto/sha512.rs +++ b/src/crypto/sha512.rs @@ -13,7 +13,6 @@ pub trait HashSha512 { fn finish_and_reset(&mut self, output: &mut [u8; SHA512_HASH_SIZE]); } - /// Opaque HMAC-SHA-512 implementation. /// Does not need to be threadsafe. pub trait HmacSha512 { diff --git a/src/frag_cache.rs b/src/frag_cache.rs index fab7a78..e132f4c 100644 --- a/src/frag_cache.rs +++ b/src/frag_cache.rs @@ -67,7 +67,10 @@ impl UnassociatedFragCache { ret_assembled: &mut Assembled, ) { debug_assert!(MAX_FRAGMENTS < MAX_UNASSOCIATED_FRAGMENTS); - if fragment_no >= fragment_count || fragment_count > MAX_FRAGMENTS || fragment_size > MAX_UNASSOCIATED_PACKET_SIZE { + if fragment_no >= fragment_count + || fragment_count > MAX_FRAGMENTS + || fragment_size > MAX_UNASSOCIATED_PACKET_SIZE + { return; } @@ -144,7 +147,10 @@ impl UnassociatedFragCache { let new_size = entry.packet_size + fragment_size as u32; let got = 1u64.wrapping_shl(fragment_no as u32); - if got & entry.fragment_have == 0 && fragment_count == entry.fragment_count as usize && new_size <= MAX_UNASSOCIATED_PACKET_SIZE as u32 { + if got & entry.fragment_have == 0 + && fragment_count == entry.fragment_count as usize + && new_size <= MAX_UNASSOCIATED_PACKET_SIZE as u32 + { entry.packet_size = new_size; entry.fragment_have |= got; @@ -156,7 +162,6 @@ impl UnassociatedFragCache { let start_idx = entry.frags_idx as usize; unsafe { for i in start_idx..start_idx + fragment_count { - ret_assembled.push(self.frags[i % self.frags.len()].assume_init_read()) } } @@ -263,13 +268,30 @@ fn test_cache() { // If the timeout is 1 we should be guaranteed to get our packet cached. let mut nonce = [0; 12]; nonce[..4].copy_from_slice(&i.to_be_bytes()); - cache.assemble(&nonce, 0, fragment.len(), fragment, j, fragment_count, 1, time, &mut assembled); + cache.assemble( + &nonce, + 0, + fragment.len(), + fragment, + j, + fragment_count, + 1, + time, + &mut assembled, + ); time += 1; } } if drop >= fragment_count { - assert!(!assembled.is_empty(), "Packet was dropped from the cache when it shouldn't have"); - assert_eq!(assembled.as_ref().len(), fragment_count, "Cache returned the wrong packet"); + assert!( + !assembled.is_empty(), + "Packet was dropped from the cache when it shouldn't have" + ); + assert_eq!( + assembled.as_ref().len(), + fragment_count, + "Cache returned the wrong packet" + ); for j in 0..fragment_count { assert_eq!(assembled.as_ref()[j][7], r, "Cache returned a corrupted packet"); } @@ -281,14 +303,25 @@ fn test_cache() { if in_progress.len() > 0 { let to_remain = (xorshift64_random() as usize % in_progress_fragments) + 16; while in_progress_fragments > to_remain { - let (id, fragment_count, mut packet) = in_progress.swap_remove(xorshift64_random() as usize % in_progress.len()); + let (id, fragment_count, mut packet) = + in_progress.swap_remove(xorshift64_random() as usize % in_progress.len()); for _ in 0..((xorshift64_random() as usize % packet.len()) + 1) { let (no, fragment) = packet.swap_remove(xorshift64_random() as usize % packet.len()); assembled.clear(); let mut nonce = [0; 12]; nonce[..4].copy_from_slice(&id.to_be_bytes()); - cache.assemble(&nonce, 0, fragment.len(), fragment, no as usize, fragment_count as usize, 1000, time, &mut assembled); + cache.assemble( + &nonce, + 0, + fragment.len(), + fragment, + no as usize, + fragment_count as usize, + 1000, + time, + &mut assembled, + ); time += 1; in_progress_fragments -= 1; diff --git a/src/indexed_heap.rs b/src/indexed_heap.rs index 70085dc..c83b8ca 100644 --- a/src/indexed_heap.rs +++ b/src/indexed_heap.rs @@ -52,7 +52,8 @@ impl IndexedBinaryHeap { let child0_idx = parent_idx * 2 + 1; let child1_idx = child0_idx + 1; if child0_idx < self.data.len() { - let largest_child = if child1_idx < self.data.len() && self.data[child1_idx].1 > self.data[child0_idx].1 { + let largest_child = if child1_idx < self.data.len() && self.data[child1_idx].1 > self.data[child0_idx].1 + { child1_idx } else { child0_idx @@ -155,7 +156,8 @@ impl IndexedBinaryHeap { .map(|data_idx| std::mem::replace(&mut self.data[data_idx].0, new_item)) } pub fn get(&self, idx: BinaryHeapIndex) -> Option<(&T, &P)> { - self.deref_index(idx).map(|data_idx| (&self.data[data_idx].0, &self.data[data_idx].1)) + self.deref_index(idx) + .map(|data_idx| (&self.data[data_idx].0, &self.data[data_idx].1)) } pub fn get_mut(&mut self, idx: BinaryHeapIndex) -> Option<(&mut T, &P)> { self.deref_index(idx).map(|data_idx| { diff --git a/src/lib.rs b/src/lib.rs index 61c0db5..44a6e59 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,20 +7,20 @@ */ pub mod crypto; +mod antireplay; mod applicationlayer; -mod fragged; +mod challenge; mod frag_cache; +mod fragged; mod handshake_cache; mod indexed_heap; mod log_event; mod proto; mod ratchet_state; -mod symmetric_state; -mod antireplay; -mod challenge; pub mod result; -mod zssp; +mod symmetric_state; mod zeta; +mod zssp; //mod context; //pub mod error; diff --git a/src/log_event.rs b/src/log_event.rs index cf96367..8690bf9 100644 --- a/src/log_event.rs +++ b/src/log_event.rs @@ -7,7 +7,7 @@ */ use std::sync::Arc; -use crate::{ApplicationLayer, zeta::Session}; +use crate::{zeta::Session, ApplicationLayer}; /// ZSSP events that might be interesting to log or aggregate into metrics. pub enum LogEvent<'a, Application: ApplicationLayer> { @@ -51,9 +51,12 @@ impl<'a, Application: ApplicationLayer> std::fmt::Debug for LogEvent<'a, Applica ServiceKKTimeout(_) => write!(f, "ServiceKKTimeout"), ServiceKeyConfirmResend(_) => write!(f, "ServiceKeyConfirmResend"), ServiceKeyConfirmTimeout(_) => write!(f, "ServiceKeyConfirmTimeout"), - ReceiveUnassociatedFragment(arg0, arg1, arg2) => { - f.debug_tuple("ReceiveUnassociatedFragment").field(arg0).field(arg1).field(arg2).finish() - } + ReceiveUnassociatedFragment(arg0, arg1, arg2) => f + .debug_tuple("ReceiveUnassociatedFragment") + .field(arg0) + .field(arg1) + .field(arg2) + .finish(), ReceiveUncheckedXK1 => write!(f, "ReceiveUncheckedXK1"), ReceiveCheckXK1Challenge(arg0) => f.debug_tuple("ReceiveCheckXK1Challenge").field(arg0).finish(), ReceiveValidXK1 => write!(f, "ReceiveValidXK1"), diff --git a/src/proto.rs b/src/proto.rs index c11f88f..d7d5eaf 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -1,4 +1,9 @@ -use crate::crypto::{aes::{AES_GCM_TAG_SIZE, AES_GCM_IV_SIZE}, kyber1024::{KYBER_CIPHERTEXT_SIZE, KYBER_PUBLIC_KEY_SIZE}, p384::P384_PUBLIC_KEY_SIZE, sha512::SHA512_HASH_SIZE}; +use crate::crypto::{ + aes::{AES_GCM_IV_SIZE, AES_GCM_TAG_SIZE}, + kyber1024::{KYBER_CIPHERTEXT_SIZE, KYBER_PUBLIC_KEY_SIZE}, + p384::P384_PUBLIC_KEY_SIZE, + sha512::SHA512_HASH_SIZE, +}; /* Common constants */ @@ -75,7 +80,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"; @@ -101,7 +107,7 @@ pub(crate) const COUNTER_WINDOW_MAX_SKIP_AHEAD: u64 = 1 << 24; /// 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; -pub(crate) const THREAD_SAFE_COUNTER_HARD_EXPIRE: u64 = u64::MAX - 1<<16; +pub(crate) const THREAD_SAFE_COUNTER_HARD_EXPIRE: u64 = u64::MAX - 1 << 16; /* Packet constants */ @@ -117,7 +123,8 @@ 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_HELLO_CHALLENGE_MIN_SIZE: usize = HANDSHAKE_HELLO_MIN_SIZE + CHALLENGE_SIZE; @@ -125,7 +132,8 @@ pub(crate) const HANDSHAKE_HELLO_CHALLENGE_MAX_SIZE: usize = HANDSHAKE_HELLO_MAX pub(crate) const HEADERED_HANDSHAKE_HELLO_CHALLENGE_MAX_SIZE: usize = HANDSHAKE_HELLO_CHALLENGE_MAX_SIZE + HEADER_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 HEADERED_HANDSHAKE_RESPONSE_SIZE: usize = HANDSHAKE_RESPONSE_SIZE + HEADER_SIZE; pub(crate) const HANDSHAKE_COMPLETION_MIN_SIZE: usize = P384_PUBLIC_KEY_SIZE + AES_GCM_TAG_SIZE + 0 + AES_GCM_TAG_SIZE; diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index 591fdb4..e2f0e5c 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -3,10 +3,10 @@ use std::marker::PhantomData; use arrayvec::ArrayVec; use zeroize::Zeroizing; -use crate::crypto::aes::{LowThroughputAesGcm, HighThroughputAesGcmPool, AES_GCM_IV_SIZE, AES_GCM_TAG_SIZE}; +use crate::crypto::aes::{HighThroughputAesGcmPool, LowThroughputAesGcm, AES_GCM_IV_SIZE, AES_GCM_TAG_SIZE}; use crate::crypto::sha512::{HashSha512, HmacSha512}; -use crate::{applicationlayer::ApplicationLayer, crypto::aes::AES_256_KEY_SIZE}; use crate::proto::*; +use crate::{applicationlayer::ApplicationLayer, crypto::aes::AES_256_KEY_SIZE}; pub struct SymmetricState { k: Zeroizing<[u8; AES_256_KEY_SIZE]>, @@ -27,7 +27,6 @@ impl Clone for SymmetricState { } } - impl SymmetricState { /// HMAC-SHA512 key derivation based on KBKDF Counter Mode: /// https://csrc.nist.gov/publications/detail/sp/800-108/rev-1/final. @@ -93,7 +92,15 @@ impl SymmetricState { let mut next_ck = Zeroizing::new([0u8; HASHLEN]); let mut temp_k = Zeroizing::new([0u8; HASHLEN]); - self.kbkdf(hmac, input_key_material, LABEL_KBKDF_CHAIN, 2, &mut next_ck, Some(&mut temp_k), None); + self.kbkdf( + hmac, + 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]); @@ -133,7 +140,12 @@ impl SymmetricState { self.k.clone_from_slice(&temp_k[..AES_256_KEY_SIZE]); } /// Corresponds to Noise `MixKeyAndHash`. - pub fn mix_key_and_hash_no_init(&mut self, hash: &mut App::Hash, hmac: &mut App::HmacHash, input_key_material: &[u8]) { + pub fn mix_key_and_hash_no_init( + &mut self, + hash: &mut App::Hash, + hmac: &mut App::HmacHash, + input_key_material: &[u8], + ) { let mut next_ck = Zeroizing::new([0u8; HASHLEN]); let mut temp_h = [0u8; HASHLEN]; @@ -152,7 +164,12 @@ impl SymmetricState { } /// Corresponds to Noise `EncryptAndHash`. #[must_use] - pub fn encrypt_and_hash_in_place(&mut self, hash: &mut App::Hash, iv: [u8; AES_GCM_IV_SIZE], data: &mut [u8]) -> [u8; AES_GCM_TAG_SIZE] { + pub fn encrypt_and_hash_in_place( + &mut self, + hash: &mut App::Hash, + iv: [u8; AES_GCM_IV_SIZE], + data: &mut [u8], + ) -> [u8; AES_GCM_TAG_SIZE] { let tag = App::Aead::encrypt_in_place(&self.k, &iv, &self.h, data); hash.update(&self.h); hash.update(data); @@ -162,7 +179,13 @@ impl SymmetricState { } /// Corresponds to Noise `DecryptAndHash`. #[must_use] - pub fn decrypt_and_hash_in_place(&mut self, hash: &mut App::Hash, iv: [u8; AES_GCM_IV_SIZE], data: &mut [u8], tag: [u8; AES_GCM_TAG_SIZE]) -> bool { + pub fn decrypt_and_hash_in_place( + &mut self, + hash: &mut App::Hash, + iv: [u8; AES_GCM_IV_SIZE], + data: &mut [u8], + tag: [u8; AES_GCM_TAG_SIZE], + ) -> bool { hash.update(&self.h); hash.update(data); hash.update(&tag); @@ -178,7 +201,13 @@ impl SymmetricState { /// is forward secrect and is cryptographically independent from all other produced keys. /// Based on Noise's unstable ASK mechanism, using KBKDF instead of HKDF. /// https://github.com/noiseprotocol/noise_wiki/wiki/Additional-Symmetric-Keys. - pub fn get_ask(&self, hmac: &mut App::HmacHash, label: &[u8; 4], key1: &mut [u8; HASHLEN], key2: &mut [u8; HASHLEN]) { + pub fn get_ask( + &self, + hmac: &mut App::HmacHash, + label: &[u8; 4], + key1: &mut [u8; HASHLEN], + key2: &mut [u8; HASHLEN], + ) { self.kbkdf(hmac, &self.h, label, 2, key1, Some(key2), None); } /// Used for internally debugging a key exchange. diff --git a/src/zeta.rs b/src/zeta.rs index 8475cf0..f0b0c4a 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -5,26 +5,28 @@ use std::collections::HashMap; use std::io::Write; use std::num::NonZeroU32; use std::ops::{Deref, DerefMut}; -use std::sync::atomic::{AtomicU64, Ordering, AtomicBool, AtomicI64}; -use std::sync::{Arc, Mutex, Weak, RwLock, MutexGuard, RwLockWriteGuard}; +use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering}; +use std::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockWriteGuard, Weak}; use zeroize::Zeroizing; use crate::antireplay::Window; use crate::applicationlayer::ApplicationLayer; use crate::applicationlayer::RatchetUpdate; use crate::challenge::{gen_null_response, respond_to_challenge_in_place}; -use crate::zssp::{ContextInner, log}; +use crate::zssp::{log, ContextInner}; //use crate::context::{log, ContextInner, SessionMap}; use crate::crypto::aes::*; +use crate::crypto::kyber1024::{ + Kyber1024PrivateKey, KYBER_CIPHERTEXT_SIZE, KYBER_PLAINTEXT_SIZE, KYBER_PUBLIC_KEY_SIZE, +}; use crate::crypto::p384::{P384KeyPair, P384PublicKey, P384_ECDH_SHARED_SECRET_SIZE, P384_PUBLIC_KEY_SIZE}; use crate::crypto::sha512::{HashSha512, HmacSha512}; -use crate::crypto::kyber1024::{Kyber1024PrivateKey, KYBER_PUBLIC_KEY_SIZE, KYBER_CIPHERTEXT_SIZE, KYBER_PLAINTEXT_SIZE}; +use crate::fragged::Fragged; use crate::indexed_heap::BinaryHeapIndex; use crate::proto::*; use crate::ratchet_state::RatchetState; -use crate::result::{byzantine_fault, FaultType, OpenError, ReceiveError, SendError, ReceiveOk}; +use crate::result::{byzantine_fault, FaultType, OpenError, ReceiveError, ReceiveOk, SendError}; use crate::symmetric_state::SymmetricState; -use crate::fragged::Fragged; #[cfg(feature = "logging")] use crate::LogEvent::*; @@ -50,11 +52,19 @@ pub(crate) fn from_nonce(n: &[u8]) -> (u8, u64) { let c_start = n.len() - 8; (n[c_start - 1], u64::from_be_bytes(n[c_start..].try_into().unwrap())) } -fn create_ratchet_state(hmac: &mut App::HmacHash, noise: &SymmetricState, pre_chain_len: u64) -> RatchetState { +fn create_ratchet_state( + hmac: &mut App::HmacHash, + noise: &SymmetricState, + pre_chain_len: u64, +) -> RatchetState { let mut rk = Zeroizing::new([0u8; HASHLEN]); let mut rf = Zeroizing::new([0u8; HASHLEN]); noise.get_ask(hmac, LABEL_RATCHET_STATE, &mut rk, &mut rf); - RatchetState::new(Zeroizing::new(rk[..RATCHET_SIZE].try_into().unwrap()), Zeroizing::new(rf[..RATCHET_SIZE].try_into().unwrap()), pre_chain_len + 1) + RatchetState::new( + Zeroizing::new(rk[..RATCHET_SIZE].try_into().unwrap()), + Zeroizing::new(rf[..RATCHET_SIZE].try_into().unwrap()), + pre_chain_len + 1, + ) } fn get_counter(session: &Session, state: &MutableState) -> Option<(u64, bool)> { if session.session_has_expired.load(Ordering::Relaxed) { @@ -131,7 +141,10 @@ impl Default for DuplexKey { } impl DuplexKey { fn replace_nk(&mut self, nk_send: &[u8; HASHLEN], nk_recv: &[u8; HASHLEN]) { - self.nk = Some(App::AeadPool::new((&nk_send[..AES_256_KEY_SIZE]).try_into().unwrap(), (&nk_recv[..AES_256_KEY_SIZE]).try_into().unwrap())) + self.nk = Some(App::AeadPool::new( + (&nk_send[..AES_256_KEY_SIZE]).try_into().unwrap(), + (&nk_recv[..AES_256_KEY_SIZE]).try_into().unwrap(), + )) } } @@ -144,7 +157,9 @@ impl Keys { fn replace_kek(&mut self, kek: &[u8; HASHLEN]) { // We want to give rust the best chance of implementing this in a way that does // not leak the key on the stack. - self.kek.get_or_insert(Zeroizing::new([0u8; AES_256_KEY_SIZE])).copy_from_slice(&kek[..AES_256_KEY_SIZE]); + self.kek + .get_or_insert(Zeroizing::new([0u8; AES_256_KEY_SIZE])) + .copy_from_slice(&kek[..AES_256_KEY_SIZE]); } } @@ -185,7 +200,13 @@ pub(crate) enum ZetaAutomata { } impl SymmetricState { - fn write_e(&mut self, hash: &mut App::Hash, hmac: &mut App::HmacHash, rng: &Mutex, packet: &mut ArrayVec) -> App::KeyPair { + fn write_e( + &mut self, + hash: &mut App::Hash, + hmac: &mut App::HmacHash, + rng: &Mutex, + packet: &mut ArrayVec, + ) -> App::KeyPair { let e_secret = App::KeyPair::generate(rng.lock().unwrap().deref_mut()); let pub_key = e_secret.public_key_bytes(); packet.extend(pub_key); @@ -193,7 +214,13 @@ impl SymmetricState { self.mix_key(hmac, &pub_key); e_secret } - fn read_e(&mut self, hash: &mut App::Hash, hmac: &mut App::HmacHash, i: &mut usize, packet: &[u8]) -> Option { + fn read_e( + &mut self, + hash: &mut App::Hash, + hmac: &mut App::HmacHash, + i: &mut usize, + packet: &[u8], + ) -> Option { let j = *i + P384_PUBLIC_KEY_SIZE; let pub_key = &packet[*i..j]; self.mix_hash(hash, pub_key); @@ -238,7 +265,9 @@ fn set_header(packet: &mut [u8], kid_send: u32, nonce: &[u8; AES_GCM_IV_SIZE]) { } fn create_a1_state( - hash: &mut App::Hash, hmac: &mut App::HmacHash, rng: &Mutex, + hash: &mut App::Hash, + hmac: &mut App::HmacHash, + rng: &Mutex, s_remote: &App::PublicKey, kid_recv: NonZeroU32, ratchet_state1: &RatchetState, @@ -312,11 +341,21 @@ pub(crate) fn trans_to_a1( let hash = &mut App::Hash::new(); let hmac = &mut App::HmacHash::new(); - let a1 = create_a1_state(hash, hmac, &ctx.rng, &s_remote, kid_recv, &ratchet_state1, ratchet_state2.as_ref(), identity).ok_or(OpenError::InvalidPublicKey)?; + let a1 = create_a1_state( + hash, + hmac, + &ctx.rng, + &s_remote, + kid_recv, + &ratchet_state1, + ratchet_state2.as_ref(), + identity, + ) + .ok_or(OpenError::InvalidPublicKey)?; let mut noise_kk_ss = Zeroizing::new([0u8; P384_ECDH_SHARED_SECRET_SIZE]); if !ctx.s_secret.agree(&s_remote, &mut noise_kk_ss) { - return Err(OpenError::InvalidPublicKey) + return Err(OpenError::InvalidPublicKey); } let mut hk_recv = Zeroizing::new([0u8; HASHLEN]); @@ -359,18 +398,18 @@ pub(crate) fn trans_to_a1( } session_map.insert(kid_recv, Arc::downgrade(&session)); - session_queue.push_reserved( - queue_idx, - Arc::downgrade(&session), - Reverse(resend_timer), - ); + session_queue.push_reserved(queue_idx, Arc::downgrade(&session), Reverse(resend_timer)); send(&mut x1, None); Ok(session) } /// Corresponds to Algorithm 13 found in Section 5. -pub(crate) fn respond_to_challenge(ctx: &Arc>, session: &Session, challenge: &[u8; CHALLENGE_SIZE]) { +pub(crate) fn respond_to_challenge( + ctx: &Arc>, + session: &Session, + challenge: &[u8; CHALLENGE_SIZE], +) { let mut state = session.state.write().unwrap(); if let ZetaAutomata::A1(a1) = &mut state.beta { let response_start = a1.x1.len() - CHALLENGE_SIZE; @@ -408,13 +447,18 @@ pub(crate) fn received_x1_trans( // Noise process prologue. let j = i + KID_SIZE; noise.mix_hash(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(hash, &ctx.s_secret.public_key_bytes()); i = j; // Process message pattern 1 e token. - let e_remote = noise.read_e(hash, hmac, &mut i, &x1).ok_or(byzantine_fault!(FailedAuth, true))?; + let e_remote = noise + .read_e(hash, hmac, &mut i, &x1) + .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 1 es token. - noise.mix_dh(hmac, &ctx.s_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise + .mix_dh(hmac, &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 = i + AES_GCM_TAG_SIZE; @@ -462,12 +506,19 @@ pub(crate) fn received_x1_trans( // Process message pattern 2 e token. let e_secret = noise.write_e(hash, hmac, &ctx.rng, &mut x2); // Process message pattern 2 ee token. - noise.mix_dh(hmac, &e_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise + .mix_dh(hmac, &e_secret, &e_remote) + .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 ekem1 token. { let i = x2.len(); let mut ekem1_secret = Zeroizing::new([0u8; KYBER_PLAINTEXT_SIZE]); - let ekem1 = App::Kem::encapsulate(ctx.rng.lock().unwrap().deref_mut(), (&x1[e1_start..e1_end]).try_into().unwrap(), &mut ekem1_secret).ok_or(byzantine_fault!(FailedAuth, true))?; + let ekem1 = App::Kem::encapsulate( + ctx.rng.lock().unwrap().deref_mut(), + (&x1[e1_start..e1_end]).try_into().unwrap(), + &mut ekem1_secret, + ) + .ok_or(byzantine_fault!(FailedAuth, true))?; x2.extend(ekem1); let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), &mut x2[i..]); x2.extend(tag); @@ -476,7 +527,10 @@ pub(crate) fn received_x1_trans( // Process message pattern 2 psk2 token. noise.mix_key_and_hash(hash, hmac, ratchet_state.key.as_ref()); // Process message pattern 2 payload. - let kid_recv = gen_kid(ctx.session_map.read().unwrap().deref(), ctx.rng.lock().unwrap().deref_mut()); + let kid_recv = gen_kid( + ctx.session_map.read().unwrap().deref(), + ctx.rng.lock().unwrap().deref_mut(), + ); let i = x2.len(); x2.extend(kid_recv.get().to_be_bytes()); @@ -504,10 +558,13 @@ pub(crate) fn received_x1_trans( noise, defrag: Mutex::new(Fragged::new()), }), - app.time() + app.time(), ); - send(&mut x2, Some(&App::PrpEnc::new(&hk_send[..AES_256_KEY_SIZE].try_into().unwrap()))); + send( + &mut x2, + Some(&App::PrpEnc::new(&hk_send[..AES_256_KEY_SIZE].try_into().unwrap())), + ); Ok(()) } /// Corresponds to Transition Algorithm 3 found in Section 4.3. @@ -548,9 +605,13 @@ pub(crate) fn received_x2_trans( let mut noise = a1.noise.clone(); let mut i = 0; // Process message pattern 2 e token. - let e_remote = noise.read_e(hash, hmac, &mut i, &x2).ok_or(byzantine_fault!(FailedAuth, true))?; + let e_remote = noise + .read_e(hash, hmac, &mut i, &x2) + .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 ee token. - noise.mix_dh(hmac, &a1.e_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise + .mix_dh(hmac, &a1.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; @@ -559,7 +620,10 @@ pub(crate) fn received_x2_trans( return Err(byzantine_fault!(FailedAuth, true)); } let mut ekem1_secret = Zeroizing::new([0u8; KYBER_PLAINTEXT_SIZE]); - if !a1.e1_secret.decapsulate((&x2[i..j]).try_into().unwrap(), &mut ekem1_secret) { + if !a1 + .e1_secret + .decapsulate((&x2[i..j]).try_into().unwrap(), &mut ekem1_secret) + { return Err(byzantine_fault!(FailedAuth, true)); } noise.mix_key(hmac, ekem1_secret.as_ref()); @@ -618,7 +682,9 @@ pub(crate) fn received_x2_trans( let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 1), &mut x3[i..]); x3.extend(tag); // Process message pattern 3 se token. - noise.mix_dh(hmac, &ctx.s_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise + .mix_dh(hmac, &ctx.s_secret, &e_remote) + .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 3 payload. let i = x3.len(); x3.try_extend_from_slice(&a1.identity).unwrap(); @@ -676,11 +742,19 @@ pub(crate) fn received_x2_trans( // This return is unreachable. return Err(byzantine_fault!(FailedAuth, true)); }; - state.beta = ZetaAutomata::A3 { identity: a1.identity.clone(), x3: x3.clone(), kid_send: kid_send.get(), nonce }; + state.beta = ZetaAutomata::A3 { + identity: a1.identity.clone(), + x3: x3.clone(), + kid_send: kid_send.get(), + nonce, + }; resend_timer }; drop(kex_lock); - ctx.session_queue.lock().unwrap().change_priority(session.queue_idx, Reverse(resend_timer)); + ctx.session_queue + .lock() + .unwrap() + .change_priority(session.queue_idx, Reverse(resend_timer)); Ok(x3) })(); @@ -722,10 +796,13 @@ pub(crate) fn received_x3_trans( if !noise.decrypt_and_hash_in_place(hash, 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(hmac, &zeta.e_secret, &s_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise + .mix_dh(hmac, &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; @@ -752,7 +829,12 @@ pub(crate) fn received_x3_trans( let mut d = ArrayVec::::new(); d.extend([0u8; HEADER_SIZE]); let nonce = to_nonce(PACKET_TYPE_SESSION_REJECTED, c); - d.extend(App::Aead::encrypt_in_place((&kek_send[..AES_256_KEY_SIZE]).try_into().unwrap(), &nonce, &[], &mut [])); + d.extend(App::Aead::encrypt_in_place( + (&kek_send[..AES_256_KEY_SIZE]).try_into().unwrap(), + &nonce, + &[], + &mut [], + )); set_header(&mut d, zeta.kid_send.get(), &nonce); d }; @@ -891,7 +973,12 @@ pub(crate) fn received_c1_trans( return Err(byzantine_fault!(OutOfSequence, false)); }; - let specified_key = state.key_ref(is_other).recv.kek.as_ref().ok_or(byzantine_fault!(OutOfSequence, true))?; + let specified_key = state + .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, &[], &mut [], tag) { return Err(byzantine_fault!(FailedAuth, true)); @@ -928,13 +1015,17 @@ pub(crate) fn received_c1_trans( state.timeout_timer = app.time() + App::SETTINGS .rekey_after_time - .saturating_sub(ctx.rng.lock().unwrap().next_u64() % App::SETTINGS.rekey_time_max_jitter) as i64; + .saturating_sub(ctx.rng.lock().unwrap().next_u64() % App::SETTINGS.rekey_time_max_jitter) + as i64; state.resend_timer = AtomicI64::new(i64::MAX); state.beta = ZetaAutomata::S2; state.timeout_timer }; drop(kex_lock); - ctx.session_queue.lock().unwrap().change_priority(session.queue_idx, Reverse(timeout_timer)); + ctx.session_queue + .lock() + .unwrap() + .change_priority(session.queue_idx, Reverse(timeout_timer)); state = session.state.read().unwrap(); } } @@ -943,9 +1034,18 @@ pub(crate) fn received_c1_trans( c2.extend([0u8; HEADER_SIZE]); let (c, _) = get_counter(session, &state).ok_or(byzantine_fault!(ExpiredCounter, false))?; let nonce = to_nonce(PACKET_TYPE_ACK, c); - let latest_confirmed_key = state.key_ref(false).send.kek.as_ref().ok_or(byzantine_fault!(OutOfSequence, true))?; + let latest_confirmed_key = state + .key_ref(false) + .send + .kek + .as_ref() + .ok_or(byzantine_fault!(OutOfSequence, true))?; c2.extend(App::Aead::encrypt_in_place(latest_confirmed_key, &nonce, &[], &mut [])); - let kid_send = state.key_ref(false).send.kid.ok_or(byzantine_fault!(OutOfSequence, false))?; + let kid_send = state + .key_ref(false) + .send + .kid + .ok_or(byzantine_fault!(OutOfSequence, false))?; set_header(&mut c2, kid_send.get(), &nonce); send(&mut c2, Some(&session.hk_send)); @@ -993,13 +1093,17 @@ pub(crate) fn received_c2_trans( state.timeout_timer = app.time() + App::SETTINGS .rekey_after_time - .saturating_sub(ctx.rng.lock().unwrap().next_u64() % App::SETTINGS.rekey_time_max_jitter) as i64; + .saturating_sub(ctx.rng.lock().unwrap().next_u64() % App::SETTINGS.rekey_time_max_jitter) + as i64; state.resend_timer = AtomicI64::new(i64::MAX); state.beta = ZetaAutomata::S2; state.timeout_timer }; drop(kex_lock); - ctx.session_queue.lock().unwrap().change_priority(session.queue_idx, Reverse(timeout_timer)); + ctx.session_queue + .lock() + .unwrap() + .change_priority(session.queue_idx, Reverse(timeout_timer)); Ok(()) } /// Corresponds to the trivial Transition Algorithm described for processing D packets found in @@ -1086,8 +1190,12 @@ pub(crate) fn process_timers( drop(state); let resend_timer = { let mut state = session.state.write().unwrap(); - session.hk_recv.reset((&hk_recv[..AES_256_KEY_SIZE]).try_into().unwrap()); - session.hk_send.reset((&hk_send[..AES_256_KEY_SIZE]).try_into().unwrap()); + session + .hk_recv + .reset((&hk_recv[..AES_256_KEY_SIZE]).try_into().unwrap()); + session + .hk_send + .reset((&hk_send[..AES_256_KEY_SIZE]).try_into().unwrap()); *state.key_mut(true) = DuplexKey::default(); state.key_mut(true).recv.kid = Some(new_kid_recv); let resend_timer = current_time + App::SETTINGS.resend_time as i64; @@ -1151,7 +1259,12 @@ pub(crate) fn process_timers( if let Some((c, _)) = get_counter(session, &state) { let nonce = to_nonce(PACKET_TYPE_REKEY_INIT, c); - let tag = App::Aead::encrypt_in_place(state.key_ref(false).send.kek.as_ref().unwrap(), &nonce, &[], &mut k1); + let tag = App::Aead::encrypt_in_place( + state.key_ref(false).send.kek.as_ref().unwrap(), + &nonce, + &[], + &mut k1, + ); k1.extend(tag); set_header(&mut k1, state.key_ref(false).send.kid.unwrap().get(), &nonce); @@ -1208,9 +1321,18 @@ pub(crate) fn process_timers( }; if let Some((c, _)) = get_counter(session, &state) { let nonce = to_nonce(packet_type, c); - let tag = App::Aead::encrypt_in_place(state.key_ref(false).send.kek.as_ref().unwrap(), &nonce, &[], &mut control_payload); + let tag = App::Aead::encrypt_in_place( + state.key_ref(false).send.kek.as_ref().unwrap(), + &nonce, + &[], + &mut control_payload, + ); control_payload.extend(tag); - set_header(&mut control_payload, state.key_ref(false).send.kid.unwrap().get(), &nonce); + set_header( + &mut control_payload, + state.key_ref(false).send.kid.unwrap().get(), + &nonce, + ); send(&mut control_payload, Some(&session.hk_send)); } @@ -1220,7 +1342,11 @@ pub(crate) fn process_timers( } } } -fn remap(ctx: &Arc>, session: &Arc>, state: &MutableState) -> NonZeroU32 { +fn remap( + ctx: &Arc>, + session: &Arc>, + state: &MutableState, +) -> NonZeroU32 { let mut session_map = ctx.session_map.write().unwrap(); let weak = if let Some(Some(weak)) = state.key_ref(true).recv.kid.as_ref().map(|kid| session_map.remove(kid)) { weak @@ -1270,7 +1396,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(state.key_ref(false).recv.kek.as_ref().unwrap(), n, &[], &mut k1[..i], &tag) { + if !App::Aead::decrypt_in_place( + state.key_ref(false).recv.kek.as_ref().unwrap(), + n, + &[], + &mut k1[..i], + &tag, + ) { return Err(byzantine_fault!(FailedAuth, true)); } let (_, c) = from_nonce(n); @@ -1289,9 +1421,13 @@ pub(crate) fn received_k1_trans( // Process message pattern 1 psk0 token. noise.mix_key_and_hash(hash, hmac, state.ratchet_state1.key.as_ref()); // Process message pattern 1 e token. - let e_remote = noise.read_e(hash, hmac, &mut i, &k1).ok_or(byzantine_fault!(FailedAuth, true))?; + let e_remote = noise + .read_e(hash, hmac, &mut i, &k1) + .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 1 es token. - noise.mix_dh(hmac, &ctx.s_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise + .mix_dh(hmac, &ctx.s_secret, &e_remote) + .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 1 ss token. noise.mix_key(hmac, session.noise_kk_ss.as_ref()); // Process message pattern 1 payload. @@ -1301,16 +1437,21 @@ pub(crate) fn received_k1_trans( if !noise.decrypt_and_hash_in_place(hash, 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 = ArrayVec::::new(); k2.extend([0u8; HEADER_SIZE]); // Process message pattern 2 e token. let e_secret = noise.write_e(hash, hmac, &ctx.rng, &mut k2); // Process message pattern 2 ee token. - noise.mix_dh(hmac, &e_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise + .mix_dh(hmac, &e_secret, &e_remote) + .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 se token. - noise.mix_dh(hmac, &ctx.s_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise + .mix_dh(hmac, &ctx.s_secret, &e_remote) + .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 payload. let i = k2.len(); let new_kid_recv = remap(ctx, session, &state); @@ -1360,7 +1501,10 @@ pub(crate) fn received_k1_trans( resend_timer }; drop(kex_lock); - ctx.session_queue.lock().unwrap().change_priority(session.queue_idx, Reverse(resend_timer)); + ctx.session_queue + .lock() + .unwrap() + .change_priority(session.queue_idx, Reverse(resend_timer)); let state = session.state.read().unwrap(); let (c, _) = get_counter(session, &state).ok_or(byzantine_fault!(ExpiredCounter, false))?; @@ -1408,7 +1552,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(state.key_ref(false).recv.kek.as_ref().unwrap(), n, &[], &mut k2[..i], &tag) { + if !App::Aead::decrypt_in_place( + state.key_ref(false).recv.kek.as_ref().unwrap(), + n, + &[], + &mut k2[..i], + &tag, + ) { return Err(byzantine_fault!(FailedAuth, true)); } let (_, c) = from_nonce(n); @@ -1422,11 +1572,17 @@ pub(crate) fn received_k2_trans( let hash = &mut App::Hash::new(); let hmac = &mut App::HmacHash::new(); // Process message pattern 2 e token. - let e_remote = noise.read_e(hash, hmac, &mut i, &k2).ok_or(byzantine_fault!(FailedAuth, true))?; + let e_remote = noise + .read_e(hash, hmac, &mut i, &k2) + .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 ee token. - noise.mix_dh(hmac, e_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise + .mix_dh(hmac, e_secret, &e_remote) + .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 se token. - noise.mix_dh(hmac, e_secret, &session.s_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise + .mix_dh(hmac, e_secret, &session.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; @@ -1434,7 +1590,8 @@ pub(crate) fn received_k2_trans( if !noise.decrypt_and_hash_in_place(hash, 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 new_ratchet_state = create_ratchet_state(hmac, &noise, state.ratchet_state1.chain_len); let result = app.save_ratchet_state( @@ -1476,7 +1633,10 @@ pub(crate) fn received_k2_trans( (current_time, resend_timer) }; drop(kex_lock); - ctx.session_queue.lock().unwrap().change_priority(session.queue_idx, Reverse(resend_timer)); + ctx.session_queue + .lock() + .unwrap() + .change_priority(session.queue_idx, Reverse(resend_timer)); process_timers(app, ctx, session, current_time, false, true, send); Ok(()) @@ -1512,9 +1672,12 @@ pub(crate) fn receive_payload_in_place( return Err(byzantine_fault!(OutOfSequence, true)); }; - let mut cipher = state.key_ref(is_other).nk + let mut cipher = state + .key_ref(is_other) + .nk .as_ref() - .ok_or(byzantine_fault!(OutOfSequence, true))?.start_dec(n); + .ok_or(byzantine_fault!(OutOfSequence, true))? + .start_dec(n); // NOTE: This only works because we check the size of every received fragment in the receive // function, otherwise this could panic. @@ -1549,7 +1712,6 @@ pub(crate) fn receive_payload_in_place( Ok(()) } - impl Drop for Session { fn drop(&mut self) { self.expire(); @@ -1562,7 +1724,11 @@ impl Session { pub fn expire(&self) { self.expire_inner(self.state_machine_lock.lock().unwrap(), self.state.write().unwrap()); } - pub(crate) fn expire_inner(&self, kex_lock: MutexGuard<'_, ()>, mut state: RwLockWriteGuard<'_, MutableState>) { + pub(crate) fn expire_inner( + &self, + kex_lock: MutexGuard<'_, ()>, + mut state: RwLockWriteGuard<'_, MutableState>, + ) { let mut kids_to_remove = None; if !matches!(&state.beta, ZetaAutomata::Null) { self.session_has_expired.store(true, Ordering::Relaxed); @@ -1630,7 +1796,10 @@ impl Session { /// Check whether this session is established. pub fn established(&self) -> bool { let state = self.state.read().unwrap(); - !matches!(&state.beta, ZetaAutomata::A1(_) | ZetaAutomata::A3 {..} | ZetaAutomata::Null) + !matches!( + &state.beta, + ZetaAutomata::A1(_) | ZetaAutomata::A3 { .. } | ZetaAutomata::Null + ) } /// The static public key of the remote peer. pub fn remote_static_key(&self) -> &App::PublicKey { diff --git a/src/zssp.rs b/src/zssp.rs index 5af962f..fac5118 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -10,8 +10,8 @@ use std::cmp::Reverse; use std::collections::HashMap; -use std::io::Write; use std::hash::Hash; +use std::io::Write; use std::num::{NonZeroU32, NonZeroU64}; use std::ops::DerefMut; use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering}; @@ -20,21 +20,21 @@ use std::sync::{Arc, Mutex, MutexGuard, RwLock, Weak}; use arrayvec::ArrayVec; use zeroize::Zeroizing; -use crate::zeta::*; use crate::challenge::ChallengeContext; -use crate::crypto::aes::{AesDec, AesEnc, AES_256_KEY_SIZE, AES_GCM_TAG_SIZE, AES_GCM_IV_SIZE}; +use crate::crypto::aes::{AesDec, AesEnc, AES_256_KEY_SIZE, AES_GCM_IV_SIZE, AES_GCM_TAG_SIZE}; use crate::crypto::p384::{P384KeyPair, P384PublicKey, P384_ECDH_SHARED_SECRET_SIZE, P384_PUBLIC_KEY_SIZE}; use crate::crypto::pqc_kyber::KYBER_SECRETKEYBYTES; use crate::crypto::rand_core::RngCore; -use crate::crypto::sha512::{HmacSha512, HashSha512}; +use crate::crypto::sha512::{HashSha512, HmacSha512}; +use crate::zeta::*; -use crate::result::{FaultType, OpenError, ReceiveError, SendError, ReceiveOk, byzantine_fault, SessionEvent}; use crate::frag_cache::UnassociatedFragCache; use crate::fragged::{Assembled, Fragged}; use crate::handshake_cache::UnassociatedHandshakeCache; use crate::indexed_heap::{BinaryHeapIndex, IndexedBinaryHeap}; use crate::log_event::LogEvent; use crate::proto::*; +use crate::result::{byzantine_fault, FaultType, OpenError, ReceiveError, ReceiveOk, SendError, SessionEvent}; use crate::symmetric_state::SymmetricState; use crate::{applicationlayer::*, RatchetState}; @@ -82,7 +82,9 @@ pub enum IncomingSessionAction { Drop, } -fn parse_fragment_header(incoming_fragment: &[u8]) -> Result<(usize, usize, [u8; AES_GCM_IV_SIZE]), ReceiveError> { +fn parse_fragment_header( + incoming_fragment: &[u8], +) -> Result<(usize, usize, [u8; AES_GCM_IV_SIZE]), ReceiveError> { let fragment_no = incoming_fragment[FRAGMENT_NO_IDX] as usize; let fragment_count = incoming_fragment[FRAGMENT_COUNT_IDX] as usize; if fragment_no >= fragment_count || fragment_count > MAX_FRAGMENTS { @@ -93,7 +95,6 @@ fn parse_fragment_header(incoming_fragment: &[u8]) -> Result<(usiz Ok((fragment_no, fragment_count, nonce)) } - /// Fragments and sends the packet, destroying it in the process. /// /// Corresponds to the fragmentation algorithm described in Section 6. @@ -119,9 +120,7 @@ fn send_with_fragmentation( fragment[FRAGMENT_COUNT_IDX] = fragment_count as u8; if let Some(hk_send) = hk_send { - hk_send.encrypt_in_place( - (&mut fragment[HEADER_AUTH_START..HEADER_AUTH_END]).try_into().unwrap(), - ); + hk_send.encrypt_in_place((&mut fragment[HEADER_AUTH_START..HEADER_AUTH_END]).try_into().unwrap()); } if !send(fragment) { return false; @@ -184,7 +183,7 @@ impl Context { identity, |packet, hk_send| { send_with_fragmentation(send, mtu, packet, hk_send); - } + }, ) } @@ -256,7 +255,8 @@ impl Context { let (fragment_no, fragment_count, nonce) = parse_fragment_header(incoming_fragment)?; let (packet_type, incoming_counter) = from_nonce(&nonce); - {//vrfy + { + //vrfy if packet_type != PACKET_TYPE_DATA { log!(app, ReceivedRawFragment(p, c, fragment_no, fragment_count)); } @@ -332,7 +332,9 @@ impl Context { return Ok(ReceiveOk::Unassociated); } else { for fragment in fragment_buffer.as_ref() { - buffer.try_extend_from_slice(&fragment.as_ref()[HEADER_SIZE..]).map_err(|_| byzantine_fault!(InvalidPacket, true))?; + buffer + .try_extend_from_slice(&fragment.as_ref()[HEADER_SIZE..]) + .map_err(|_| byzantine_fault!(InvalidPacket, true))?; } // We have not yet authenticated the sender so we do not report // receiving a packet from them. @@ -351,7 +353,13 @@ impl Context { match packet_type { PACKET_TYPE_HANDSHAKE_RESPONSE => { log!(app, ReceivedRawX2); - received_x2_trans( + received_x2_trans(app, ctx, &session, kid_recv, &nonce, assembled_packet, send_associated)?; + log!(app, X2IsAuthSentX3(&session)); + SessionEvent::Control + } + PACKET_TYPE_KEY_CONFIRM => { + log!(app, ReceivedRawKeyConfirm); + let result = received_c1_trans( app, ctx, &session, @@ -360,13 +368,6 @@ impl Context { assembled_packet, send_associated, )?; - log!(app, X2IsAuthSentX3(&session)); - SessionEvent::Control - } - PACKET_TYPE_KEY_CONFIRM => { - log!(app, ReceivedRawKeyConfirm); - let result = - received_c1_trans(app, ctx, &session, kid_recv, &nonce, assembled_packet,send_associated)?; log!(app, KeyConfirmIsAuthSentAck(&session)); if result { SessionEvent::Established @@ -416,10 +417,14 @@ impl Context { let (fragment_no, fragment_count, nonce) = parse_fragment_header(incoming_fragment)?; let (packet_type, incoming_counter) = from_nonce(&nonce); - {//vrfy - log!(app, ReceivedRawFragment(packet_type, incoming_counter, frag_no, frag_count)); + { + //vrfy + log!( + app, + ReceivedRawFragment(packet_type, incoming_counter, frag_no, frag_count) + ); if packet_type != PACKET_TYPE_HANDSHAKE_COMPLETION || incoming_counter != 0 { - return Err(byzantine_fault!(InvalidPacket, true)) + return Err(byzantine_fault!(InvalidPacket, true)); } } @@ -430,13 +435,15 @@ impl Context { incoming_fragment_buf, fragment_no, fragment_count, - &mut fragment_buffer + &mut fragment_buffer, ); if fragment_buffer.is_empty() { return Ok(ReceiveOk::Unassociated); } else { for fragment in fragment_buffer.as_ref() { - buffer.try_extend_from_slice(&fragment.as_ref()[HEADER_SIZE..]).map_err(|_| byzantine_fault!(InvalidPacket, true))?; + buffer + .try_extend_from_slice(&fragment.as_ref()[HEADER_SIZE..]) + .map_err(|_| byzantine_fault!(InvalidPacket, true))?; } buffer.as_mut() } @@ -451,12 +458,7 @@ impl Context { log!(app, ReceivedRawX3); let session = received_x3_trans(app, ctx, zeta, kid_recv, assembled_packet, |packet, hk_send| { - send_with_fragmentation( - send_unassociated_reply, - send_unassociated_mtu, - packet, - hk_send, - ); + send_with_fragmentation(send_unassociated_reply, send_unassociated_mtu, packet, hk_send); })?; log!(app, X3IsAuthSentKeyConfirm(&session)); Ok(ReceiveOk::Session(session, SessionEvent::NewSession)) @@ -471,10 +473,11 @@ impl Context { let (fragment_no, fragment_count, nonce) = parse_fragment_header(incoming_fragment)?; let (packet_type, _c) = from_nonce(&nonce); - {//vrfy + { + //vrfy log!(app, ReceivedRawFragment(packet_type, _c, frag_no, frag_count)); if packet_type != PACKET_TYPE_HANDSHAKE_HELLO && packet_type != PACKET_TYPE_CHALLENGE { - return Err(byzantine_fault!(InvalidPacket, true)) + return Err(byzantine_fault!(InvalidPacket, true)); } } @@ -489,13 +492,15 @@ impl Context { fragment_count, App::SETTINGS.resend_time as i64, app.time(), - &mut fragment_buffer + &mut fragment_buffer, ); if fragment_buffer.is_empty() { return Ok(ReceiveOk::Unassociated); } else { for fragment in fragment_buffer.as_ref() { - buffer.try_extend_from_slice(&fragment.as_ref()[HEADER_SIZE..]).map_err(|_| byzantine_fault!(InvalidPacket, true))?; + buffer + .try_extend_from_slice(&fragment.as_ref()[HEADER_SIZE..]) + .map_err(|_| byzantine_fault!(InvalidPacket, true))?; } buffer.as_mut() } @@ -506,17 +511,24 @@ impl Context { if packet_type == PACKET_TYPE_HANDSHAKE_HELLO { log!(app, ReceivedRawX1); - if !(HANDSHAKE_HELLO_CHALLENGE_MIN_SIZE..=HANDSHAKE_HELLO_CHALLENGE_MAX_SIZE).contains(&assembled_packet.len()) { + if !(HANDSHAKE_HELLO_CHALLENGE_MIN_SIZE..=HANDSHAKE_HELLO_CHALLENGE_MAX_SIZE) + .contains(&assembled_packet.len()) + { return Err(byzantine_fault!(InvalidPacket, true)); } // Process recv challenge layer. let challenge_start = assembled_packet.len() - CHALLENGE_SIZE; - let result = ctx.challenge.process_hello::(remote_address, (&assembled_packet[challenge_start..]).try_into().unwrap()); + let result = ctx.challenge.process_hello::( + remote_address, + (&assembled_packet[challenge_start..]).try_into().unwrap(), + ); if let Err(challenge) = result { log!(app, X1FailedChallengeSentNewChallenge); let mut challenge_packet = ArrayVec::::new(); challenge_packet.extend([0u8; HEADER_SIZE]); - challenge_packet.try_extend_from_slice(&assembled_packet[..KID_SIZE]).unwrap(); + challenge_packet + .try_extend_from_slice(&assembled_packet[..KID_SIZE]) + .unwrap(); challenge_packet.extend(challenge); let nonce = to_nonce(PACKET_TYPE_CHALLENGE, ctx.rng.lock().unwrap().next_u64()); challenge_packet[FRAGMENT_COUNT_IDX] = 1; @@ -531,12 +543,7 @@ impl Context { // Process recv zeta layer. received_x1_trans(app, ctx, &nonce, assembled_packet, |packet, hk_send| { - send_with_fragmentation( - send_unassociated_reply, - send_unassociated_mtu, - packet, - hk_send, - ); + send_with_fragmentation(send_unassociated_reply, send_unassociated_mtu, packet, hk_send); })?; log!(app, X1IsAuthSentX2); @@ -547,7 +554,9 @@ 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(kid_recv) = + NonZeroU32::new(u32::from_be_bytes(assembled_packet[..KID_SIZE].try_into().unwrap())) + { if let Some(Some(session)) = ctx.session_map.read().unwrap().get(&kid_recv).map(|r| r.upgrade()) { respond_to_challenge(ctx, &session, &assembled_packet[KID_SIZE..].try_into().unwrap()); log!(app, ChallengeIsAuth(&session)); From 2a7784b5226bce33fdb6abdac33dca80ce6891b1 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 7 Aug 2023 23:07:16 -0400 Subject: [PATCH 19/50] cargo fmt --- src/applicationlayer.rs | 2 +- src/handshake_cache.rs | 2 +- src/lib.rs | 16 +-- src/log_event.rs | 2 +- src/proto.rs | 1 - src/zeta.rs | 125 +++++++++++++++++----- src/zssp.rs | 227 ++++++---------------------------------- 7 files changed, 142 insertions(+), 233 deletions(-) diff --git a/src/applicationlayer.rs b/src/applicationlayer.rs index 4d7fcfc..f078770 100644 --- a/src/applicationlayer.rs +++ b/src/applicationlayer.rs @@ -14,7 +14,7 @@ use crate::crypto::rand_core::{CryptoRng, RngCore}; use crate::crypto::sha512::{HashSha512, HmacSha512}; use crate::proto::RATCHET_SIZE; use crate::zeta::Session; -use crate::RatchetState; +use crate::ratchet_state::RatchetState; //use crate::{log_event::LogEvent, Session}; /// A container for a vast majority of the dynamic settings within ZSSP, including all time-based settings. diff --git a/src/handshake_cache.rs b/src/handshake_cache.rs index 8ce3852..1344412 100644 --- a/src/handshake_cache.rs +++ b/src/handshake_cache.rs @@ -11,7 +11,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, RwLock}; use crate::zeta::StateB2; -use crate::{proto::MAX_UNASSOCIATED_HANDSHAKE_STATES, ApplicationLayer}; +use crate::{proto::MAX_UNASSOCIATED_HANDSHAKE_STATES, applicationlayer::ApplicationLayer}; pub(crate) struct UnassociatedHandshakeCache { has_pending: AtomicBool, // Allowed to be falsely positive diff --git a/src/lib.rs b/src/lib.rs index 44a6e59..358dc61 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,24 +8,24 @@ pub mod crypto; mod antireplay; -mod applicationlayer; +pub mod applicationlayer; mod challenge; mod frag_cache; mod fragged; mod handshake_cache; mod indexed_heap; -mod log_event; -mod proto; -mod ratchet_state; +pub mod log_event; +pub mod proto; +pub mod ratchet_state; pub mod result; mod symmetric_state; -mod zeta; -mod zssp; +pub mod zeta; +pub mod zssp; //mod context; //pub mod error; -pub use crate::applicationlayer::ApplicationLayer; +//pub use crate::applicationlayer::ApplicationLayer; //pub use crate::log_event::LogEvent; //pub use crate::proto::{MAX_IDENTITY_BLOB_SIZE, MIN_PACKET_SIZE, MIN_TRANSPORT_MTU, RATCHET_SIZE}; -pub use crate::ratchet_state::RatchetState; +//pub use crate::ratchet_state::RatchetState; //pub use crate::zssp::{Context, ContextInner, IncomingSessionAction, ReceiveResult, Session, SessionEvent}; diff --git a/src/log_event.rs b/src/log_event.rs index 8690bf9..43fdc32 100644 --- a/src/log_event.rs +++ b/src/log_event.rs @@ -7,7 +7,7 @@ */ use std::sync::Arc; -use crate::{zeta::Session, ApplicationLayer}; +use crate::{zeta::Session, applicationlayer::ApplicationLayer}; /// ZSSP events that might be interesting to log or aggregate into metrics. pub enum LogEvent<'a, Application: ApplicationLayer> { diff --git a/src/proto.rs b/src/proto.rs index d7d5eaf..91ea301 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -92,7 +92,6 @@ 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; /// 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 diff --git a/src/zeta.rs b/src/zeta.rs index f0b0c4a..79e7eba 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -6,14 +6,14 @@ use std::io::Write; use std::num::NonZeroU32; use std::ops::{Deref, DerefMut}; use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering}; -use std::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockWriteGuard, Weak}; +use std::sync::{Arc, Mutex, RwLock, Weak}; use zeroize::Zeroizing; use crate::antireplay::Window; use crate::applicationlayer::ApplicationLayer; use crate::applicationlayer::RatchetUpdate; use crate::challenge::{gen_null_response, respond_to_challenge_in_place}; -use crate::zssp::{log, ContextInner}; +use crate::zssp::{log, ContextInner, SessionQueue}; //use crate::context::{log, ContextInner, SessionMap}; use crate::crypto::aes::*; use crate::crypto::kyber1024::{ @@ -25,7 +25,7 @@ use crate::fragged::Fragged; use crate::indexed_heap::BinaryHeapIndex; use crate::proto::*; use crate::ratchet_state::RatchetState; -use crate::result::{byzantine_fault, FaultType, OpenError, ReceiveError, ReceiveOk, SendError}; +use crate::result::{byzantine_fault, FaultType, OpenError, ReceiveError, SendError}; use crate::symmetric_state::SymmetricState; #[cfg(feature = "logging")] use crate::LogEvent::*; @@ -71,8 +71,12 @@ fn get_counter(session: &Session, state: &MutableSta None } else { let c = session.send_counter.fetch_add(1, Ordering::Relaxed); + if c > state.key_creation_counter + EXPIRE_AFTER_USES { + session.session_has_expired.store(true, Ordering::SeqCst); + return None; + } if c > THREAD_SAFE_COUNTER_HARD_EXPIRE { - session.session_has_expired.store(true, Ordering::SeqCst) + session.session_has_expired.store(true, Ordering::SeqCst); } Some((c, c > state.key_creation_counter + App::SETTINGS.rekey_after_key_uses)) } @@ -1109,8 +1113,6 @@ 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( - app: &App, - ctx: &Arc>, session: &Arc>, kid: NonZeroU32, n: &[u8; AES_GCM_IV_SIZE], @@ -1137,8 +1139,10 @@ pub(crate) fn received_d_trans( if !session.window.update(c) { return Err(byzantine_fault!(ExpiredCounter, true)); } + drop(state); - session.expire_inner(kex_lock, session.state.write().unwrap()); + drop(kex_lock); + session.expire(); Ok(()) } /// Corresponds to the timer rules of the Zeta State Machine found in Section 4.1 - Definition 3. @@ -1152,7 +1156,7 @@ pub(crate) fn process_timers( send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Option { let kex_lock = session.state_machine_lock.lock().unwrap(); - let mut state = session.state.read().unwrap(); + let state = session.state.read().unwrap(); if force_timeout || state.timeout_timer <= current_time { // Corresponds to the timeout timer Transition Algorithm described in Section 4.1 - Definition 3. match &state.beta { @@ -1221,8 +1225,8 @@ pub(crate) fn process_timers( // ... // -> psk, e, es, ss let mut noise = SymmetricState::initialize(PROTOCOL_NAME_NOISE_KK); - let mut hash = &mut App::Hash::new(); - let mut hmac = &mut App::HmacHash::new(); + let hash = &mut App::Hash::new(); + let hmac = &mut App::HmacHash::new(); let mut k1 = ArrayVec::::new(); k1.extend([0u8; HEADER_SIZE]); // Noise process prologue. @@ -1650,10 +1654,77 @@ pub(crate) fn received_k2_trans( } result } +/// Corresponds to Algorithm 9 found in Section 4.3. +pub(crate) fn send_payload( + ctx: &Arc>, + session: &Arc>, + payload: &[u8], + mut send: impl FnMut(&[u8]) -> bool, + mtu_sized_buffer: &mut [u8], +) -> Result<(), SendError> { + use SendError::*; + let mtu = mtu_sized_buffer.len(); + if mtu < MIN_TRANSPORT_MTU { + return Err(InvalidParameter); + } + + let state = session.state.read().unwrap(); + if matches!(&state.beta, ZetaAutomata::Null) { + return Err(SessionExpired); + } + if !matches!( + &state.beta, + ZetaAutomata::S1 | ZetaAutomata::S2 | ZetaAutomata::R1 { .. } | ZetaAutomata::R2 { .. } + ) { + return Err(SessionNotEstablished); + } + let (c, should_rekey) = get_counter(session, &state).ok_or(SessionExpired)?; + let nonce = to_nonce(PACKET_TYPE_DATA, c); + + let key = state.key_ref(false); + let mut cipher = key.nk.as_ref().unwrap().start_enc(&nonce); + + let payload_mtu = mtu - HEADER_SIZE; + debug_assert!(payload_mtu >= 4); + let fragment_count = payload.len().saturating_add(payload_mtu - 1) / payload_mtu; // Ceiling div. + let fragment_base_size = payload.len() / fragment_count; + let fragment_size_remainder = payload.len() % fragment_count; + + mtu_sized_buffer[..KID_SIZE].copy_from_slice(&key.send.kid.unwrap().get().to_be_bytes()); + mtu_sized_buffer[FRAGMENT_COUNT_IDX] = fragment_count as u8; + mtu_sized_buffer[PACKET_NONCE_START..].copy_from_slice(&nonce[NONCE_SIZE_DIFF..]); + + let mut i = 0; + for fragment_no in 0..fragment_count { + let fragment_len = fragment_base_size + (fragment_no < fragment_size_remainder) as usize; + let j = i + fragment_len; + + mtu_sized_buffer[FRAGMENT_NO_IDX] = fragment_no as u8; + cipher.encrypt(&payload[i..j], &mut mtu_sized_buffer[HEADER_SIZE..HEADER_SIZE + fragment_len]); + + session.hk_send.encrypt_in_place((&mut mtu_sized_buffer[HEADER_AUTH_START..HEADER_AUTH_END]).try_into().unwrap()); + + if !send(&mtu_sized_buffer[..HEADER_SIZE + fragment_len]) { + return Ok(()); + } + i = j; + } + drop(cipher); + drop(state); + + if should_rekey { + let mut state = session.state.write().unwrap(); + state.timeout_timer = i64::MIN; + drop(state); + ctx.session_queue + .lock() + .unwrap() + .change_priority(session.queue_idx, Reverse(i64::MIN)); + } + Ok(()) +} /// Corresponds to Algorithm 10 found in Section 4.3. pub(crate) fn receive_payload_in_place( - app: &App, - ctx: &Arc>, session: &Arc>, kid: NonZeroU32, n: &[u8; AES_GCM_IV_SIZE], @@ -1662,7 +1733,6 @@ pub(crate) fn receive_payload_in_place( ) -> Result<(), ReceiveError> { use FaultType::*; - let kex_lock = session.state_machine_lock.lock().unwrap(); let state = session.state.read().unwrap(); let is_other = if Some(kid) == state.key_ref(true).recv.kid { true @@ -1722,13 +1792,20 @@ 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(&self) { - self.expire_inner(self.state_machine_lock.lock().unwrap(), self.state.write().unwrap()); + if let Some(ctx) = self.ctx.upgrade() { + self.expire_inner(Some(&ctx), Some(&mut ctx.session_queue.lock().unwrap())); + } else { + self.expire_inner(None, None); + } } + /// Allows us to expire sessions with the correct locking order, preventing deadlock. pub(crate) fn expire_inner( &self, - kex_lock: MutexGuard<'_, ()>, - mut state: RwLockWriteGuard<'_, MutableState>, + ctx: Option<&Arc>>, + session_queue: Option<&mut SessionQueue>, ) { + let _kex_lock = self.state_machine_lock.lock().unwrap(); + let mut state = self.state.write().unwrap(); let mut kids_to_remove = None; if !matches!(&state.beta, ZetaAutomata::Null) { self.session_has_expired.store(true, Ordering::Relaxed); @@ -1738,15 +1815,13 @@ impl Session { state.timeout_timer = i64::MAX; state.beta = ZetaAutomata::Null; } - drop(state); - drop(kex_lock); - if let Some(kids_to_remove) = kids_to_remove { - if let Some(ctx) = self.ctx.upgrade() { - ctx.session_queue.lock().unwrap().remove(self.queue_idx); - let mut session_map = ctx.session_map.write().unwrap(); - for kid_recv in kids_to_remove.iter().flatten() { - session_map.remove(kid_recv); - } + if let Some(session_queue) = session_queue { + session_queue.remove(self.queue_idx); + } + if let (Some(ctx), Some(kids_to_remove)) = (ctx, kids_to_remove) { + let mut session_map = ctx.session_map.write().unwrap(); + for kid_recv in kids_to_remove.iter().flatten() { + session_map.remove(kid_recv); } } } diff --git a/src/zssp.rs b/src/zssp.rs index fac5118..b0ba9f8 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -36,7 +36,7 @@ use crate::log_event::LogEvent; use crate::proto::*; use crate::result::{byzantine_fault, FaultType, OpenError, ReceiveError, ReceiveOk, SendError, SessionEvent}; use crate::symmetric_state::SymmetricState; -use crate::{applicationlayer::*, RatchetState}; +use crate::{applicationlayer::*, ratchet_state::RatchetState}; /// Macro to turn off logging at compile time. macro_rules! log { @@ -315,7 +315,7 @@ impl Context { } else { std::slice::from_mut(&mut incoming_fragment_buf) }; - receive_payload_in_place(app, ctx, &session, kid_recv, &nonce, fragments, output_buffer)?; + receive_payload_in_place(&session, kid_recv, &nonce, fragments, output_buffer)?; SessionEvent::Data } else { let mut buffer = ArrayVec::::new(); @@ -395,7 +395,7 @@ impl Context { } PACKET_TYPE_SESSION_REJECTED => { log!(app, ReceivedRawD); - received_d_trans(app, ctx, &session, kid_recv, &nonce, assembled_packet)?; + received_d_trans(&session, kid_recv, &nonce, assembled_packet)?; log!(app, DIsAuthClosedSession(&session)); SessionEvent::Rejected } @@ -569,7 +569,6 @@ impl Context { } } } - /* /// Send data over the session. /// /// * `session` - The session to send to @@ -581,70 +580,11 @@ impl Context { pub fn send( &self, session: &Arc>, - mut send: impl FnMut(&mut [u8]) -> bool, + send: impl FnMut(&[u8]) -> bool, mtu_sized_buffer: &mut [u8], - mut data: &[u8], - current_time: i64, + data: &[u8], ) -> Result<(), SendError> { - if mtu_sized_buffer.len() < MIN_TRANSPORT_MTU { - return Err(SendError::InvalidParameter); - } - let state = session.state.read().unwrap(); - let key = state.cipher_states[state.current_key].as_ref().ok_or(SendError::SessionNotEstablished)?; - let counter = session.get_next_outgoing_counter()?; - - let mut c = key.get_send_cipher(counter)?; - c.set_iv(&create_message_nonce(PACKET_TYPE_DATA, counter)); - - let fragment_max_chunk_size = mtu_sized_buffer.len() - HEADER_SIZE; - let fragment_count = (data.len() + AES_GCM_TAG_SIZE + (fragment_max_chunk_size - 1)) / fragment_max_chunk_size; - if fragment_count > MAX_FRAGMENTS { - return Err(SendError::DataTooLarge); - } - let last_fragment_no = fragment_count - 1; - - for fragment_no in 0..fragment_count { - let chunk_size = fragment_max_chunk_size.min(data.len()); - let mut fragment_size = chunk_size + HEADER_SIZE; - - set_packet_header( - mtu_sized_buffer, - fragment_count as u8, - fragment_no as u8, - PACKET_TYPE_DATA, - key.remote_key_id.get(), - counter, - ); - - c.encrypt(&data[..chunk_size], &mut mtu_sized_buffer[HEADER_SIZE..fragment_size]); - data = &data[chunk_size..]; - - if fragment_no == last_fragment_no { - debug_assert!(data.is_empty()); - let tagged_fragment_size = fragment_size + AES_GCM_TAG_SIZE; - c.finish_encrypt((&mut mtu_sized_buffer[fragment_size..tagged_fragment_size]).try_into().unwrap()); - fragment_size = tagged_fragment_size; - } - - session.header_send_cipher.encrypt_in_place( - (&mut mtu_sized_buffer[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]) - .try_into() - .unwrap(), - ); - if !send(&mut mtu_sized_buffer[..fragment_size]) { - break; - } - } - drop(c); - if counter >= key.rekey_at_counter { - if let OfferStateMachine::Normal { .. } = &state.outgoing_offer { - drop(state); - if let Ok(timer) = initiate_rekey(&self.0, session, send, current_time) { - self.0.session_queue.lock().unwrap().change_priority(session.queue_idx, Reverse(timer)); - } - } - } - Ok(()) + send_payload(&self.0, session, data, send, mtu_sized_buffer) } /// Perform periodic background service and cleanup tasks. @@ -660,20 +600,19 @@ impl Context { /// should rekey. pub fn service bool>( &self, - app: &App, + app: App, mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, current_time: i64, ) -> i64 { - let retry_next = current_time.saturating_add(App::RETRY_INTERVAL_MS); - let mut next_service_time = 2 * App::RETRY_INTERVAL_MS; - - let mut session_queue = self.0.session_queue.lock().unwrap(); + let ctx = &self.0; + let mut session_queue = ctx.session_queue.lock().unwrap(); + let mut next_service_time = current_time + App::SETTINGS.fragment_assembly_timeout as i64; // This update system takes heavy advantage of the fact that sessions only need to be updated // either roughly every second or roughly every hour. That big gap allows for minor optimizations. // If the gap changes (unlikely) this code may need to be rewritten. - while let Some((session, timer, queue_idx)) = session_queue.peek() { - if timer.0 >= current_time { - next_service_time = next_service_time.min(timer.0 - current_time); + while let Some((session, Reverse(timer), queue_idx)) = session_queue.peek() { + if *timer >= current_time { + next_service_time = next_service_time.min(*timer); break; } let session = match session.upgrade() { @@ -683,127 +622,23 @@ impl Context { continue; } }; - let state = session.state.read().unwrap(); - let next_timer = match &state.outgoing_offer { - Normal { timeout, .. } => { - if *timeout <= current_time { - drop(state); - if let Some((send, _)) = send_to(&session) { - let result = initiate_rekey(&self.0, &session, send, current_time); - if result.is_ok() { - app.event_log(LogEvent::ServiceKKStart(&session), current_time); - } - result.unwrap_or(retry_next) - } else { - retry_next - } - } else { - *timeout - } + let result = process_timers(&app, ctx, &session, current_time, false, false, |packet, hk_send| { + if let Some((send_fragment, mut mtu)) = send_to(&session) { + mtu = mtu.max(MIN_TRANSPORT_MTU); + send_with_fragmentation( + send_fragment, + mtu, + packet, + hk_send + ); } - // If there's an outstanding attempt to open a session, retransmit this - // periodically in case the initial packet doesn't make it. - NoiseXKPattern1or3(handshake_state) => { - if let Some(ts) = process_timer(&handshake_state.next_retry_time, App::RETRY_INTERVAL_MS, current_time) { - ts - } else { - // We have to eventually time out NoiseXKPattern3 because of unreliable network conditions. - if handshake_state.timeout <= current_time { - drop(state); - let _kex_lock = session.state_machine_lock.lock().unwrap(); - let mut state = session.state.write().unwrap(); - let ratchet_state = state.ratchet_states.clone(); - // Since we dropped the lock we must re-check if we are in the correct state. - if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { - if handshake_state.timeout <= current_time { - app.event_log(LogEvent::ServiceXKTimeout(&session), current_time); - handshake_state.reinitialize( - &session, - &ratchet_state, - &mut self.0.session_map.write().unwrap(), - &mut self.0.rng.lock().unwrap(), - current_time, - ); - } - } - } else if let Some((mut send, mut mtu)) = send_to(&session) { - mtu = mtu.max(MIN_TRANSPORT_MTU); - match &handshake_state.offer { - NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_message, noise_message_len, message_id, .. } => { - app.event_log(LogEvent::ServiceXK1Resend(&session), current_time); - // We are in state NoiseXKPattern1 so resend noise_pattern1. - send_with_fragmentation( - &mut send, - mtu, - &mut noise_message.clone()[..*noise_message_len], - PACKET_TYPE_NOISE_XK_PATTERN_1, - None, - *message_id, - None::<&App::PrpEnc>, - ); - } - NoiseXKAliceHandshakeState::NoiseXKPattern3 { noise_message, noise_message_len, .. } => { - app.event_log(LogEvent::ServiceXK3Resend(&session), current_time); - send_with_fragmentation( - &mut send, - mtu, - &mut noise_message.clone()[..*noise_message_len], - PACKET_TYPE_NOISE_XK_PATTERN_3, - state.cipher_states[0].as_ref().map(|k| k.remote_key_id), - 0, - Some(&session.header_send_cipher), - ); - } - } - } - retry_next - } - } - NoiseKKPattern1 { next_retry_time, timeout, noise_message, .. } | NoiseKKPattern2 { next_retry_time, timeout, noise_message, .. } => { - if let Some(ts) = process_timer(next_retry_time, App::RETRY_INTERVAL_MS, current_time) { - ts - } else { - if *timeout <= current_time { - app.event_log(LogEvent::ServiceKKTimeout(&session), current_time); - next_retry_time.store(i64::MAX, Ordering::Relaxed); - drop(state); - session.expire_inner(&self.0, &mut session_queue); - } else { - let packet_type = if let NoiseKKPattern1 { .. } = &state.outgoing_offer { - app.event_log(LogEvent::ServiceKK1Resend(&session), current_time); - PACKET_TYPE_NOISE_KK_PATTERN_1 - } else { - app.event_log(LogEvent::ServiceKK2Resend(&session), current_time); - PACKET_TYPE_NOISE_KK_PATTERN_2 - }; - if let Some((send, _)) = send_to(&session) { - let _ = session.send_control(&state, send, packet_type, noise_message); - } - } - retry_next - } - } - KeyConfirm { next_retry_time, timeout, .. } => { - if let Some(ts) = process_timer(next_retry_time, App::RETRY_INTERVAL_MS, current_time) { - ts - } else { - if *timeout <= current_time { - app.event_log(LogEvent::ServiceKeyConfirmTimeout(&session), current_time); - next_retry_time.store(i64::MAX, Ordering::Relaxed); - drop(state); - session.expire_inner(&self.0, &mut session_queue); - } else { - app.event_log(LogEvent::ServiceKeyConfirmResend(&session), current_time); - if let Some((send, _)) = send_to(&session) { - let _ = session.send_control(&state, send, PACKET_TYPE_KEY_CONFIRM, &[]); - } - } - retry_next - } - } - Null => retry_next, - }; - session_queue.change_priority(queue_idx, Reverse(next_timer)); + }); + if let Some(next_timer) = result { + next_service_time = next_service_time.min(next_timer); + session_queue.change_priority(queue_idx, Reverse(next_timer)); + } else { + session.expire_inner(Some(ctx), Some(&mut session_queue)); + } } drop(session_queue); @@ -811,9 +646,9 @@ impl Context { .unassociated_defrag_cache .lock() .unwrap() - .check_for_expiry(App::INITIAL_OFFER_TIMEOUT_MS, current_time); + .check_for_expiry(App::SETTINGS.fragment_assembly_timeout as i64, current_time); self.0.unassociated_handshake_states.service(current_time); next_service_time - } */ + } } From df2c589d09f04213b819b39b8f8fd204720e36e4 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 8 Aug 2023 08:37:16 -0400 Subject: [PATCH 20/50] refactored --- src/{applicationlayer.rs => application.rs} | 73 +- src/challenge.rs | 5 +- src/context.rs | 436 --- src/crypto/mod.rs | 16 +- src/error.rs | 110 - src/frag_cache.rs | 2 +- src/fragged.rs | 2 +- src/handshake_cache.rs | 2 +- src/lib.rs | 2 +- src/log_event.rs | 2 +- src/proto.rs | 7 +- src/proto_old.rs | 296 -- src/ratchet_state.rs | 147 +- src/ratchet_state_old.rs | 62 - src/result.rs | 2 +- src/symmetric_state.rs | 6 +- src/symmetric_state_old.rs | 156 -- src/zeta.rs | 116 +- src/zssp copy.rs | 2704 ------------------- src/zssp.rs | 9 +- 20 files changed, 240 insertions(+), 3915 deletions(-) rename src/{applicationlayer.rs => application.rs} (82%) delete mode 100644 src/context.rs delete mode 100644 src/error.rs delete mode 100644 src/proto_old.rs delete mode 100644 src/ratchet_state_old.rs delete mode 100644 src/symmetric_state_old.rs delete mode 100644 src/zssp copy.rs diff --git a/src/applicationlayer.rs b/src/application.rs similarity index 82% rename from src/applicationlayer.rs rename to src/application.rs index f078770..0a28af9 100644 --- a/src/applicationlayer.rs +++ b/src/application.rs @@ -1,21 +1,12 @@ use std::sync::Arc; +use rand_core::{CryptoRng, RngCore}; -/* 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 crate::crypto::aes::{AesDec, AesEnc, HighThroughputAesGcmPool, LowThroughputAesGcm}; -use crate::crypto::kyber1024::Kyber1024PrivateKey; -use crate::crypto::p384::{P384KeyPair, P384PublicKey}; -use crate::crypto::rand_core::{CryptoRng, RngCore}; -use crate::crypto::sha512::{HashSha512, HmacSha512}; -use crate::proto::RATCHET_SIZE; +use crate::crypto::*; use crate::zeta::Session; use crate::ratchet_state::RatchetState; -//use crate::{log_event::LogEvent, Session}; + +pub use crate::proto::RATCHET_SIZE; +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 @@ -194,22 +185,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], @@ -232,17 +218,10 @@ pub trait ApplicationLayer: Sized { &self, 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`. + ) -> 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. /// /// 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 @@ -254,8 +233,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, @@ -271,16 +249,21 @@ pub trait ApplicationLayer: Sized { fn event_log(&self, event: LogEvent<'_, Self>); } -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>, -} - +/// 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, } diff --git a/src/challenge.rs b/src/challenge.rs index 11e4578..c63c916 100644 --- a/src/challenge.rs +++ b/src/challenge.rs @@ -4,10 +4,7 @@ use std::sync::atomic::{AtomicU64, Ordering}; use rand_core::{CryptoRng, RngCore}; use crate::antireplay::Window; -use crate::crypto::{ - secure_eq, - sha512::{HashSha512, SHA512_HASH_SIZE}, -}; +use crate::crypto::*; use crate::proto::*; pub struct ChallengeContext { diff --git a/src/context.rs b/src/context.rs deleted file mode 100644 index 2320365..0000000 --- a/src/context.rs +++ /dev/null @@ -1,436 +0,0 @@ -use rand_core::RngCore; -use std::cmp::Reverse; -use std::collections::hash_map::Entry; -use std::collections::HashMap; -use std::hash::Hash; -use std::num::NonZeroU32; -use std::sync::{Arc, Mutex, Weak, RwLock}; - -use crate::applicationlayer::ApplicationLayer; -use crate::crypto::aes::{AES_256_KEY_SIZE, AES_GCM_IV_SIZE}; -use crate::frag_cache::UnassociatedFragCache; -use crate::handshake_cache::UnassociatedHandshakeCache; -use crate::indexed_heap::IndexedBinaryHeap; -//use crate::fragmentation::{send_with_fragmentation, DefragBuffer}; -use crate::proto::*; -use crate::result::{byzantine_fault, 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 { - ($app:expr, $event:expr) => { - #[cfg(feature = "logging")] - $app.event_log($event); - }; -} -pub(crate) use log; - -/// Session context for local application. -/// -/// Each application using ZSSP must create an instance of this to own sessions and -/// 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 { - fn clone(&self) -> Self { - Self(self.0.clone()) - } -} - -pub(crate) type SessionMap = RwLock>>>; - -pub(crate) struct ContextInner { - pub(crate) rng: Mutex, - pub(crate) s_secret: App::KeyPair, - pub(crate) session_queue: Mutex>, Reverse>>, - pub(crate) session_map: SessionMap, - unassociated_defrag_cache: Mutex>, - unassociated_handshake_states: UnassociatedHandshakeCache, - //pub(crate) b2_map: Mutex>>, - - //hello_defrag: Mutex, - challenge: ChallengeContext, -} - -/// 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() -} - -impl Context { - /// Create a new session 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), - s_secret: static_secret_key, - session_map: RwLock::new(HashMap::new()), - challenge, - session_queue: Mutex::new(IndexedBinaryHeap::new()), - unassociated_defrag_cache: Mutex::new(UnassociatedFragCache::new()), - unassociated_handshake_states: UnassociatedHandshakeCache::new(), - })) - } - /// 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 - /// * `mtu` - MTU for initial packets - /// * `static_remote_key` - Remote side's static public NIST P-384 key - /// * `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 - pub fn open( - &self, - app: App, - send: impl FnMut(Vec) -> bool, - mut mtu: usize, - static_remote_key: App::PublicKey, - session_data: App::SessionData, - identity: Vec, - ) -> Result>, OpenError> { - mtu = mtu.max(MIN_TRANSPORT_MTU); - let ctx = &self.0; - - // Process zeta layer. - trans_to_a1( - app, - &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); - }, - ) - } - - /// Receive, authenticate, decrypt, and process a physical wire packet. - /// - /// * `app` - Interface to application using ZSSP - /// * `send_unassociated_reply` - Function to send reply packets directly when no session exists - /// * `send_unassociated_mtu` - MTU for unassociated replies - /// * `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>( - &self, - app: App, - send_unassociated_reply: impl FnMut(Vec) -> bool, - mut send_unassociated_mtu: usize, - send_to: impl FnOnce(&Arc>) -> Option<(SendFn, usize)>, - remote_address: &impl Hash, - raw_fragment: Vec, - ) -> Result, ReceiveError> { - use crate::result::FaultType::*; - send_unassociated_mtu = send_unassociated_mtu.max(MIN_TRANSPORT_MTU); - let ctx = &self.0; - - // 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()); - 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)); - } - })?; - 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 (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)?; - SessionEvent::Data(assembled_packet) - } - PACKET_TYPE_HANDSHAKE_RESPONSE => { - log!(app, ReceivedRawX2); - received_x2_trans( - &mut zeta, - &session, - &app, - &ctx, - kid_recv, - to_aes_nonce(&pn), - assembled_packet, - send_associated, - )?; - log!(app, X2IsAuthSentX3(&session)); - SessionEvent::Control - } - 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)?; - log!(app, KeyConfirmIsAuthSentAck(&session)); - if result { - SessionEvent::Established - } else { - SessionEvent::Control - } - } - PACKET_TYPE_ACK => { - log!(app, ReceivedRawAck); - received_c2_trans(&mut zeta, &app, &ctx.rng, kid_recv, to_aes_nonce(&pn), assembled_packet)?; - log!(app, AckIsAuth(&session)); - SessionEvent::Control - } - PACKET_TYPE_REKEY_INIT => { - log!(app, ReceivedRawK1); - received_k1_trans( - &mut zeta, - &session, - &app, - &ctx.rng, - &ctx.session_map, - &ctx.s_secret, - kid_recv, - to_aes_nonce(&pn), - assembled_packet, - send_associated, - )?; - log!(app, K1IsAuthSentK2(&session)); - SessionEvent::Control - } - PACKET_TYPE_REKEY_COMPLETE => { - log!(app, ReceivedRawK2); - received_k2_trans(&mut zeta, &app, kid_recv, to_aes_nonce(&pn), assembled_packet, send_associated)?; - log!(app, K2IsAuthSentKeyConfirm(&session)); - SessionEvent::Control - } - PACKET_TYPE_SESSION_REJECTED => { - log!(app, ReceivedRawD); - received_d_trans(&mut zeta, kid_recv, to_aes_nonce(&pn), assembled_packet)?; - log!(app, DIsAuthClosedSession(&session)); - SessionEvent::Rejected - } - _ => return Err(byzantine_fault!(InvalidPacket, true)), // This is unreachable. - }; - drop(zeta); - Ok(ReceiveOk::Session(session, ret)) - } else { - Ok(ReceiveOk::Unassociated) - } - } else { - let mut b2_map = ctx.b2_map.lock().unwrap(); - 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)) - } - })?; - 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, - ); - })?; - log!(app, X3IsAuthSentKeyConfirm(&session)); - Ok(ReceiveOk::Session(session, SessionEvent::NewSession)) - } else { - Ok(ReceiveOk::Unassociated) - } - } else { - // When sessions are added or dropped or packets arrive extremely delayed it is - // possible to receive no longer recognized key ids. - Err(byzantine_fault!(UnknownLocalKeyId, false)) - } - } - } else { - // Process recv fragmentation layer. - 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 { - Ok(()) - } 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()); - if let Err(challenge) = result { - log!(app, X1FailedChallengeSentNewChallenge); - 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()); - send_with_fragmentation::( - send_unassociated_reply, - send_unassociated_mtu, - 0, - to_packet_nonce(&nonce), - &challenge_packet, - None, - ); - // If we issue a challenge the first hello packet will always fail. - return Err(byzantine_fault!(FailedAuth, false)); - } else if let Ok(true) = result { - log!(app, X1SucceededChallenge); - } - 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), - ); - })?; - log!(app, X1IsAuthSentX2); - Ok(ReceiveOk::Unassociated) - } else if p == PACKET_TYPE_CHALLENGE { - log!(app, ReceivedRawChallenge); - // Process recv challenge layer. - 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()) { - let mut zeta = session.0.lock().unwrap(); - respond_to_challenge(&mut zeta, &ctx.rng, &assembled_packet[KID_SIZE..].try_into().unwrap()); - log!(app, ChallengeIsAuth(&session)); - return Ok(ReceiveOk::Unassociated); - } - } - Err(byzantine_fault!(UnknownLocalKeyId, true)) - } else { - Err(byzantine_fault!(InvalidPacket, true)) - } - } else { - Ok(ReceiveOk::Unassociated) - } - } - } - - /// Send data over the session. - /// - /// * `session` - The session to send to - /// * `send` - Function to call to send physical packet(s); the buffer passed to `send` is a - /// 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> { - mtu = mtu.max(MIN_TRANSPORT_MTU); - let mut zeta = session.0.lock().unwrap(); - send_payload(&mut zeta, payload, |Packet(kid, nonce, payload), hk| { - send_with_fragmentation::(send, mtu, *kid, to_packet_nonce(nonce), &payload, hk); - }) - } - - /// Perform periodic background service and cleanup tasks. - /// - /// 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. - /// - /// * `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 { - let ctx = &self.0; - let sessions = ctx.sessions.lock().unwrap(); - 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(); - service( - &mut zeta, - &session, - ctx, - &app, - current_time, - |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); - } - }, - ); - next_timer = next_timer.min(zeta.next_timer()); - zeta.defrag.service::(current_time); - } - } - ctx.hello_defrag.lock().unwrap().service::(current_time); - (App::SETTINGS.resend_time as i64).min(next_timer - current_time) - } -} diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index f31b7f6..00ca2aa 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -1,13 +1,17 @@ -// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. +mod aes; +pub use self::aes::*; -pub mod aes; -pub mod kyber1024; -pub mod p384; -pub mod sha512; +mod p384; +pub use self::p384::*; + +mod sha512; +pub use sha512::*; + +mod kyber1024; +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 pqc_kyber; pub use rand_core; /// Constant time byte slice equality. diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index 63c31c0..0000000 --- a/src/error.rs +++ /dev/null @@ -1,110 +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/ - */ - -#[derive(Debug, PartialEq, Eq)] -pub enum OpenError { - /// An invalid parameter was supplied to the function. - InvalidPublicKey, - - /// Local identity blob is too large to send, even with fragmentation. - DataTooLarge, - - RatchetIoError(IoError), -} - -#[derive(Debug, PartialEq, Eq)] -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. - /// - /// The associated session will no longer send or receive data and must be immediately dropped. - SessionExpired, - - /// Attempt to send using a session without a shared symmetric key. - /// The caller should wait until the handshake has completed. - SessionNotEstablished, - - /// Data object is too large to send, even with fragmentation. - DataTooLarge, -} - -/// A type of fault occurred because we received a bad packet. -/// -/// An unauthenticated attacker can intentionally trigger any of these, so it is best to -/// treat these as raw user input that needs to be sanitize. -#[derive(Debug, PartialEq, Eq)] -pub enum FaultType { - /// The received packet was addressed to an unrecognized local session. - UnknownLocalKeyId, - - /// The received packet from the remote peer was not well formed. - InvalidPacket, - - /// Packet failed one or more authentication (MAC) checks. - FailedAuthentication, - - /// Packet counter was repeated or outside window of allowed counter values. - ExpiredCounter, - - /// Packet contained protocol control parameters that are disallowed at this point in - /// time by ZSSP. - OutOfSequence, -} - -#[derive(Debug, PartialEq, Eq)] -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. - /// - /// Because an unauthenticated remote peer can force these to occur with specific - /// contained information, it is recommended in production to either drop these - /// immediately, or log them safely to a local output stream and then drop them. - ByzantineFault { - /// The type of fault that has occurred. Be cautious if you choose to read this - /// value, as an attacker has control over it. - error: FaultType, - /// Some byzantine faults within ZSSP are naturally occurring, i.e. they can occur - /// between two well behaved and trusted parties executing the protocol. - /// This boolean is true if this is one of these faults. If you go to the file and - /// line number specified by this error you will find a comment describing - /// how and why exactly this fault can occur naturally. - /// - /// Faults that can occur because the underlying communication medium is lossy and - /// sequentially inconsistent (as in UDP) are considered naturally occurring. - /// However ZSSP considers faults that occur because data integrity has not been - /// persevered (i.e. bits have been flipped) to be unnatural. - /// ZSSP also considers collisions of what are supposed to be uniform random - /// numbers to be unnatural. - is_naturally_occurring: bool, - /// The file of this implementation of ZSSP from which this error was generated. - file: &'static str, - /// The line number of this implementation of ZSSP from which this error was - /// generated. As such this number uniquely identifies each possible fault that - /// can occur during ZSSP. Advanced user can use this information to debug more - /// complicated usages of ZSSP. - line: u32, - }, - - /// The caller supplied data buffer is too small to receive data from the remote peer. - /// An attacker can cause this to occur, so users should place a hard upper limit on - /// how large their supplied data buffers can be. - DataBufferTooSmall, - - /// Rekeying failed and session secret has reached its hard usage count limit. - /// The associated session will no longer function and has to be dropped. - MaxKeyLifetimeExceeded, - - /// One of the ratchet saving or lookup functions returned an error, so the packet had to be - /// dropped. - RatchetIoError(IoError), -} diff --git a/src/frag_cache.rs b/src/frag_cache.rs index e132f4c..cbe95df 100644 --- a/src/frag_cache.rs +++ b/src/frag_cache.rs @@ -10,7 +10,7 @@ use std::collections::hash_map::RandomState; use std::hash::{BuildHasher, Hash, Hasher}; use std::mem::MaybeUninit; -use crate::crypto::aes::AES_GCM_IV_SIZE; +use crate::crypto::AES_GCM_IV_SIZE; use crate::fragged::Assembled; use crate::proto::{MAX_FRAGMENTS, MAX_UNASSOCIATED_FRAGMENTS, MAX_UNASSOCIATED_PACKETS, MAX_UNASSOCIATED_PACKET_SIZE}; diff --git a/src/fragged.rs b/src/fragged.rs index 5cb1a49..38a0e18 100644 --- a/src/fragged.rs +++ b/src/fragged.rs @@ -9,7 +9,7 @@ use arrayvec::ArrayVec; use std::mem::{needs_drop, zeroed, MaybeUninit}; -use crate::crypto::aes::AES_GCM_IV_SIZE; +use crate::crypto::AES_GCM_IV_SIZE; use crate::proto::{MAX_FRAGMENTS, NONCE_SIZE_DIFF}; pub type Assembled = ArrayVec; diff --git a/src/handshake_cache.rs b/src/handshake_cache.rs index 1344412..f5cd728 100644 --- a/src/handshake_cache.rs +++ b/src/handshake_cache.rs @@ -11,7 +11,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, RwLock}; use crate::zeta::StateB2; -use crate::{proto::MAX_UNASSOCIATED_HANDSHAKE_STATES, applicationlayer::ApplicationLayer}; +use crate::{proto::MAX_UNASSOCIATED_HANDSHAKE_STATES, application::ApplicationLayer}; pub(crate) struct UnassociatedHandshakeCache { has_pending: AtomicBool, // Allowed to be falsely positive diff --git a/src/lib.rs b/src/lib.rs index 358dc61..bd202c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,7 @@ pub mod crypto; mod antireplay; -pub mod applicationlayer; +pub mod application; mod challenge; mod frag_cache; mod fragged; diff --git a/src/log_event.rs b/src/log_event.rs index 43fdc32..a160ad9 100644 --- a/src/log_event.rs +++ b/src/log_event.rs @@ -7,7 +7,7 @@ */ use std::sync::Arc; -use crate::{zeta::Session, applicationlayer::ApplicationLayer}; +use crate::{zeta::Session, application::ApplicationLayer}; /// ZSSP events that might be interesting to log or aggregate into metrics. pub enum LogEvent<'a, Application: ApplicationLayer> { diff --git a/src/proto.rs b/src/proto.rs index 91ea301..8c1d252 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -1,9 +1,4 @@ -use crate::crypto::{ - aes::{AES_GCM_IV_SIZE, AES_GCM_TAG_SIZE}, - kyber1024::{KYBER_CIPHERTEXT_SIZE, KYBER_PUBLIC_KEY_SIZE}, - p384::P384_PUBLIC_KEY_SIZE, - sha512::SHA512_HASH_SIZE, -}; +use crate::crypto::*; /* Common constants */ diff --git a/src/proto_old.rs b/src/proto_old.rs deleted file mode 100644 index 2f83f3e..0000000 --- a/src/proto_old.rs +++ /dev/null @@ -1,296 +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::hash::Hasher; -use std::mem::size_of; - -use crate::crypto::aes::AES_GCM_TAG_SIZE; -use crate::crypto::p384::P384_PUBLIC_KEY_SIZE; -use crate::crypto::pqc_kyber::{KYBER_CIPHERTEXTBYTES, KYBER_PUBLICKEYBYTES}; -use crate::crypto::sha512::{HashSha512, SHA512_HASH_SIZE}; -use hex_literal::hex; - -/// 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. -pub const MIN_TRANSPORT_MTU: usize = 128; - -pub const RATCHET_SIZE: usize = 32; - -/// 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 = NoiseXKPattern3::MAX_SIZE - NoiseXKPattern3::MIN_SIZE; - -/// Initial value of 'h'. -/// echo -n 'Noise_XKhfs+psk2_P384+Kyber1024_AESGCM_SHA512' | shasum -a 512 -pub(crate) const INITIAL_H: [u8; SHA512_HASH_SIZE] = - hex!("cd1f422196a5a614e24392cf34dcbf340ee61ad6ee6834274ff35fd42a7a5c44d04a045101555548a291778dd036b93ae21005a26c003213f57a5df9fb17f745"); -/// Initial value of 'ck' for rekeying. -/// echo -n 'Noise_KKpsk0_P384_AESGCM_SHA512' | shasum -a 512 -pub(crate) const INITIAL_H_REKEY: [u8; SHA512_HASH_SIZE] = - hex!("daeedd651ac9c5173f2eaaff996beebac6f3f1bfe9a70bb1cc54fa1fb2bf46260d71a3c4fb4d4ee36f654c31773a8a15e5d5be974a0668dc7db70f4e13ed172e"); - -pub(crate) const SESSION_ID_SIZE: usize = 4; - -pub(crate) const PACKET_TYPE_NOISE_XK_PATTERN_1: u8 = 0; -pub(crate) const PACKET_TYPE_NOISE_XK_PATTERN_2: u8 = 1; -pub(crate) const PACKET_TYPE_NOISE_XK_PATTERN_3: u8 = 2; -pub(crate) const PACKET_TYPE_KEY_CONFIRM: u8 = 3; -pub(crate) const PACKET_TYPE_ACK: u8 = 4; -pub(crate) const PACKET_TYPE_NOISE_KK_PATTERN_1: u8 = 5; -pub(crate) const PACKET_TYPE_NOISE_KK_PATTERN_2: u8 = 6; -pub(crate) const PACKET_TYPE_SESSION_REJECTED: u8 = 7; -pub(crate) const PACKET_TYPE_DATA: u8 = 8; -pub(crate) const PACKET_TYPE_BOB_DOS_CHALLENGE: u8 = 9; -pub(crate) const PACKET_TYPE_RANGE_TRANSPORT: std::ops::Range = 3..9; - -/// Noise asks that the counter be initialized to 0 but for out of order reasons we have -/// to start it at 1. -/// Since with unreliable transport the first counter could always end up dropped this is -/// functionally equivalent to initializing to 0. -pub(crate) const INIT_COUNTER: u64 = 0; -pub(crate) const LABEL_RATCHET_STATE: u8 = b'R'; -pub(crate) const LABEL_HEADER_KEY: u8 = b'H'; -pub(crate) const LABEL_KEX_KEY: u8 = b'K'; - -/// Size of keys used during derivation, mixing, etc. -pub(crate) const HASHLEN: usize = SHA512_HASH_SIZE; - -pub(crate) const HEADER_SIZE: usize = 16; -pub(crate) const HEADER_PROTECT_ENC_START: usize = 4; -pub(crate) const HEADER_PROTECT_ENC_END: usize = 20; -pub(crate) const CHALLENGE_COUNTER_SIZE: usize = 8; -pub(crate) const CHALLENGE_MAC_SIZE: usize = 16; -pub(crate) const CHALLENGE_POW_SIZE: usize = 8; -pub(crate) const CHALLENGE_SALT_SIZE: usize = 32; - -pub(crate) const MAX_NOISE_HANDSHAKE_SIZE: usize = MAX_FRAGMENTS * MIN_TRANSPORT_MTU; -pub(crate) const CONTROL_PACKET_MAX_SIZE: usize = HEADER_SIZE + NoiseKKPattern1or2::SIZE + AES_GCM_TAG_SIZE; -pub(crate) const CONTROL_PACKET_MIN_SIZE: usize = HEADER_SIZE + AES_GCM_TAG_SIZE; - -/// 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 = 16777216; -/// 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; -/// We hard-expire the Noise counter long before we reach u64::MAX because of the ABA problem. -/// Over (1<<16) threads would have to attempt to increment the counter at the same time -/// to overflow it. -/// Having (1<<16) threads active at the same time would crash basically any system. -pub(crate) const THREAD_SAFE_COUNTER_HARD_EXPIRE: u64 = u64::MAX - (1 << 16); - -/// 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; // hard protocol max: 63 -/// Maximum window over which session packets may be reordered to be defragmented and -/// reassembled. Out of order fragments may be dropped in favor of newer fragments. -/// Increasing this value makes a session consume more significantly more memory. -pub(crate) const SESSION_MAX_FRAGMENTS_OOO: usize = 32; - -/// The maximum number of unassociated packets that a receive context will cache. -/// Additional packets will either be dropped or cause a different packet to be dropped -/// from the cache. -/// Larger values consume more memory but provide better reliability and DDOS resistance. -pub(crate) const MAX_UNASSOCIATED_PACKETS: usize = 32; -/// The maximum number of fragments of unassociated packets that a receive context will -/// cache. -/// All unassociated fragments share the same buffer, when it fills up additional -/// fragments will be dropped or cause other fragments to be dropped from the cache. -/// Larger values consume more memory but provide better reliability and DDOS resistance. -pub(crate) const MAX_UNASSOCIATED_FRAGMENTS: usize = 32 * 32; -/// The maximum number of `NoiseXKBobHandshakeState` that a receive context will cache. -/// These are extremely large and since Alice has not been authenticated we put a hard -/// limit to how many we cache. -/// Larger values consume more memory but provide better reliability and DDOS resistance. -pub(crate) const MAX_UNASSOCIATED_HANDSHAKE_STATES: usize = 32; - -/// The maximum size a packet that is not associated to a session may be. -/// Excludes the size of headers for fragmentation. -pub(crate) const MAX_UNASSOCIATED_PACKET_SIZE: usize = NoiseXKPattern1::MAX_SIZE - HEADER_SIZE; - -/* -XKhfs+psk2: - <- s - ... - -> e, es, e1 - <- e, ee, ekem1, psk - -> s, se -*/ -/* -KKpsk0: - -> s - <- s - ... - -> psk, e, es, ss - <- e, ee, se -*/ -/* -Header: - [0..4] recipient key id --- start AES(ck_es * h_e_e1_p) encrypted block -- - [4] fragment count (1..255) - [5] fragment number (0..254) - [6] reserved zero --- start AES-GCM Nonce -- - [7] packet type - [8..16] 64-bit counter or packet id -*/ -/// The first packet in Noise_XK exchange containing Alice's ephemeral keys, key id, -/// and a random symmetric key to protect header fragmentation fields for this session. -#[repr(C, packed)] -pub(crate) struct NoiseXKPattern1 { - pub header: [u8; HEADER_SIZE], - /// -- start prologue -- - pub alice_key_id: [u8; SESSION_ID_SIZE], - /// -- end prologue -- - pub noise_e: [u8; P384_PUBLIC_KEY_SIZE], - /// -- start AES-GCM(k_es) encrypted section - pub noise_e1: [u8; KYBER_PUBLICKEYBYTES], - /// -- end encrypted section - pub e1_gcm_tag: [u8; AES_GCM_TAG_SIZE], - pub payload: [u8; RATCHET_SIZE + RATCHET_SIZE + AES_GCM_TAG_SIZE + ChallengeResponse::SIZE], -} - -#[repr(C, packed)] -pub(crate) struct ChallengeResponse { - pub challenge_counter: [u8; CHALLENGE_COUNTER_SIZE], - pub challenge_mac: [u8; CHALLENGE_MAC_SIZE], - pub challenge_pow: [u8; CHALLENGE_POW_SIZE], -} - -impl NoiseXKPattern1 { - pub const PROLOGUE_START: usize = HEADER_SIZE; - pub const PROLOGUE_END: usize = Self::PROLOGUE_START + SESSION_ID_SIZE; - pub const E1_ENC_START: usize = Self::PROLOGUE_END + P384_PUBLIC_KEY_SIZE; - pub const E1_AUTH_START: usize = Self::E1_ENC_START + KYBER_PUBLICKEYBYTES; - pub const P_ENC_START: usize = Self::E1_AUTH_START + AES_GCM_TAG_SIZE; - - pub const MIN_SIZE: usize = Self::P_ENC_START + AES_GCM_TAG_SIZE + ChallengeResponse::SIZE; - pub const MAX_SIZE: usize = Self::MIN_SIZE + RATCHET_SIZE + RATCHET_SIZE; -} -impl ChallengeResponse { - pub const SIZE: usize = CHALLENGE_COUNTER_SIZE + CHALLENGE_MAC_SIZE + CHALLENGE_POW_SIZE; -} - -#[repr(C, packed)] -pub(crate) struct BobDOSChallenge { - pub header: [u8; HEADER_SIZE], - pub alice_key_id: [u8; SESSION_ID_SIZE], - pub challenge_counter: [u8; CHALLENGE_COUNTER_SIZE], - pub challenge_mac: [u8; CHALLENGE_MAC_SIZE], - pub prior_challenge_pow: [u8; CHALLENGE_POW_SIZE], -} - -impl BobDOSChallenge { - pub const SIZE: usize = HEADER_SIZE + SESSION_ID_SIZE + CHALLENGE_COUNTER_SIZE + CHALLENGE_MAC_SIZE + CHALLENGE_POW_SIZE; -} - -/// The response to NoiseXKPattern1 containing Bob's ephemeral keys. -#[repr(C, packed)] -pub(crate) struct NoiseXKPattern2 { - pub header: [u8; HEADER_SIZE], - pub noise_e: [u8; P384_PUBLIC_KEY_SIZE], - /// -- start AES-GCM(k_es_ee) encrypted section - pub noise_ekem1: [u8; KYBER_CIPHERTEXTBYTES], - /// -- end encrypted section - pub ekem1_gcm_tag: [u8; AES_GCM_TAG_SIZE], - /// -- start AES-GCM(k_es_ee_ekem1_psk) encrypted section - pub bob_key_id: [u8; SESSION_ID_SIZE], - /// -- end encrypted section - pub p_gcm_tag: [u8; AES_GCM_TAG_SIZE], -} - -impl NoiseXKPattern2 { - pub const EKEM1_ENC_START: usize = HEADER_SIZE + P384_PUBLIC_KEY_SIZE; - pub const EKEM1_AUTH_START: usize = Self::EKEM1_ENC_START + KYBER_CIPHERTEXTBYTES; - pub const P_ENC_START: usize = Self::EKEM1_AUTH_START + AES_GCM_TAG_SIZE; - pub const P_AUTH_START: usize = Self::P_ENC_START + SESSION_ID_SIZE; - pub const P_AUTH_END: usize = Self::P_AUTH_START + AES_GCM_TAG_SIZE; - pub const SIZE: usize = Self::P_AUTH_END; -} - -/// Alice's final response containing her identity (she already knows Bob's) and meta-data. -/// While Alice's response does match what is described in this struct, -/// this struct is unused because it would contain variable length fields. -/// It is present here for documentation purposes. -#[repr(C, packed)] -pub(crate) struct NoiseXKPattern3 { - pub header: [u8; HEADER_SIZE], - /// -- start AES-GCM(k_es_ee_ekem1_psk) encrypted section - pub noise_s: [u8; P384_PUBLIC_KEY_SIZE], - /// -- end encrypted section - pub s_gcm_tag: [u8; AES_GCM_TAG_SIZE], - /// -- start AES-GCM(k_es_ee_ekem1_psk_se) encrypted section - pub alice_blob: [u8; 0], - /// -- end encrypted section - pub p_gcm_tag: [u8; AES_GCM_TAG_SIZE], -} -impl NoiseXKPattern3 { - pub const MIN_SIZE: usize = HEADER_SIZE + P384_PUBLIC_KEY_SIZE + AES_GCM_TAG_SIZE + AES_GCM_TAG_SIZE; - pub const MAX_SIZE: usize = MAX_NOISE_HANDSHAKE_SIZE; -} - -#[repr(C, packed)] -pub(crate) struct NoiseKKPattern1or2 { - pub header: [u8; HEADER_SIZE], - pub noise_e: [u8; P384_PUBLIC_KEY_SIZE], - pub key_id: [u8; SESSION_ID_SIZE], - pub gcm_tag: [u8; AES_GCM_TAG_SIZE], - pub kek_tag: [u8; AES_GCM_TAG_SIZE], -} -impl NoiseKKPattern1or2 { - pub const ENC_START: usize = HEADER_SIZE + P384_PUBLIC_KEY_SIZE; - pub const AUTH_START: usize = Self::ENC_START + SESSION_ID_SIZE; - pub const AUTH_END: usize = Self::AUTH_START + AES_GCM_TAG_SIZE; - pub const SIZE: usize = Self::AUTH_END + AES_GCM_TAG_SIZE; -} - -// Annotate only these structs as being compatible with byte_array_as_proto_buffer(). These structs -// are packed flat buffers containing only byte or byte array fields, making them safe to treat -// this way even on architectures that require type size aligned access. -pub(crate) trait ProtocolFlatBuffer {} -impl ProtocolFlatBuffer for NoiseXKPattern1 {} -impl ProtocolFlatBuffer for NoiseXKPattern2 {} -impl ProtocolFlatBuffer for NoiseKKPattern1or2 {} -impl ProtocolFlatBuffer for BobDOSChallenge {} -impl ProtocolFlatBuffer for ChallengeResponse {} - -#[inline(always)] -pub(crate) fn byte_array_as_proto_buffer(b: &[u8]) -> &B { - assert!(b.len() >= size_of::()); - unsafe { &*b.as_ptr().cast() } -} - -#[inline(always)] -pub(crate) fn byte_array_as_proto_buffer_mut(b: &mut [u8]) -> &mut B { - assert!(b.len() >= size_of::()); - unsafe { &mut *b.as_mut_ptr().cast() } -} -/// Trick rust into letting us use a hasher that returns more than 64 bits. -pub(crate) struct ShaHasher<'a, ShaImpl: HashSha512>(pub &'a mut ShaImpl); -impl<'a, ShaImpl: HashSha512> Hasher for ShaHasher<'a, ShaImpl> { - fn finish(&self) -> u64 { - panic!() - } - fn write(&mut self, bytes: &[u8]) { - self.0.update(bytes) - } -} diff --git a/src/ratchet_state.rs b/src/ratchet_state.rs index 378eb78..d90298b 100644 --- a/src/ratchet_state.rs +++ b/src/ratchet_state.rs @@ -1,10 +1,15 @@ +use arrayvec::ArrayVec; use zeroize::Zeroizing; -use crate::crypto::secure_eq; +use crate::crypto::*; 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 +19,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 { @@ -41,29 +52,23 @@ impl RatchetState { chain_len: 0, } } - //pub fn new_from_otp(otp: &[u8]) -> RatchetState { - // let mut buffer = Vec::new(); - // buffer.push(1); - // buffer.extend(LABEL_OTP_TO_RATCHET); - // buffer.push(0x00); - // buffer.extend((2u16 * 512u16).to_be_bytes()); - // let r1 = Hmac::hmac(otp, &buffer); - // buffer[0] = 2; - // let r2 = Hmac::hmac(otp, &buffer); - // Self::new( - // Zeroizing::new(r1[..RATCHET_SIZE].try_into().unwrap()), - // Zeroizing::new(r2[..RATCHET_SIZE].try_into().unwrap()), - // 1, - // ) - //} + pub fn new_from_otp(otp: &[u8]) -> RatchetState { + let mut buffer = ArrayVec::::new(); + buffer.push(1); + buffer.extend(*LABEL_OTP_TO_RATCHET); + buffer.push(0x00); + buffer.extend((1024u16).to_be_bytes()); - pub fn new_initial_states() -> (RatchetState, Option) { - (RatchetState::empty(), None) + let mut hmac = Hmac::new(); + let mut output = Zeroizing::new([0u8; HASHLEN]); + hmac.hash(otp, &buffer, &mut output); + let rk = Zeroizing::new(output[..RATCHET_SIZE].try_into().unwrap()); + buffer[0] = 2; + hmac.hash(otp, &buffer, &mut output); + let rf = Zeroizing::new(output[..RATCHET_SIZE].try_into().unwrap()); + + Self::new(rk, rf, 1) } - //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 +79,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/ratchet_state_old.rs b/src/ratchet_state_old.rs deleted file mode 100644 index 73519e6..0000000 --- a/src/ratchet_state_old.rs +++ /dev/null @@ -1,62 +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::num::NonZeroU64; - -#[derive(Clone, PartialEq, Eq)] -pub enum RatchetState { - Null, - Empty, - NonEmpty(NonEmptyRatchetState), -} -use RatchetState::*; -use zeroize::Zeroizing; - -use crate::proto::RATCHET_SIZE; -impl RatchetState { - pub fn new_nonempty(key: Zeroizing<[u8; RATCHET_SIZE]>, fingerprint: Zeroizing<[u8; RATCHET_SIZE]>, chain_len: NonZeroU64) -> Self { - NonEmpty(NonEmptyRatchetState { key, fingerprint, chain_len }) - } - pub fn new_initial_states() -> [RatchetState; 2] { - [RatchetState::Empty, RatchetState::Null] - } - pub fn is_null(&self) -> bool { - matches!(self, Null) - } - pub fn is_empty(&self) -> bool { - matches!(self, Empty) - } - pub fn nonempty(&self) -> Option<&NonEmptyRatchetState> { - match self { - NonEmpty(rs) => Some(rs), - _ => None, - } - } - pub fn chain_len(&self) -> u64 { - self.nonempty().map_or(0, |rs| rs.chain_len.get()) - } - //pub fn fingerprint(&self) -> Option<&[u8; RATCHET_SIZE]> { - // self.nonempty().map(|rs| rs.fingerprint.as_ref()) - //} - //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.as_ref()), - // } - //} -} -/// A ratchet key and fingerprint, -/// along with the length of the ratchet chain the keys were derived from. -#[derive(Clone, PartialEq, Eq)] -pub struct NonEmptyRatchetState { - pub key: Zeroizing<[u8; RATCHET_SIZE]>, - pub fingerprint: Zeroizing<[u8; RATCHET_SIZE]>, - pub chain_len: NonZeroU64, -} diff --git a/src/result.rs b/src/result.rs index c070bc9..15b023d 100644 --- a/src/result.rs +++ b/src/result.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use crate::applicationlayer::ApplicationLayer; +use crate::application::ApplicationLayer; use crate::zeta::Session; /// An error that can occur when attempting to open a session. diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index e2f0e5c..a780b72 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -1,12 +1,10 @@ use std::marker::PhantomData; -use arrayvec::ArrayVec; use zeroize::Zeroizing; -use crate::crypto::aes::{HighThroughputAesGcmPool, LowThroughputAesGcm, AES_GCM_IV_SIZE, AES_GCM_TAG_SIZE}; -use crate::crypto::sha512::{HashSha512, HmacSha512}; +use crate::crypto::*; use crate::proto::*; -use crate::{applicationlayer::ApplicationLayer, crypto::aes::AES_256_KEY_SIZE}; +use crate::application::ApplicationLayer; pub struct SymmetricState { k: Zeroizing<[u8; AES_256_KEY_SIZE]>, diff --git a/src/symmetric_state_old.rs b/src/symmetric_state_old.rs deleted file mode 100644 index 264fc45..0000000 --- a/src/symmetric_state_old.rs +++ /dev/null @@ -1,156 +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 crate::crypto::aes::AES_256_KEY_SIZE; -use crate::crypto::sha512::HmacSha512; - -use crate::proto::NOISE_HASHLEN; - -#[derive(Clone)] -pub(crate) struct SymmetricState { - chaining_key: Secret, - token_counter: u8, -} - -impl SymmetricState { - pub(crate) fn new(h: [u8; NOISE_HASHLEN]) -> Self { - Self { chaining_key: Secret(h), token_counter: b'P' } - } - /// Corresponds to Noise `MixKey`. - pub(crate) fn mix_key(&mut self, hm: &mut impl HmacSha512, input_key_material: &[u8]) { - let mut next_ck = Secret::new(); - - self.kbkdf(hm, input_key_material, self.label(), 2, next_ck.as_mut(), None, None); - self.token_counter += 1; - - self.chaining_key.overwrite(&next_ck); - // We don't need a key at this step of Noise, so generating that key and calling - // `InitializeKey` would be completely pointless. - } - /// Corresponds to Noise `MixKey` followed by `InitializeKey`. - pub(crate) fn mix_key_initialize_key(&mut self, hm: &mut impl HmacSha512, input_key_material: &[u8]) -> Secret { - let mut next_ck = Secret::new(); - let mut temp_k = [0u8; NOISE_HASHLEN]; - - self.kbkdf(hm, input_key_material, self.label(), 2, next_ck.as_mut(), Some(&mut temp_k), None); - self.token_counter += 1; - - self.chaining_key.overwrite(&next_ck); - Secret::from_bytes_then_delete(&mut temp_k[..AES_256_KEY_SIZE]) - } - /// Corresponds to Noise `MixKeyAndHash`. - pub(crate) fn mix_key_and_hash(&mut self, hm: &mut impl HmacSha512, input_key_material: &[u8]) -> [u8; NOISE_HASHLEN] { - let mut next_ck = Secret::new(); - let mut temp_h = [0u8; NOISE_HASHLEN]; - - self.kbkdf(hm, input_key_material, self.label(), 3, next_ck.as_mut(), Some(&mut temp_h), None); - self.token_counter += 1; - - self.chaining_key.overwrite(&next_ck); - temp_h - } - /// Corresponds to Noise `MixKeyAndHash` followed by `InitializeKey`. - pub(crate) fn mix_key_and_hash_initialize_key( - &mut self, - hm: &mut impl HmacSha512, - input_key_material: &[u8], - ) -> ([u8; NOISE_HASHLEN], Secret) { - let mut next_ck = Secret::new(); - let mut temp_h = [0u8; NOISE_HASHLEN]; - let mut temp_k = [0u8; NOISE_HASHLEN]; - - self.kbkdf( - hm, - input_key_material, - self.label(), - 3, - next_ck.as_mut(), - Some(&mut temp_h), - Some(&mut temp_k), - ); - self.token_counter += 1; - - self.chaining_key.overwrite(&next_ck); - (temp_h, Secret::from_bytes_then_delete(&mut temp_k[..AES_256_KEY_SIZE])) - } - /// Get an additional symmetric key (ASK) that is a collision resistant hash of the transcript, - /// is forward secrect and is cryptographically independent from all other produced keys. - /// Based on Noise's unstable ASK mechanism, using KBKDF instead of HKDF. - /// https://github.com/noiseprotocol/noise_wiki/wiki/Additional-Symmetric-Keys. - pub(crate) fn get_ask2( - &self, - hm: &mut impl HmacSha512, - label: u8, - noise_h: &[u8; NOISE_HASHLEN], - ) -> (Secret, Secret) { - let mut temp_k1 = [0u8; NOISE_HASHLEN]; - let mut temp_k2 = [0u8; NOISE_HASHLEN]; - self.kbkdf(hm, noise_h, [b'A', b'S', b'K', label], 2, &mut temp_k1, Some(&mut temp_k2), None); - ( - Secret::from_bytes_then_delete(&mut temp_k1[..AES_256_KEY_SIZE]), - Secret::from_bytes_then_delete(&mut temp_k2[..AES_256_KEY_SIZE]), - ) - } - /// Corresponds to Noise `Split`. - pub(crate) fn split(self, hm: &mut impl HmacSha512) -> (Secret, Secret) { - let mut temp_k1 = [0u8; NOISE_HASHLEN]; - let mut temp_k2 = [0u8; NOISE_HASHLEN]; - self.kbkdf(hm, &[], self.label(), 2, &mut temp_k1, Some(&mut temp_k2), None); - // Normally KBKDF would not truncate to derive the correct length of AES keys, - // but Noise specifies that the AES keys be truncated from NOISE_HASHLEN to AES_256_KEY_SIZE. - ( - Secret::from_bytes_then_delete(&mut temp_k1[..AES_256_KEY_SIZE]), - Secret::from_bytes_then_delete(&mut temp_k2[..AES_256_KEY_SIZE]), - ) - } - fn label(&self) -> [u8; 4] { - [b'Z', b'S', b'S', self.token_counter] - } - /// 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 - /// `HKDF(self.chaining_key, input_key_material)` but this is how NIST rolls. - /// These are the values we have assigned to the 4 variables involved in their KDF: - /// * K_IN = `input_key_material` - /// * Label = `label` - /// * Context = `self.chaining_key` - /// * L = `num_outputs*512u16` - /// We have intentionally made every input small and fixed size to avoid unnecessary complexity - /// and data representation ambiguity. - fn kbkdf( - &self, - hm: &mut impl HmacSha512, - input_key_material: &[u8], - label: [u8; 4], - num_outputs: u16, - output1: &mut [u8; NOISE_HASHLEN], - output2: Option<&mut [u8; NOISE_HASHLEN]>, - output3: Option<&mut [u8; NOISE_HASHLEN]>, - ) { - let l = &(num_outputs * 512u16).to_be_bytes(); - - hm.reset(input_key_material); - hm.update(&[1, label[0], label[1], label[2], label[3], 0x00]); - hm.update(self.chaining_key.as_ref()); - hm.update(l); - hm.finish(output1); - if let Some(output2) = output2 { - hm.reset(input_key_material); - hm.update(&[2, label[0], label[1], label[2], label[3], 0x00]); - hm.update(self.chaining_key.as_ref()); - hm.update(l); - hm.finish(output2); - } - if let Some(output3) = output3 { - hm.reset(input_key_material); - hm.update(&[3, label[0], label[1], label[2], label[3], 0x00]); - hm.update(self.chaining_key.as_ref()); - hm.update(l); - hm.finish(output3); - } - } -} diff --git a/src/zeta.rs b/src/zeta.rs index 79e7eba..0278fb7 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -10,21 +10,14 @@ use std::sync::{Arc, Mutex, RwLock, Weak}; use zeroize::Zeroizing; use crate::antireplay::Window; -use crate::applicationlayer::ApplicationLayer; -use crate::applicationlayer::RatchetUpdate; +use crate::application::*; use crate::challenge::{gen_null_response, respond_to_challenge_in_place}; use crate::zssp::{log, ContextInner, SessionQueue}; -//use crate::context::{log, ContextInner, SessionMap}; -use crate::crypto::aes::*; -use crate::crypto::kyber1024::{ - Kyber1024PrivateKey, KYBER_CIPHERTEXT_SIZE, KYBER_PLAINTEXT_SIZE, KYBER_PUBLIC_KEY_SIZE, -}; -use crate::crypto::p384::{P384KeyPair, P384PublicKey, P384_ECDH_SHARED_SECRET_SIZE, P384_PUBLIC_KEY_SIZE}; -use crate::crypto::sha512::{HashSha512, HmacSha512}; +use crate::crypto::*; use crate::fragged::Fragged; use crate::indexed_heap::BinaryHeapIndex; 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")] @@ -167,10 +160,6 @@ impl 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); - /// Corresponds to State A_1 of the Zeta State Machine found in Section 4.1. #[derive(Clone)] pub(crate) struct StateA1 { @@ -181,16 +170,17 @@ pub(crate) struct StateA1 { x1: ArrayVec, } +pub(crate) struct StateA3 { + identity: ArrayVec, + x3: ArrayVec, +} + + /// Corresponds to the ZKE Automata found in Section 4.1 - Definition 2. pub(crate) enum ZetaAutomata { Null, A1(Box>), - A3 { - identity: ArrayVec, - kid_send: u32, - nonce: [u8; AES_GCM_IV_SIZE], - x3: ArrayVec, - }, + A3(Box), S1, S2, R1 { @@ -232,6 +222,34 @@ impl SymmetricState { *i = j; App::PublicKey::from_bytes((pub_key).try_into().unwrap()) } + fn write_e_no_init( + &mut self, + hash: &mut App::Hash, + hmac: &mut App::HmacHash, + rng: &Mutex, + packet: &mut ArrayVec, + ) -> App::KeyPair { + let e_secret = App::KeyPair::generate(rng.lock().unwrap().deref_mut()); + let pub_key = e_secret.public_key_bytes(); + packet.extend(pub_key); + self.mix_hash(hash, &pub_key); + self.mix_key_no_init(hmac, &pub_key); + e_secret + } + fn read_e_no_init( + &mut self, + hash: &mut App::Hash, + hmac: &mut App::HmacHash, + i: &mut usize, + packet: &[u8], + ) -> Option { + let j = *i + P384_PUBLIC_KEY_SIZE; + let pub_key = &packet[*i..j]; + self.mix_hash(hash, pub_key); + self.mix_key_no_init(hmac, pub_key); + *i = j; + App::PublicKey::from_bytes((pub_key).try_into().unwrap()) + } fn mix_dh(&mut self, hmac: &mut App::HmacHash, secret: &App::KeyPair, remote: &App::PublicKey) -> Option<()> { let mut ecdh_secret = Zeroizing::new([0u8; P384_ECDH_SHARED_SECRET_SIZE]); if secret.agree(&remote, &mut ecdh_secret) { @@ -241,6 +259,15 @@ impl SymmetricState { None } } + fn mix_dh_no_init(&mut self, hmac: &mut App::HmacHash, secret: &App::KeyPair, remote: &App::PublicKey) -> Option<()> { + let mut ecdh_secret = Zeroizing::new([0u8; P384_ECDH_SHARED_SECRET_SIZE]); + if secret.agree(&remote, &mut ecdh_secret) { + self.mix_key_no_init(hmac, ecdh_secret.as_ref()); + Some(()) + } else { + None + } + } } /// Generate a random local key id that is currently unused. @@ -335,9 +362,9 @@ pub(crate) fn trans_to_a1( identity: &[u8], send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Result>, OpenError> { - let (ratchet_state1, ratchet_state2) = app + let RatchetStates{state1, state2} = app .restore_by_identity(&s_remote, &session_data) - .map_err(|e| OpenError::RatchetIoError(e))?; + .map_err(|e| OpenError::RatchetIoError(e))?.unwrap_or_default(); let mut session_queue = ctx.session_queue.lock().unwrap(); let mut session_map = ctx.session_map.write().unwrap(); @@ -351,8 +378,8 @@ pub(crate) fn trans_to_a1( &ctx.rng, &s_remote, kid_recv, - &ratchet_state1, - ratchet_state2.as_ref(), + &state1, + state2.as_ref(), identity, ) .ok_or(OpenError::InvalidPublicKey)?; @@ -382,8 +409,8 @@ pub(crate) fn trans_to_a1( window: Window::new(), state_machine_lock: Mutex::new(()), state: RwLock::new(MutableState { - ratchet_state1: ratchet_state1.clone(), - ratchet_state2: ratchet_state2.clone(), + ratchet_state1: state1.clone(), + ratchet_state2: state2.clone(), key_creation_counter: 0, key_index: true, keys: [DuplexKey::default(), DuplexKey::default()], @@ -709,8 +736,8 @@ 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 { @@ -746,12 +773,10 @@ pub(crate) fn received_x2_trans( // This return is unreachable. return Err(byzantine_fault!(FailedAuth, true)); }; - state.beta = ZetaAutomata::A3 { + state.beta = ZetaAutomata::A3(Box::new(StateA3 { identity: a1.identity.clone(), x3: x3.clone(), - kid_send: kid_send.get(), - nonce, - }; + })); resend_timer }; drop(kex_lock); @@ -845,8 +870,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 { @@ -875,8 +901,8 @@ 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 { @@ -1003,8 +1029,8 @@ pub(crate) fn received_c1_trans( state1: &state.ratchet_state1, state2: None, state1_was_just_added: false, - state_deleted1: state.ratchet_state2.as_ref(), - state_deleted2: None, + deleted_state1: state.ratchet_state2.as_ref(), + deleted_state2: None, }, ); if let Err(e) = result { @@ -1164,7 +1190,7 @@ pub(crate) fn process_timers( ZetaAutomata::A1(_) | ZetaAutomata::A3 { .. } => { let identity = match &state.beta { ZetaAutomata::A1(a1) => &a1.identity, - ZetaAutomata::A3 { identity, .. } => identity, + ZetaAutomata::A3(a3) => &a3.identity, _ => unreachable!(), }; if matches!(&state.beta, ZetaAutomata::A1(_)) { @@ -1302,9 +1328,9 @@ pub(crate) fn process_timers( send(&mut a1.x1.clone(), None); return Some(resend_next); } - ZetaAutomata::A3 { x3, .. } => { + ZetaAutomata::A3(a3) => { log!(app, ResentX3(session)); - send(&mut x3.clone(), Some(&session.hk_send)); + send(&mut a3.x3.clone(), Some(&session.hk_send)); return Some(resend_next); } ZetaAutomata::S1 => { @@ -1471,8 +1497,8 @@ pub(crate) fn received_k1_trans( state1: &new_ratchet_state, state2: Some(&state.ratchet_state1), state1_was_just_added: true, - state_deleted1: state.ratchet_state2.as_ref(), - state_deleted2: None, + deleted_state1: state.ratchet_state2.as_ref(), + deleted_state2: None, }, ); if let Err(e) = result { @@ -1605,8 +1631,8 @@ pub(crate) fn received_k2_trans( state1: &new_ratchet_state, state2: None, state1_was_just_added: true, - state_deleted1: Some(&state.ratchet_state1), - state_deleted2: state.ratchet_state2.as_ref(), + deleted_state1: Some(&state.ratchet_state1), + deleted_state2: state.ratchet_state2.as_ref(), }, ); if let Err(e) = result { diff --git a/src/zssp copy.rs b/src/zssp copy.rs deleted file mode 100644 index 413c38d..0000000 --- a/src/zssp copy.rs +++ /dev/null @@ -1,2704 +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/ -*/ -// ZSSP: ZeroTier Secure Session Protocol -// FIPS compliant Noise_XK with Jedi powers (Kyber1024) and built-in attack-resistant large payload (fragmentation) support. - -use std::cmp::Reverse; -use std::collections::HashMap; -use std::hash::Hash; -use std::num::{NonZeroU32, NonZeroU64}; -use std::ops::DerefMut; -use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering}; -use std::sync::{Arc, Mutex, MutexGuard, RwLock, Weak}; - -use crate::crypto::aes::{AesDec, AesEnc}; -use crate::crypto::p384::{P384KeyPair, P384PublicKey, P384_ECDH_SHARED_SECRET_SIZE, P384_PUBLIC_KEY_SIZE}; -use crate::crypto::pqc_kyber::KYBER_SECRETKEYBYTES; -use crate::crypto::rand_core::RngCore; -use crate::crypto::sha512::{HmacSha512, HashSha512}; - -use crate::error::{FaultType, OpenError, ReceiveError, SendError}; -use crate::frag_cache::UnassociatedFragCache; -use crate::fragged::{Assembled, Fragged}; -use crate::handshake_cache::UnassociatedHandshakeCache; -use crate::indexed_heap::{BinaryHeapIndex, IndexedBinaryHeap}; -use crate::log_event::LogEvent; -use crate::proto::*; -use crate::symmetric_state::SymmetricState; -use crate::{applicationlayer::*, RatchetState}; - -/// Session context for local application. -/// -/// Each application using ZSSP must create an instance of this to own sessions and -/// 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(pub Arc>); -impl Clone for Context { - fn clone(&self) -> Self { - Self(self.0.clone()) - } -} -pub struct ContextInner { - static_keypair: Application::KeyPair, - unassociated_defrag_cache: Mutex>, - unassociated_handshake_states: UnassociatedHandshakeCache, - /// `session_queue -> state_machine_lock -> state -> session_map` - session_queue: Mutex>, Reverse>>, - session_map: RwLock>, bool)>>, - challenge_counter: AtomicU64, - challenge_antireplay_window: [AtomicU64; CHALLENGE_COUNTER_WINDOW_MAX_OOO], - challenge_salt: [u8; CHALLENGE_SALT_SIZE], - rng: Mutex, -} - -/// Result generated by the context packet receive function, with possible payloads. -pub enum ReceiveResult<'b, Application: ApplicationLayer> { - /// 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<'b>), - /// Packet was a part of a handshake, and while it superficially appeared valid the application - /// explicitly rejected it. - /// Relates to callbacks `check_allow_incoming_session`, `hello_requires_recognized_ratchet` - /// and `check_accept_session`. - Rejected, -} - -#[derive(Debug, PartialEq, Eq)] -pub enum SessionEvent<'b> { - /// The received packet was valid, and it contained the necessary keys to fully establish a new - /// session with Alice, the handshake initiator. - /// - /// 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, - /// 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. - /// - /// Alice will receive this return value when the received packet confirms both parties - /// have completed the initial handshake and now have a shared session with each other. - /// If according to the upper protocol, Bob is the first party to send data, it is possible for - /// Alice to start receiving data from Bob before this value is returned. - /// - /// This return value can only occur once per session, only for session objects that were - /// created with `Context::open`. - Established, - /// Bob explicitly refused to establish a session with Alice, and sent us an error code. - /// The application should immediately drop this session as Bob will not allow us to connect. - /// - /// This return value cannot occur after a session is fully established. - Rejected, - /// The received packet was valid and a data payload was decoded and authenticated. - Data(&'b mut [u8]), - /// The received packet was some authentic protocol control packet. No action needs to be taken. - Control, -} - -#[derive(Debug, PartialEq, Eq)] -pub enum IncomingSessionAction { - Allow, - Challenge, - Drop, -} - -/// ZeroTier Secure Session Protocol (ZSSP) Session -/// -/// A FIPS/NIST compliant variant of Noise_XK with hybrid Kyber1024 PQ data forward secrecy. -pub struct Session { - /// An arbitrary application defined object associated with each session. - pub application_data: Application::SessionData, - /// Is true if the local peer acted as Bob, the responder in the initial key exchange. - pub was_bob: bool, - /// The receive context associated with this session, - /// only this context can receive messages from the remote peer. - context: Weak>, - /// Handle into the session queue for changing the update timer. - queue_idx: BinaryHeapIndex, - - remote_static_key: Application::PublicKey, - send_counter: AtomicU64, - /// This bool signals to all threads to stop incrementing the counter and instead error out. - session_has_expired: AtomicBool, - /// The following is a ring buffer of previously seen counter values, where we use the counter's - /// value as the index of the head of the ring buffer. - counter_antireplay_window: [AtomicU64; COUNTER_WINDOW_MAX_OOO], - /// Enforces atomicity of state machine transitions. - /// There is a standard locking sequence, - /// it goes `session_queue -> state_machine_lock -> state -> session_map`. - /// Any lock can be skipped but they must be locked in that order. - state_machine_lock: Mutex<()>, - state: RwLock>, - defrag: [Mutex>; SESSION_MAX_FRAGMENTS_OOO], - header_send_cipher: Application::PrpEnc, - header_receive_cipher: Application::PrpDec, - kex_send_cipher: Mutex>, - kex_receive_cipher: Mutex>, - /// Pre-computed rekeying values. - noise_kk_ss: Secret, - noise_kk_local_init_h: [u8; HASHLEN], - noise_kk_remote_init_h: [u8; HASHLEN], -} -/// `AesGcm` is not threadsafe, but it is threadsafe when inside a `Mutex`. -unsafe impl Send for Session {} -unsafe impl Sync for Session {} - -/// Session state may only be mutated during atomic transitions of the offer state machine. -struct SessionMutableState { - ratchet_states: [RatchetState; 2], - /// For OOO rekeying reliability we allow our version of Noise CipherState to hold the last two - /// session keys, instead of just the most recent one. - cipher_states: [Option>; 2], - /// This is the index of `noise_cipher_state` that contains the most recent key. - /// It will be attached to fragment headers to help with OOO transport. - current_key: usize, - /// This defines the exact state of the offer state machine we are in. - outgoing_offer: OfferStateMachine, -} - -/// These offer enums form a state machine. -/// Documented below are the only legal transitions for this state machine. -/// A session is initialized with an `outgoing_offer` of either NoiseXKPattern1 or Normal. -enum OfferStateMachine { - Normal { - timeout: i64, - }, // -> NoiseKKPattern1, NoiseKKPattern2 - /// This state uses a lot of memory so we put it on the heap. - NoiseXKPattern1or3(Box>), // -> Normal - NoiseKKPattern1 { - next_retry_time: AtomicI64, - timeout: i64, - new_key_id: NonZeroU32, - noise_e_secret: Application::KeyPair, - noise_message: [u8; NoiseKKPattern1or2::SIZE], - noise_ck: SymmetricState, - noise_h_pskep: [u8; HASHLEN], - }, // -> NoiseKKPattern2, KeyConfirm - NoiseKKPattern2 { - next_retry_time: AtomicI64, - timeout: i64, - noise_message: [u8; NoiseKKPattern1or2::SIZE], - kex_send_key: Secret, - }, // -> Normal - KeyConfirm { - next_retry_time: AtomicI64, - timeout: i64, - }, // -> Normal - Null, -} - -pub(crate) struct NoiseXKBobHandshakeState { - /// Can never be Null. - ratchet_state: RatchetState, - remote_key_id: NonZeroU32, - local_key_id: NonZeroU32, - header_receive_key: Secret, - header_send_key: Secret, - noise_h_ee1peekem1pskp: [u8; HASHLEN], - noise_e_secret: Application::KeyPair, - noise_ck_eseeekem1psk: SymmetricState, - noise_k_eseeekem1psk: Secret, - noise_pattern3_defrag: Mutex>, -} - -struct NoiseXKAliceHandshake { - next_retry_time: AtomicI64, - timeout: i64, - /// A secure random number put in the header of Alice's fragments to identify them. - /// If a DDOS attacker could guess this they could block Alice starting the handshake. - local_key_id: NonZeroU32, - alice_identity_blob: Application::LocalIdentityBlob, - offer: NoiseXKAliceHandshakeState, -} - -enum NoiseXKAliceHandshakeState { - NoiseXKPattern1 { - noise_h_ee1p: [u8; HASHLEN], - noise_e_secret: Application::KeyPair, - noise_e1_secret: Secret, - noise_ck_es: SymmetricState, - /// ZSSP assumes an unreliable, out-of-order physical transport environment, so for that - /// reason we have to resend key offers. - noise_message: [u8; NoiseXKPattern1::MAX_SIZE], - noise_message_len: usize, - message_id: u64, - }, - NoiseXKPattern3 { - noise_message: [u8; NoiseXKPattern3::MAX_SIZE], - noise_message_len: usize, - }, -} - -struct SessionKey { - remote_key_id: NonZeroU32, - local_key_id: NonZeroU32, - /// Pool of reusable sending ciphers. - receive_cipher_pool: [Mutex; 8], - /// Pool of reusable receiving ciphers. - send_cipher_pool: [Mutex; 8], - /// Rekey at or after this counter. - rekey_at_counter: u64, - /// Hard error when this counter value is reached or exceeded. - expire_at_counter: u64, -} - -macro_rules! byzantine_fault { - ($name:expr, $is_natural:ident) => { - ReceiveError::ByzantineFault { - file: file!(), - line: line!(), - error: $name, - is_naturally_occurring: $is_natural, - } - }; -} - -impl Context { - /// Create a new session context. - pub fn new(static_keypair: Application::KeyPair, mut rng: Application::Rng) -> Self { - debug_assert!(Application::REKEY_AFTER_TIME_MAX_JITTER_MS > 0, "Invalid protocol constant"); - let mut challenge_salt = [0u8; CHALLENGE_SALT_SIZE]; - rng.fill_bytes(&mut challenge_salt); - Self(Arc::new(ContextInner { - static_keypair, - unassociated_defrag_cache: Mutex::new(UnassociatedFragCache::new()), - unassociated_handshake_states: UnassociatedHandshakeCache::new(), - session_map: RwLock::new(HashMap::new()), - session_queue: Mutex::new(IndexedBinaryHeap::new()), - challenge_counter: AtomicU64::new(INIT_COUNTER), - challenge_antireplay_window: std::array::from_fn(|_| AtomicU64::new(0)), - challenge_salt, - rng: Mutex::new(rng), - })) - } - - /// Perform periodic background service and cleanup tasks. - /// - /// 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 second or two are not - /// a problem. - /// - /// * `send_to` - Function to get a sender and an MTU to send something over an active session - /// * `current_time` - 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 - /// should rekey. - pub fn service bool>( - &self, - app: &Application, - mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, - current_time: i64, - ) -> i64 { - let retry_next = current_time.saturating_add(Application::RETRY_INTERVAL_MS); - let mut next_service_time = 2 * Application::RETRY_INTERVAL_MS; - - let mut session_queue = self.0.session_queue.lock().unwrap(); - // This update system takes heavy advantage of the fact that sessions only need to be updated - // either roughly every second or roughly every hour. That big gap allows for minor optimizations. - // If the gap changes (unlikely) this code may need to be rewritten. - while let Some((session, timer, queue_idx)) = session_queue.peek() { - if timer.0 >= current_time { - next_service_time = next_service_time.min(timer.0 - current_time); - break; - } - let session = match session.upgrade() { - Some(s) => s, - _ => { - session_queue.remove(queue_idx); - continue; - } - }; - let state = session.state.read().unwrap(); - use OfferStateMachine::*; - let next_timer = match &state.outgoing_offer { - Normal { timeout, .. } => { - if *timeout <= current_time { - drop(state); - if let Some((send, _)) = send_to(&session) { - let result = initiate_rekey(&self.0, &session, send, current_time); - if result.is_ok() { - app.event_log(LogEvent::ServiceKKStart(&session), current_time); - } - result.unwrap_or(retry_next) - } else { - retry_next - } - } else { - *timeout - } - } - // If there's an outstanding attempt to open a session, retransmit this - // periodically in case the initial packet doesn't make it. - NoiseXKPattern1or3(handshake_state) => { - if let Some(ts) = process_timer(&handshake_state.next_retry_time, Application::RETRY_INTERVAL_MS, current_time) { - ts - } else { - // We have to eventually time out NoiseXKPattern3 because of unreliable network conditions. - if handshake_state.timeout <= current_time { - drop(state); - let _kex_lock = session.state_machine_lock.lock().unwrap(); - let mut state = session.state.write().unwrap(); - let ratchet_state = state.ratchet_states.clone(); - // Since we dropped the lock we must re-check if we are in the correct state. - if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { - if handshake_state.timeout <= current_time { - app.event_log(LogEvent::ServiceXKTimeout(&session), current_time); - handshake_state.reinitialize( - &session, - &ratchet_state, - &mut self.0.session_map.write().unwrap(), - &mut self.0.rng.lock().unwrap(), - current_time, - ); - } - } - } else if let Some((mut send, mut mtu)) = send_to(&session) { - mtu = mtu.max(MIN_TRANSPORT_MTU); - match &handshake_state.offer { - NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_message, noise_message_len, message_id, .. } => { - app.event_log(LogEvent::ServiceXK1Resend(&session), current_time); - // We are in state NoiseXKPattern1 so resend noise_pattern1. - send_with_fragmentation( - &mut send, - mtu, - &mut noise_message.clone()[..*noise_message_len], - PACKET_TYPE_NOISE_XK_PATTERN_1, - None, - *message_id, - None::<&Application::PrpEnc>, - ); - } - NoiseXKAliceHandshakeState::NoiseXKPattern3 { noise_message, noise_message_len, .. } => { - app.event_log(LogEvent::ServiceXK3Resend(&session), current_time); - send_with_fragmentation( - &mut send, - mtu, - &mut noise_message.clone()[..*noise_message_len], - PACKET_TYPE_NOISE_XK_PATTERN_3, - state.cipher_states[0].as_ref().map(|k| k.remote_key_id), - 0, - Some(&session.header_send_cipher), - ); - } - } - } - retry_next - } - } - NoiseKKPattern1 { next_retry_time, timeout, noise_message, .. } | NoiseKKPattern2 { next_retry_time, timeout, noise_message, .. } => { - if let Some(ts) = process_timer(next_retry_time, Application::RETRY_INTERVAL_MS, current_time) { - ts - } else { - if *timeout <= current_time { - app.event_log(LogEvent::ServiceKKTimeout(&session), current_time); - next_retry_time.store(i64::MAX, Ordering::Relaxed); - drop(state); - session.expire_inner(&self.0, &mut session_queue); - } else { - let packet_type = if let NoiseKKPattern1 { .. } = &state.outgoing_offer { - app.event_log(LogEvent::ServiceKK1Resend(&session), current_time); - PACKET_TYPE_NOISE_KK_PATTERN_1 - } else { - app.event_log(LogEvent::ServiceKK2Resend(&session), current_time); - PACKET_TYPE_NOISE_KK_PATTERN_2 - }; - if let Some((send, _)) = send_to(&session) { - let _ = session.send_control(&state, send, packet_type, noise_message); - } - } - retry_next - } - } - KeyConfirm { next_retry_time, timeout, .. } => { - if let Some(ts) = process_timer(next_retry_time, Application::RETRY_INTERVAL_MS, current_time) { - ts - } else { - if *timeout <= current_time { - app.event_log(LogEvent::ServiceKeyConfirmTimeout(&session), current_time); - next_retry_time.store(i64::MAX, Ordering::Relaxed); - drop(state); - session.expire_inner(&self.0, &mut session_queue); - } else { - app.event_log(LogEvent::ServiceKeyConfirmResend(&session), current_time); - if let Some((send, _)) = send_to(&session) { - let _ = session.send_control(&state, send, PACKET_TYPE_KEY_CONFIRM, &[]); - } - } - retry_next - } - } - Null => retry_next, - }; - session_queue.change_priority(queue_idx, Reverse(next_timer)); - } - drop(session_queue); - - self.0 - .unassociated_defrag_cache - .lock() - .unwrap() - .check_for_expiry(Application::INITIAL_OFFER_TIMEOUT_MS, current_time); - self.0.unassociated_handshake_states.service(current_time); - - next_service_time - } - - /// 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 - /// * `mtu` - MTU for initial packets - /// * `remote_static_key` - Remote side's static public NIST P-384 key - /// * `application_data` - Arbitrary data meaningful to the application to include with session - /// object - /// * `ratchet_state` - The last saved and confirmed ratchet state associated with this remote - /// peer, or None if we do not have one. - /// * `local_identity_blob` - Payload to be sent to Bob that contains the information necessary - /// for the upper protocol to authenticate and approve of Alice's identity. - /// * `current_time` - Current time in milliseconds. Does not have to be monotonic, nor synced - /// with the remote peer. Used to determine when this offer should be resent. - pub fn open( - &self, - app: &Application, - mut send: impl FnMut(&mut [u8]) -> bool, - mut mtu: usize, - remote_static_key: Application::PublicKey, - application_data: Application::SessionData, - local_identity_blob: Application::LocalIdentityBlob, - current_time: i64, - ) -> Result>, OpenError> { - mtu = mtu.max(MIN_TRANSPORT_MTU); - if local_identity_blob.as_ref().len() > MAX_IDENTITY_BLOB_SIZE { - return Err(OpenError::DataTooLarge); - } - let result = app.restore_by_identity(&remote_static_key, &application_data, current_time); - match result { - Ok(ratchet_states) => { - let sha512 = &mut Application::Hash::new(); - - let mut noise_kk_ss = Secret::new(); - if !self.0.static_keypair.agree(&remote_static_key, noise_kk_ss.as_mut()) { - return Err(OpenError::InvalidPublicKey); - } - let noise_kk_local_init_h = mix_hash(sha512, &INITIAL_H_REKEY, self.0.static_keypair.public_key_bytes()); - let noise_kk_local_init_h = mix_hash(sha512, &noise_kk_local_init_h, remote_static_key.as_bytes()); - let noise_kk_remote_init_h = mix_hash(sha512, &INITIAL_H_REKEY, remote_static_key.as_bytes()); - let noise_kk_remote_init_h = mix_hash(sha512, &noise_kk_remote_init_h, self.0.static_keypair.public_key_bytes()); - - let mut session_queue = self.0.session_queue.lock().unwrap(); - let mut session_map = self.0.session_map.write().unwrap(); - let local_key_id = generate_key_id(&session_map, &mut self.0.rng.lock().unwrap()); - // Begin Noise XKhfs+psk2. - let (offer, a2b_header_key, b2a_header_key) = NoiseXKAliceHandshake::::initialize( - local_key_id, - &remote_static_key, - &ratchet_states, - &mut self.0.rng.lock().unwrap(), - )?; - let handshake_state = Box::new(NoiseXKAliceHandshake { - next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), - timeout: current_time.saturating_add(Application::INITIAL_OFFER_TIMEOUT_MS), - local_key_id, - alice_identity_blob: local_identity_blob, - offer, - }); - if let NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_message, noise_message_len, message_id, .. } = &handshake_state.offer { - send_with_fragmentation( - &mut send, - mtu, - &mut noise_message.clone()[..*noise_message_len], - PACKET_TYPE_NOISE_XK_PATTERN_1, - None, - *message_id, - None::<&Application::PrpEnc>, - ); - } - - let queue_idx = session_queue.reserve_index(); - let session = Arc::new(Session { - context: Arc::downgrade(&self.0), - queue_idx, - application_data, - remote_static_key, - send_counter: AtomicU64::new(INIT_COUNTER), - session_has_expired: AtomicBool::new(false), - counter_antireplay_window: std::array::from_fn(|_| AtomicU64::new(0)), - state_machine_lock: Mutex::new(()), - state: RwLock::new(SessionMutableState { - ratchet_states: ratchet_states.clone(), - cipher_states: [None, None], - // Points at 1 until the first key is confirmed. - current_key: 1, - outgoing_offer: OfferStateMachine::NoiseXKPattern1or3(handshake_state), - }), - header_send_cipher: Application::PrpEnc::new(a2b_header_key.as_ref()), - header_receive_cipher: Application::PrpDec::new(b2a_header_key.as_ref()), - kex_receive_cipher: Mutex::new(None), - kex_send_cipher: Mutex::new(None), - noise_kk_ss: noise_kk_ss.clone(), - noise_kk_local_init_h, - noise_kk_remote_init_h, - defrag: std::array::from_fn(|_| Mutex::new(Fragged::new())), - was_bob: false, - }); - session_map.insert(local_key_id, (Arc::downgrade(&session), false)); - session_queue.push_reserved( - queue_idx, - Arc::downgrade(&session), - Reverse(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), - ); - - Ok(session) - } - Err(e) => Err(OpenError::RatchetIoError(e)), - } - } - - /// Receive, authenticate, decrypt, and process a physical wire packet. - /// - /// The check_allow_incoming_session function is called when an initial Noise_XK init message is - /// received. This is before anything is known about the caller. A return value of true proceeds - /// with negotiation. False drops the packet and ignores the inbound attempt. - /// - /// The check_accept_session function is called at the end of negotiation for an incoming - /// session with the caller's static public blob. It must return the P-384 static public key - /// extracted from the supplied blob and application data. A return of Some() accepts the - /// session and will always result in a new session ReceiveResult being returned. - /// - /// * `app` - Interface to application using ZSSP - /// * `check_allow_incoming_session` - Function to call to check whether an unidentified new - /// session should be accepted - /// * `check_accept_session` - Function to accept sessions after final negotiation. - /// The second argument is the identity blob that the remote peer sent us. The application - /// must verify this identity is associated with the remote peer's static key. - /// The third argument is the ratchet chain length, or ratchet count. - /// To prevent desync, if this function returns (Some(_), _), no other open session with the - /// same remote peer must exist. - /// * `send_unassociated_reply` - Function to send reply packets directly when no session exists - /// * `send_unassociated_mtu` - MTU for unassociated replies - /// * `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 - /// * `data_buf` - Buffer to receive decrypted and authenticated object data (an error is - /// returned if too small) - /// * `incoming_physical_packet_buf` - Buffer containing incoming wire packet - /// (receive() takes ownership) - /// * `current_time` - Current time in milliseconds. Does not have to be monotonic, nor synced - /// with the remote peer. Used to check the state of local offers we may currently have or want - /// to put in-flight. - pub fn receive<'a, SendFn: FnMut(&mut [u8]) -> bool>( - &self, - app: &Application, - check_allow_incoming_session: impl FnOnce() -> IncomingSessionAction, - check_accept_session: impl FnOnce(&Application::PublicKey, &[u8], u64) -> (Option<(bool, Application::SessionData)>, bool), - mut send_unassociated_reply: impl FnMut(&mut [u8]) -> bool, - mut send_unassociated_mtu: usize, - mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, - remote_address: &impl Hash, - data_buf: &'a mut [u8], - mut incoming_physical_packet_buf: Application::IncomingPacketBuffer, - current_time: i64, - ) -> Result, ReceiveError> { - send_unassociated_mtu = send_unassociated_mtu.max(MIN_TRANSPORT_MTU); - let incoming_physical_packet: &mut [u8] = incoming_physical_packet_buf.as_mut(); - let incoming_physical_packet_len = incoming_physical_packet.len(); - if incoming_physical_packet_len < MIN_PACKET_SIZE { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - - // The first section parses the header and looks up relevant state information. If it's a DATA - // or NOP packet it gets handled right here, otherwise we pull out a set of variables and - // continue to the logic that handles KEX and session control packets. - - let mut assembled_packet = Assembled::new(); // needs to outlive the block below - let mut incoming = None; - let (session, packet_type, fragments) = { - let local_key_id = incoming_physical_packet[0..SESSION_ID_SIZE].try_into().unwrap(); - // `from_ne_bytes` because this id was generated locally. - if let Some(local_key_id) = NonZeroU32::new(u32::from_ne_bytes(local_key_id)) { - let session_map = self.0.session_map.read().unwrap(); - if let Some((Some(session), key_index)) = session_map.get(&local_key_id).map(|r| (r.0.upgrade(), r.1 as usize)) { - drop(session_map); - session.header_receive_cipher.decrypt_in_place( - (&mut incoming_physical_packet[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]) - .try_into() - .unwrap(), - ); - let (fragment_count, fragment_no, packet_type, incoming_counter, header_nonce) = parse_packet_header(incoming_physical_packet); - // Handle replay protection. - if PACKET_TYPE_RANGE_TRANSPORT.contains(&packet_type) { - // For DOS resistant reply-protection we need to check that the given counter is - // in the window of valid counters immediately. - // But for packets larger than 1 fragment we can't actually record the - // counter as received until we've authenticated the packet. - // So we check the counter window twice, and only update it the second time - // after the packet has been authenticated. - if !session.check_receive_window(incoming_counter) { - // This can occur naturally if packets arrive way out of order, or - // if they are duplicates. - // This can also be naturally triggered if Bob has just successfully - // received the first session key and is reject all of Alice's resends. - // This can also occur if a session was manually expired, but not - // dropped, and the remote party is still sending us data. - return Err(byzantine_fault!(FaultType::ExpiredCounter, true)); - } - if packet_type != PACKET_TYPE_DATA { - // This is a control packet. - if fragment_count != 1 || fragment_no > 0 { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - return receive_control_fragment( - self, - session, - app, - send_to, - packet_type, - incoming_counter, - incoming_physical_packet_buf.as_mut(), - current_time, - ); - } - } else if packet_type == PACKET_TYPE_NOISE_XK_PATTERN_2 { - // We need to reject fragments marked with this type if they are sent out - // of sequence, since an attacker is able to replay them. - match &session.state.read().unwrap().outgoing_offer { - OfferStateMachine::NoiseXKPattern1or3(handshake_state) => match &handshake_state.offer { - NoiseXKAliceHandshakeState::NoiseXKPattern1 { .. } => { - if incoming_counter >= COUNTER_WINDOW_MAX_SKIP_AHEAD { - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - } - // This error can occur naturally if Bob's initial reply to Alice had a - // resend that was delayed massively and arrived out of order. - _ => return Err(byzantine_fault!(FaultType::OutOfSequence, true)), - }, - _ => return Err(byzantine_fault!(FaultType::OutOfSequence, false)), - }; - } else if packet_type == PACKET_TYPE_NOISE_XK_PATTERN_3 { - // This can be triggered if Bob successfully received a session key and - // needs to reject all of Alice's resends of PACKET_TYPE_NOISE_XK_PATTERN_3. - return Err(byzantine_fault!(FaultType::OutOfSequence, true)); - } else { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - // Handle defragmentation. - let fragments = if fragment_count > 1 { - let idx = incoming_counter as usize % session.defrag.len(); - session.defrag[idx].lock().unwrap().assemble( - header_nonce, - incoming_physical_packet_buf, - fragment_no, - fragment_count, - &mut assembled_packet, - ); - if assembled_packet.is_empty() { - // We have not yet authenticated the sender so we do not report - // receiving a packet from them. - return Ok(ReceiveResult::Unassociated); - } else { - assembled_packet.as_ref() - } - } else { - std::array::from_ref(&incoming_physical_packet_buf) - }; - // Handle DATA in the fastest path when we have a session. - if packet_type == PACKET_TYPE_DATA { - let state = session.state.read().unwrap(); - // The error here can occur because the other party is using a brand new - // session key that we have not received yet. - let key = state.cipher_states[key_index] - .as_ref() - .ok_or(byzantine_fault!(FaultType::OutOfSequence, true))?; - let mut c = key.get_receive_cipher(incoming_counter); - c.set_iv(&create_message_nonce(packet_type, incoming_counter)); - - let mut data_len = 0; - - // Decrypt fragments 0..N-1 where N is the number of fragments. - for f in fragments[..(fragments.len() - 1)].iter() { - let f: &[u8] = f.as_ref(); - debug_assert!(f.len() >= HEADER_SIZE); - let current_frag_data_start = data_len; - data_len += f.len() - HEADER_SIZE; - if data_len > data_buf.len() { - return Err(ReceiveError::DataBufferTooSmall); - } - c.decrypt(&f[HEADER_SIZE..], &mut data_buf[current_frag_data_start..data_len]); - } - - // Decrypt final fragment (or only fragment if not fragmented) - let current_frag_data_start = data_len; - let last_fragment = fragments.last().unwrap().as_ref(); - if last_fragment.len() < (HEADER_SIZE + AES_GCM_TAG_SIZE) { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - data_len += last_fragment.len() - (HEADER_SIZE + AES_GCM_TAG_SIZE); - if data_len > data_buf.len() { - return Err(ReceiveError::DataBufferTooSmall); - } - let payload_end = last_fragment.len() - AES_GCM_TAG_SIZE; - c.decrypt(&last_fragment[HEADER_SIZE..payload_end], &mut data_buf[current_frag_data_start..data_len]); - - let aead_authentication_ok = c.finish_decrypt(&last_fragment[payload_end..].try_into().unwrap()); - drop(c); - drop(state); - - if !aead_authentication_ok { - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - if !session.update_receive_window(incoming_counter) { - // This can be naturally triggered because Bob has just - // successfully received a session key and needs to reject - // all of Alice's resends. - // This can also occur naturally if some part of the outer - // system is duplicating the packets being sent to us. - // We are safely deduplicating them here. - return Err(byzantine_fault!(FaultType::ExpiredCounter, true)); - } - // Packet fully authenticated - return Ok(ReceiveResult::Session(session, SessionEvent::Data(&mut data_buf[..data_len]))); - } else if packet_type == PACKET_TYPE_NOISE_XK_PATTERN_2 { - (Some(session), packet_type, fragments) - } else { - unreachable!() - } - } else { - drop(session_map); - // Check for and handle PACKET_TYPE_ALICE_NOISE_XK_PATTERN_3 - incoming = self.0.unassociated_handshake_states.get(local_key_id); - if let Some(incoming) = incoming.as_ref() { - Application::PrpDec::new(incoming.header_receive_key.as_ref()).decrypt_in_place( - (&mut incoming_physical_packet[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]) - .try_into() - .unwrap(), - ); - let (fragment_count, fragment_no, packet_type, _, header_nonce) = parse_packet_header(incoming_physical_packet); - app.event_log( - LogEvent::ReceiveUnassociatedFragment(fragment_count, fragment_no, packet_type), - current_time, - ); - if packet_type != PACKET_TYPE_NOISE_XK_PATTERN_3 { - return Err(byzantine_fault!(FaultType::OutOfSequence, false)); - } - let fragments = if fragment_count > 1 { - incoming.noise_pattern3_defrag.lock().unwrap().assemble( - header_nonce, - incoming_physical_packet_buf, - fragment_no, - fragment_count, - &mut assembled_packet, - ); - if !assembled_packet.is_empty() { - assembled_packet.as_ref() - } else { - return Ok(ReceiveResult::Unassociated); - } - } else { - std::array::from_ref(&incoming_physical_packet_buf) - }; - // We must guarantee that this incoming handshake is processed once and only - // once. This prevents catastrophic nonce reuse caused by multithreading. - if self.0.unassociated_handshake_states.remove(local_key_id) { - (None, PACKET_TYPE_NOISE_XK_PATTERN_3, fragments) - } else { - return Ok(ReceiveResult::Unassociated); - } - } else { - // This can occur naturally because either Bob's incoming_sessions cache got - // full so Alice's incoming session was dropped, or the session this packet - // was for was dropped by the application. - return Err(byzantine_fault!(FaultType::UnknownLocalKeyId, true)); - } - } - } else { - let (fragment_count, fragment_no, packet_type, _, header_nonce) = parse_packet_header(incoming_physical_packet); - app.event_log( - LogEvent::ReceiveUnassociatedFragment(fragment_count, fragment_no, packet_type), - current_time, - ); - if packet_type != PACKET_TYPE_NOISE_XK_PATTERN_1 && packet_type != PACKET_TYPE_BOB_DOS_CHALLENGE { - return Err(byzantine_fault!(FaultType::OutOfSequence, false)); - } - let fragments = if fragment_count > 1 { - self.0.unassociated_defrag_cache.lock().unwrap().assemble( - header_nonce, - remote_address, - incoming_physical_packet_len - HEADER_SIZE, - incoming_physical_packet_buf, - fragment_no, - fragment_count, - Application::RETRY_INTERVAL_MS, - current_time, - &mut assembled_packet, - ); - if !assembled_packet.is_empty() { - assembled_packet.as_ref() - } else { - return Ok(ReceiveResult::Unassociated); - } - } else { - std::array::from_ref(&incoming_physical_packet_buf) - }; - (None, packet_type, fragments) - } - }; - - debug_assert!(!fragments.is_empty()); - debug_assert!(incoming.is_none() || session.is_none()); - - let message = &mut [0u8; MAX_NOISE_HANDSHAKE_SIZE]; - let message_size = assemble_fragments_into::(fragments, message)?; - if message_size < MIN_PACKET_SIZE { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - - use OfferStateMachine::*; - match packet_type { - PACKET_TYPE_NOISE_XK_PATTERN_1 => { - // Alice (remote) --> Bob (local) - // -> e, es, e1 - app.event_log(LogEvent::ReceiveUncheckedXK1, current_time); - - if session.is_some() || incoming.is_some() { - return Err(byzantine_fault!(FaultType::OutOfSequence, false)); - } - if !(NoiseXKPattern1::MIN_SIZE..=NoiseXKPattern1::MAX_SIZE).contains(&message_size) { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - // The message id must be the first 8 bytes of the gcm tag. - // This forces the message id to be authenticated along with the entire message. - let p_auth_end = message_size - ChallengeResponse::SIZE; - if message[8..16] != message[p_auth_end - 8..p_auth_end] { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - let p_size = p_auth_end - NoiseXKPattern1::P_ENC_START - AES_GCM_TAG_SIZE; - let total_ratchet_fingerprints = p_size / RATCHET_SIZE; - if p_size % RATCHET_SIZE != 0 || total_ratchet_fingerprints > 2 { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - - let noise_pattern1: &NoiseXKPattern1 = byte_array_as_proto_buffer(message); - if let Some(remote_key_id) = NonZeroU32::new(u32::from_ne_bytes(noise_pattern1.alice_key_id)) { - let sha512 = &mut Application::Hash::new(); - // Let application filter incoming connection attempts by whatever criteria it wants. - // This should ideally prevent ZSSP from wasting time on DDOS attacks. - match check_allow_incoming_session() { - IncomingSessionAction::Allow => {} - IncomingSessionAction::Challenge => { - let response: &ChallengeResponse = byte_array_as_proto_buffer(&message[p_auth_end..message_size]); - let counter = u64::from_be_bytes(response.challenge_counter.try_into().unwrap()); - - sha512.reset(); - let mut hasher = ShaHasher(sha512); - let mut output = [0u8; HASHLEN]; - hasher.0.update(&response.challenge_counter); - remote_address.hash(&mut hasher); - hasher.0.update(&self.0.challenge_salt); - hasher.0.finish(&mut output); - let is_valid = self.check_challenge_window(counter) - && secure_eq(&output[..CHALLENGE_MAC_SIZE], &response.challenge_mac) - && verify_pow::(hasher.0, &message[p_auth_end..message_size]) - && self.update_challenge_window(counter); - app.event_log(LogEvent::ReceiveCheckXK1Challenge(is_valid), current_time); - if !is_valid { - // Alice failed the challenge so issue them a new challenge. - let mut challenge_buffer = [0u8; BobDOSChallenge::SIZE]; - let challenge: &mut BobDOSChallenge = byte_array_as_proto_buffer_mut(&mut challenge_buffer); - challenge.alice_key_id = remote_key_id.get().to_ne_bytes(); - // We attach a monotonically increasing counter value to the challenge - // so it cannot be replayed. - let counter = self.0.challenge_counter.fetch_add(1, Ordering::Relaxed); - challenge.challenge_counter = counter.to_be_bytes(); - - hasher.0.reset(); - hasher.0.update(&counter.to_be_bytes()); - remote_address.hash(&mut hasher); - hasher.0.update(&self.0.challenge_salt); - hasher.0.finish(&mut output); - challenge.challenge_mac.copy_from_slice(&output[..CHALLENGE_MAC_SIZE]); - challenge.prior_challenge_pow = response.challenge_pow; - // We haven't decrypted any of Alice's packet so we don't know the - // header protection cipher. - // For DOS resistance Alice will not accept unencrypted headers directly - // into their session defrag buffer, so we have to send them this reply - // through their incoming sessions cache. - send_with_fragmentation( - &mut send_unassociated_reply, - send_unassociated_mtu, - &mut challenge_buffer, - PACKET_TYPE_BOB_DOS_CHALLENGE, - None, - self.0.rng.lock().unwrap().next_u64(), - None::<&Application::PrpEnc>, - ); - return Ok(ReceiveResult::Unassociated); - } - // Alice succeeded at the challenge so continue to decryption. - } - IncomingSessionAction::Drop => return Ok(ReceiveResult::Rejected), - } - - // Noise process handshake prologue. - let noise_h = mix_hash( - sha512, - &INITIAL_H, - &message[NoiseXKPattern1::PROLOGUE_START..NoiseXKPattern1::PROLOGUE_END], - ); - let noise_h = mix_hash(sha512, &noise_h, self.0.static_keypair.public_key_bytes()); - // Noise process pattern1 e token. - let mut noise_ck = SymmetricState::new(INITIAL_H); - let hmac = &mut Application::HmacHash::new(); - let mut noise_es = Secret::new(); - let noise_e_pattern1 = from_bytes_agreement::(&noise_pattern1.noise_e, &self.0.static_keypair, noise_es.as_mut()) - .ok_or(byzantine_fault!(FaultType::FailedAuthentication, false))?; - let noise_h_e = mix_hash(sha512, &noise_h, &noise_pattern1.noise_e); - noise_ck.mix_key(hmac, &noise_pattern1.noise_e); - // Noise process pattern1 es token. - let noise_k_es = noise_ck.mix_key_initialize_key(hmac, noise_es.as_ref()); - drop(noise_es); - // Noise process pattern1 e1 token. - let (is_auth, noise_h_ee1) = decrypt_and_hash::( - sha512, - &noise_k_es, - &noise_h_e, - packet_type, - 0, - &mut message[NoiseXKPattern1::E1_ENC_START..NoiseXKPattern1::P_ENC_START], - ); - if !is_auth { - // This could occur naturally if Alice's ApplicationLayer is dynamically - // changing their mtu, which in bad network conditions could clobber their - // resent KEX packet. - // Or maybe Alice randomly generated the same temporary id twice in a row. - // Since these situations are super unlikely to occur we still mark this error - // as unnatural. - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - // Noise process pattern1 payload. - let (is_auth, noise_h_ee1p) = decrypt_and_hash::( - sha512, - &noise_k_es, - &noise_h_ee1, - packet_type, - 1, - &mut message[NoiseXKPattern1::P_ENC_START..p_auth_end], - ); - drop(noise_k_es); - if !is_auth { - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - let (header_b2a_key, header_a2b_key) = noise_ck.get_ask2(hmac, LABEL_HEADER_KEY, &noise_h_ee1p); - // Get ratchet key. - let noise_pattern1: &NoiseXKPattern1 = byte_array_as_proto_buffer(message); - let mut ratchet_state = RatchetState::Null; - for i in 0..total_ratchet_fingerprints { - match app.restore_by_fingerprint( - (&noise_pattern1.payload[i * RATCHET_SIZE..(i + 1) * RATCHET_SIZE]).try_into().unwrap(), - current_time, - ) { - Ok(RatchetState::Null) | Ok(RatchetState::Empty) => {} - Ok(rs) => { - ratchet_state = rs; - break; - } - Err(e) => return Err(ReceiveError::RatchetIoError(e)), - } - } - if ratchet_state.is_null() { - if app.hello_requires_recognized_ratchet(current_time) { - return Ok(ReceiveResult::Rejected); - } - ratchet_state = RatchetState::Empty; - } - - // Start of Noise XKhfs+psk2 pattern2. - let mut message2 = [0u8; NoiseXKPattern2::SIZE]; - let noise_pattern2: &mut NoiseXKPattern2 = byte_array_as_proto_buffer_mut(&mut message2); - // Noise process pattern2 e token. - let noise_e_pattern2_secret = Application::KeyPair::generate(&mut self.0.rng.lock().unwrap()); - noise_pattern2.noise_e = *noise_e_pattern2_secret.public_key_bytes(); - let noise_h_ee1pe = mix_hash(sha512, &noise_h_ee1p, &noise_pattern2.noise_e); - noise_ck.mix_key(hmac, &noise_pattern2.noise_e); - // Noise process pattern2 ee token. - let mut noise_ee = Secret::new(); - if !noise_e_pattern2_secret.agree(&noise_e_pattern1, noise_ee.as_mut()) { - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - let noise_k_esee = noise_ck.mix_key_initialize_key(hmac, noise_ee.as_ref()); - drop(noise_ee); - // Noise process pattern2 ekem1 token. - let (noise_ekem1, noise_ekem1_secret) = pqc_kyber::encapsulate(&noise_pattern1.noise_e1, self.0.rng.lock().unwrap().deref_mut()) - .map_err(|_| byzantine_fault!(FaultType::FailedAuthentication, false)) - .map(|(ct, ekem1)| (ct, Secret(ekem1)))?; - // Alice fully authenticated. - noise_pattern2.noise_ekem1 = noise_ekem1; - let noise_h_ee1peekem1 = encrypt_and_hash::( - sha512, - &noise_k_esee, - &noise_h_ee1pe, - PACKET_TYPE_NOISE_XK_PATTERN_2, - 0, - &mut message2[NoiseXKPattern2::EKEM1_ENC_START..NoiseXKPattern2::P_ENC_START], - ); - drop(noise_k_esee); - noise_ck.mix_key(hmac, noise_ekem1_secret.as_ref()); - drop(noise_ekem1_secret); - // Noise process pattern2 psk token. - let ratchet_key = ratchet_state.key().unwrap(); - let (temp_h, noise_k_eseeekem1psk) = noise_ck.mix_key_and_hash_initialize_key(hmac, ratchet_key); - let noise_h_ee1peekem1psk = mix_hash(sha512, &noise_h_ee1peekem1, &temp_h); - // Noise process pattern2 payload. - // We try to prevent the id we generate from colliding with another session but - // because we might have handshakes in flight it's impossible to 100% prevent. - // In those exceedingly rare cases we have to drop Alice's session and start over. - let local_key_id = generate_key_id(&self.0.session_map.read().unwrap(), &mut self.0.rng.lock().unwrap()); - let noise_pattern2: &mut NoiseXKPattern2 = byte_array_as_proto_buffer_mut(&mut message2); - noise_pattern2.bob_key_id = local_key_id.get().to_ne_bytes(); - - let noise_h_ee1peekem1pskp = encrypt_and_hash::( - sha512, - &noise_k_eseeekem1psk, - &noise_h_ee1peekem1psk, - PACKET_TYPE_NOISE_XK_PATTERN_2, - 0, - &mut message2[NoiseXKPattern2::P_ENC_START..NoiseXKPattern2::P_AUTH_END], - ); - - app.event_log(LogEvent::ReceiveValidXK1, current_time); - let handshake = Arc::new(NoiseXKBobHandshakeState { - local_key_id, - remote_key_id, - ratchet_state, - noise_h_ee1peekem1pskp, - noise_ck_eseeekem1psk: noise_ck.clone(), - noise_k_eseeekem1psk: noise_k_eseeekem1psk.clone(), - noise_e_secret: noise_e_pattern2_secret, - header_receive_key: header_a2b_key.clone(), - header_send_key: header_b2a_key.clone(), - noise_pattern3_defrag: Mutex::new(Fragged::new()), - }); - self.0.unassociated_handshake_states.insert(local_key_id, handshake, current_time); - - // We put a copy of the gcm tag in the header so Alice can tell this packet apart - // from any other pattern 1 packet we send, without having to make Bob maintain state. - let mut pattern2_id = 0u64.to_ne_bytes(); - pattern2_id[5] = message2[NoiseXKPattern2::P_AUTH_END - 3]; - pattern2_id[6] = message2[NoiseXKPattern2::P_AUTH_END - 2]; - pattern2_id[7] = message2[NoiseXKPattern2::P_AUTH_END - 1]; - send_with_fragmentation( - &mut send_unassociated_reply, - send_unassociated_mtu, - &mut message2, - PACKET_TYPE_NOISE_XK_PATTERN_2, - Some(remote_key_id), - u64::from_be_bytes(pattern2_id), - Some(&Application::PrpEnc::new(header_b2a_key.first_n::())), - ); - - return Ok(ReceiveResult::Unassociated); - } else { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - } - PACKET_TYPE_BOB_DOS_CHALLENGE => { - let message = &mut message[..message_size]; - app.event_log(LogEvent::ReceiveUncheckedDOSChallenge, current_time); - - // We expect Bob to only send this to us through our unassociated defrag cache. - if incoming.is_some() || session.is_some() { - return Err(byzantine_fault!(FaultType::OutOfSequence, false)); - } - if message.len() != BobDOSChallenge::SIZE { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - let challenge: &BobDOSChallenge = byte_array_as_proto_buffer(message); - - if let Some(local_key_id) = NonZeroU32::new(u32::from_ne_bytes(challenge.alice_key_id)) { - if let Some(session) = self.0.session_map.read().unwrap().get(&local_key_id).and_then(|s| s.0.upgrade()) { - // We don't need to hold the kex lock because we are not transitioning state. - let mut state = session.state.write().unwrap(); - if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { - if let NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_message, noise_message_len, .. } = &mut handshake_state.offer { - let response_raw = &mut noise_message[*noise_message_len - ChallengeResponse::SIZE..]; - - let response: &mut ChallengeResponse = byte_array_as_proto_buffer_mut(response_raw); - // Only people who know what Alice's prior pow was can convince us to - // compute a new pow. - if challenge.prior_challenge_pow != response.challenge_pow { - // This can occur if Bob sends us multiple challenges and they - // arrive OOO. - return Err(byzantine_fault!(FaultType::FailedAuthentication, true)); - } - response.challenge_counter.copy_from_slice(&challenge.challenge_counter); - response.challenge_mac.copy_from_slice(&challenge.challenge_mac); - let mut pow = self.0.rng.lock().unwrap().next_u64(); - let sha512 = &mut Application::Hash::new(); - loop { - let response: &mut ChallengeResponse = byte_array_as_proto_buffer_mut(response_raw); - response.challenge_pow.copy_from_slice(&pow.to_be_bytes()); - if verify_pow::(sha512, response_raw) { - break; - } - pow = pow.wrapping_add(1); - } - - app.event_log(LogEvent::ReceiveValidDOSChallenge(&session), current_time); - return Ok(ReceiveResult::Unassociated); - } else { - // This could happen if Bob challenges Alice, but their challenge packet - // gets massively delayed. - return Err(byzantine_fault!(FaultType::OutOfSequence, true)); - } - } else { - // This could happen if Bob challenges Alice, but their challenge packet - // gets massively delayed. - return Err(byzantine_fault!(FaultType::OutOfSequence, true)); - } - } else { - // This can occur naturally if Alice's session was dropped. - return Err(byzantine_fault!(FaultType::UnknownLocalKeyId, true)); - } - } else { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - } - PACKET_TYPE_NOISE_XK_PATTERN_2 => { - // Bob (remote) --> Alice (local) - // <- e, ee, ekem1, psk - let message = &mut message[..message_size]; - app.event_log(LogEvent::ReceiveUncheckedXK2, current_time); - - if incoming.is_some() { - return Err(byzantine_fault!(FaultType::OutOfSequence, false)); - } - if message.len() != NoiseXKPattern2::SIZE { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - - let session = session.ok_or(byzantine_fault!(FaultType::UnknownLocalKeyId, false))?; - let kex_lock = session.state_machine_lock.lock().unwrap(); - let state = session.state.read().unwrap(); - - if let NoiseXKPattern1or3(handshake_state) = &state.outgoing_offer { - if let NoiseXKAliceHandshakeState::NoiseXKPattern1 { - noise_h_ee1p, noise_e_secret, noise_e1_secret, noise_ck_es, .. - } = &handshake_state.offer - { - let noise_pattern2: &NoiseXKPattern2 = byte_array_as_proto_buffer(message); - // Authenticate header counter. - if noise_pattern2.header[13..16] != noise_pattern2.p_gcm_tag[13..16] { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - - // Noise process pattern2 e token. - let mut noise_ee = Secret::new(); - if let Some(noise_e_pattern2) = - from_bytes_agreement::(&noise_pattern2.noise_e, noise_e_secret, noise_ee.as_mut()) - { - let sha512 = &mut Application::Hash::new(); - let hmac = &mut Application::HmacHash::new(); - let mut noise_ck = noise_ck_es.clone(); - let noise_h_ee1pe = mix_hash(sha512, noise_h_ee1p, noise_e_pattern2.as_bytes()); - noise_ck.mix_key(hmac, noise_e_pattern2.as_bytes()); - // Noise process pattern2 ee token. - let noise_k_esee = noise_ck.mix_key_initialize_key(hmac, noise_ee.as_ref()); - drop(noise_ee); - // Noise process pattern2 ekem1 token. - let (is_auth, noise_h_ee1peekem1) = decrypt_and_hash::( - sha512, - &noise_k_esee, - &noise_h_ee1pe, - packet_type, - 0, - &mut message[NoiseXKPattern2::EKEM1_ENC_START..NoiseXKPattern2::P_ENC_START], - ); - let noise_pattern2: &NoiseXKPattern2 = byte_array_as_proto_buffer(message); - let noise_ekem1_secret = pqc_kyber::decapsulate(&noise_pattern2.noise_ekem1, noise_e1_secret.as_ref()).map(Secret); - if let Some(Ok(noise_ekem1_secret)) = is_auth.then_some(noise_ekem1_secret) { - noise_ck.mix_key(hmac, noise_ekem1_secret.as_ref()); - drop(noise_ekem1_secret); - - // We attempt to decrypt the payload at most three times. First two times with - // the ratchet key Alice remembers, and final time with a ratchet - // key of zero if Alice allows ratchet downgrades. - // The following code is not constant time, meaning we leak to an - // attacker whether or not we downgraded. - // We don't currently consider this sensitive enough information to hide. - let mut test_ratchet_key = |ratchet_key| -> Option<(NonZeroU32, SymmetricState, Secret, [u8; 64])> { - // Check for which ratchet key Bob wants to use. - let mut noise_ck = noise_ck.clone(); - let mut payload = [0u8; NoiseXKPattern2::P_AUTH_END - NoiseXKPattern2::P_ENC_START]; - payload.copy_from_slice(&message[NoiseXKPattern2::P_ENC_START..NoiseXKPattern2::P_AUTH_END]); - // Noise process pattern2 psk token. - let (temp_h, noise_k_eseeekem1psk) = noise_ck.mix_key_and_hash_initialize_key(hmac, ratchet_key); - let noise_h_ee1peekem1psk = mix_hash(sha512, &noise_h_ee1peekem1, &temp_h); - // Noise process pattern2 payload. - let (is_auth, noise_h_ee1peekem1pskp) = decrypt_and_hash::( - sha512, - &noise_k_eseeekem1psk, - &noise_h_ee1peekem1psk, - packet_type, - 0, - &mut payload, - ); - if is_auth { - let key_id = NonZeroU32::new(u32::from_ne_bytes( - (&payload[..NoiseXKPattern2::P_AUTH_START - NoiseXKPattern2::P_ENC_START]) - .try_into() - .unwrap(), - )); - key_id.map(|kid| (kid, noise_ck, noise_k_eseeekem1psk, noise_h_ee1peekem1pskp)) - } else { - None - } - }; - // Check first key. - let mut ratchet_i = 0; - let mut result = None; - let mut chain_len = 0; - if let Some(key) = state.ratchet_states[0].key() { - chain_len = state.ratchet_states[0].chain_len(); - result = test_ratchet_key(key); - } - // Check second key. - if result.is_none() { - ratchet_i = 1; - if let Some(key) = state.ratchet_states[1].key() { - chain_len = state.ratchet_states[1].chain_len(); - result = test_ratchet_key(key); - } - } - // Check zero key. - if result.is_none() && !app.initiator_disallows_downgrade(&session, current_time) { - chain_len = 0; - result = test_ratchet_key(&[0u8; RATCHET_SIZE]); - if result.is_some() { - // TODO: add some kind of warning callback or signal. - } - } - - if let Some((remote_key_id, mut noise_ck, noise_k_eseeekem1psk, noise_h_ee1peekem1pskp)) = result { - // Start of Noise XKhfs+psk2 pattern3. - let mut message3 = [0u8; NoiseXKPattern3::MAX_SIZE]; - // Noise process pattern3 s token. - let mut noise_se = Secret::new(); - if self.0.static_keypair.agree(&noise_e_pattern2, noise_se.as_mut()) { - let payload = handshake_state.alice_identity_blob.as_ref(); - // Packet fully authenticated. - let s_enc_start = HEADER_SIZE; - let s_auth_start = s_enc_start + P384_PUBLIC_KEY_SIZE; - let p_enc_start = s_auth_start + AES_GCM_TAG_SIZE; - let p_auth_start = p_enc_start + payload.len(); - let p_auth_end = p_auth_start + AES_GCM_TAG_SIZE; - let message3_len = p_auth_end; - - message3[s_enc_start..s_auth_start].copy_from_slice(self.0.static_keypair.public_key_bytes()); - let noise_h_ee1peekem1pskps = encrypt_and_hash::( - sha512, - &noise_k_eseeekem1psk, - &noise_h_ee1peekem1pskp, - PACKET_TYPE_NOISE_XK_PATTERN_3, - 1, - &mut message3[s_enc_start..p_enc_start], - ); - drop(noise_k_eseeekem1psk); - // Noise process pattern3 se token. - let noise_k_eseeekem1pskse = noise_ck.mix_key_initialize_key(hmac, noise_se.as_ref()); - drop(noise_se); - // Noise process pattern3 payload token. - message3[p_enc_start..p_auth_start].copy_from_slice(payload); - let noise_h_ee1peekem1pskpsp = encrypt_and_hash::( - sha512, - &noise_k_eseeekem1pskse, - &noise_h_ee1peekem1pskps, - PACKET_TYPE_NOISE_XK_PATTERN_3, - 0, - &mut message3[p_enc_start..p_auth_end], - ); - drop(noise_k_eseeekem1pskse); - // Alice finished Noise XKhfs+psk2 handshake. - // Transition offer state machine to the NoiseXKPattern3 state. - let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_ee1peekem1pskpsp); - let new_ratchet_state = RatchetState::new_nonempty(rk, rf, NonZeroU64::new(chain_len + 1).unwrap()); - - let ratchet_to_preserve = &state.ratchet_states[ratchet_i]; - let result = app.save_ratchet_state( - &session.remote_static_key, - &session.application_data, - [&state.ratchet_states[0], &state.ratchet_states[1]], - [&new_ratchet_state, ratchet_to_preserve], - current_time, - ); - if let Err(e) = result { - return Err(ReceiveError::RatchetIoError(e)); - } - - let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(hmac, LABEL_KEX_KEY, &noise_h_ee1peekem1pskpsp); - - let local_key_id = handshake_state.local_key_id; - drop(state); - let mut state = session.state.write().unwrap(); - session - .kex_send_cipher - .lock() - .unwrap() - .replace(Application::AeadEnc::new(kex_key_a2b.as_ref())); - session - .kex_receive_cipher - .lock() - .unwrap() - .replace(Application::AeadDec::new(kex_key_b2a.as_ref())); - state.ratchet_states[1] = state.ratchet_states[ratchet_i].clone(); - state.ratchet_states[0] = new_ratchet_state; - - state.cipher_states[0].replace(SessionKey::new( - hmac, - noise_ck, - local_key_id, - remote_key_id, - INIT_COUNTER, - false, - )); - debug_assert!(state.cipher_states[1].is_none()); - if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { - handshake_state.next_retry_time = - AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)); - handshake_state.timeout = current_time.saturating_add(Application::INITIAL_OFFER_TIMEOUT_MS); - handshake_state.offer = NoiseXKAliceHandshakeState::NoiseXKPattern3 { - noise_message: message3, - noise_message_len: p_auth_end, - }; - } - drop(state); - drop(kex_lock); - - if let Some((mut send, mut mtu)) = send_to(&session) { - mtu = mtu.max(MIN_TRANSPORT_MTU); - send_with_fragmentation( - &mut send, - mtu, - &mut message3[..message3_len], - PACKET_TYPE_NOISE_XK_PATTERN_3, - Some(remote_key_id), - 0, - Some(&session.header_send_cipher), - ); - } - app.event_log(LogEvent::ReceiveValidXK2(&session), current_time); - return Ok(ReceiveResult::Session(session, SessionEvent::Control)); - } - } - } - } - // Bob failed authentication so we must restart our offer according to Noise. - // We restart the offer instead of dropping the session to defend against DOS. - drop(state); - let mut state = session.state.write().unwrap(); - let ratchet_state = state.ratchet_states.clone(); - if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { - if !handshake_state.reinitialize( - &session, - &ratchet_state, - &mut self.0.session_map.write().unwrap(), - &mut self.0.rng.lock().unwrap(), - current_time, - ) { - session.expire() - } - } - drop(state); - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } else { - return Err(byzantine_fault!(FaultType::OutOfSequence, false)); - } - } else { - return Err(byzantine_fault!(FaultType::OutOfSequence, false)); - } - } - PACKET_TYPE_NOISE_XK_PATTERN_3 => { - // Alice (remote) --> Bob (local) - // -> s, se - let message = &mut message[..message_size]; - app.event_log(LogEvent::ReceiveUncheckedXK3, current_time); - - if session.is_some() { - return Err(byzantine_fault!(FaultType::OutOfSequence, false)); - } - if message.len() < NoiseXKPattern3::MIN_SIZE || message.len() > NoiseXKPattern3::MAX_SIZE { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - // The code above guarantees to us that each `incoming` handshake state that reaches - // this point will be strictly unique, even for the same remote peer. - // This property is strictly necessary to prevent catastrophic nonce reuse due to - // two session being created with the same set of keys. - let handshake_state = incoming.ok_or(byzantine_fault!(FaultType::UnknownLocalKeyId, false))?; - let s_enc_start = HEADER_SIZE; - - let s_auth_start = s_enc_start + P384_PUBLIC_KEY_SIZE; - let p_enc_start = s_auth_start + AES_GCM_TAG_SIZE; - let p_auth_end = message.len(); - let p_auth_start = p_auth_end - AES_GCM_TAG_SIZE; - - if !(p_enc_start <= p_auth_start) { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - // Do not read from the message before this point, otherwise an array out of bounds - // error is possible. - // Noise process pattern3 s token. - let sha512 = &mut Application::Hash::new(); - let hmac = &mut Application::HmacHash::new(); - let (is_auth, noise_h_ee1peekem1pskps) = decrypt_and_hash::( - sha512, - &handshake_state.noise_k_eseeekem1psk, - &handshake_state.noise_h_ee1peekem1pskp, - packet_type, - 1, - &mut message[s_enc_start..p_enc_start], - ); - if !is_auth { - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - // Noise process pattern3 se token. - let mut noise_se = Secret::new(); - if let Some(remote_s_public_key) = - from_bytes_agreement::(&message[s_enc_start..s_auth_start], &handshake_state.noise_e_secret, noise_se.as_mut()) - { - let mut noise_ck = handshake_state.noise_ck_eseeekem1psk.clone(); - let noise_k_eseeekem1pskse = noise_ck.mix_key_initialize_key(hmac, noise_se.as_ref()); - drop(noise_se); - // Noise process pattern3 payload. - let (is_auth, noise_h_ee1peekem1pskpsp) = decrypt_and_hash::( - sha512, - &noise_k_eseeekem1pskse, - &noise_h_ee1peekem1pskps, - packet_type, - 0, - &mut message[p_enc_start..p_auth_end], - ); - drop(noise_k_eseeekem1pskse); - if !is_auth { - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - // Bob finished Noise XKhfs+psk2 handshake. - let header_send_cipher = Application::PrpEnc::new(handshake_state.header_send_key.as_ref()); - let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(hmac, LABEL_KEX_KEY, &noise_h_ee1peekem1pskpsp); - let mut send_reject = || { - // 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 - // handshake is being dropped, so nonce reuse can't happen. - let (mut fragment, len) = encrypt_control( - &mut Application::AeadEnc::new(kex_key_b2a.as_ref()), - &header_send_cipher, - PACKET_TYPE_SESSION_REJECTED, - INIT_COUNTER, - handshake_state.remote_key_id.get(), - &[], - ); - send_unassociated_reply(&mut fragment[..len]); - }; - - let (responder_disallows_downgrade, responder_silently_rejects) = check_accept_session( - &remote_s_public_key, - &message[p_enc_start..p_auth_start], - handshake_state.ratchet_state.chain_len(), - ); - if let Some((responder_disallows_downgrade, application_data)) = responder_disallows_downgrade { - let result = app.restore_by_identity(&remote_s_public_key, &application_data, current_time); - match result { - Ok(true_ratchet_states) => { - let mut has_match = false; - for rs in &true_ratchet_states { - if !rs.is_null() { - has_match |= &handshake_state.ratchet_state == rs; - } - } - if !has_match { - if !responder_disallows_downgrade && handshake_state.ratchet_state.is_empty() { - // TODO: add some kind of warning callback or signal. - } else { - if !responder_silently_rejects { - send_reject(); - } - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - } - - let mut noise_kk_ss = Secret::new(); - if !self.0.static_keypair.agree(&remote_s_public_key, noise_kk_ss.as_mut()) { - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - let noise_kk_local_init_h = mix_hash(sha512, &INITIAL_H_REKEY, self.0.static_keypair.public_key_bytes()); - let noise_kk_local_init_h = mix_hash(sha512, &noise_kk_local_init_h, remote_s_public_key.as_bytes()); - let noise_kk_remote_init_h = mix_hash(sha512, &INITIAL_H_REKEY, remote_s_public_key.as_bytes()); - let noise_kk_remote_init_h = mix_hash(sha512, &noise_kk_remote_init_h, self.0.static_keypair.public_key_bytes()); - - let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_ee1peekem1pskpsp); - // We must make sure the ratchet key is saved before we transition. - let new_ratchet_state = - RatchetState::new_nonempty(rk, rf, NonZeroU64::new(handshake_state.ratchet_state.chain_len() + 1).unwrap()); - let result = app.save_ratchet_state( - &remote_s_public_key, - &application_data, - [&true_ratchet_states[0], &true_ratchet_states[1]], - [&new_ratchet_state, &RatchetState::Null], - current_time, - ); - if let Err(e) = result { - return Err(ReceiveError::RatchetIoError(e)); - } - - let mut session_queue = self.0.session_queue.lock().unwrap(); - let queue_idx = session_queue.reserve_index(); - let session = Arc::new(Session { - context: Arc::downgrade(&self.0), - queue_idx, - application_data, - remote_static_key: remote_s_public_key, - send_counter: AtomicU64::new(INIT_COUNTER), - session_has_expired: AtomicBool::new(false), - counter_antireplay_window: std::array::from_fn(|_| AtomicU64::new(0)), - state_machine_lock: Mutex::new(()), - state: RwLock::new(SessionMutableState { - ratchet_states: [new_ratchet_state.clone(), RatchetState::Null], - cipher_states: [ - Some(SessionKey::new( - hmac, - noise_ck, - handshake_state.local_key_id, - handshake_state.remote_key_id, - INIT_COUNTER, - true, - )), - None, - ], - current_key: 0, - outgoing_offer: KeyConfirm { - next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), - timeout: current_time.saturating_add(Application::EXPIRATION_TIMEOUT_MS), - }, - }), - header_receive_cipher: Application::PrpDec::new(handshake_state.header_receive_key.as_ref()), - header_send_cipher, - kex_send_cipher: Mutex::new(Some(Application::AeadEnc::new(kex_key_b2a.as_ref()))), - kex_receive_cipher: Mutex::new(Some(Application::AeadDec::new(kex_key_a2b.as_ref()))), - noise_kk_ss, - noise_kk_local_init_h, - noise_kk_remote_init_h, - defrag: std::array::from_fn(|_| Mutex::new(Fragged::new())), - was_bob: true, - }); - let timer = Reverse(current_time.saturating_add(Application::RETRY_INTERVAL_MS)); - session_queue.push_reserved(queue_idx, Arc::downgrade(&session), timer); - drop(session_queue); - // There is the miniscule possibility this key id is already - // in use, in which case we have to drop this session like - // nothing ever happened. - let mut session_map = self.0.session_map.write().unwrap(); - if let std::collections::hash_map::Entry::Vacant(e) = session_map.entry(handshake_state.local_key_id) { - e.insert((Arc::downgrade(&session), false)); - drop(session_map); - let _ = - session.send_control(&session.state.read().unwrap(), send_unassociated_reply, PACKET_TYPE_KEY_CONFIRM, &[]); - - app.event_log(LogEvent::ReceiveValidXK3(&session.application_data), current_time); - return Ok(ReceiveResult::Session(session, SessionEvent::NewSession)); - } else { - // This can occur if we accidentally generate a key id collision. - // There is an extremely short amount of time during which - // another session can steal this session's id, we'll have to - // restart the handshake in this case. - return Err(byzantine_fault!(FaultType::UnknownLocalKeyId, true)); - } - } - Err(e) => { - return Err(ReceiveError::RatchetIoError(e)); - } - } - } else { - if !responder_silently_rejects { - send_reject(); - } - return Ok(ReceiveResult::Rejected); - } - } else { - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - } - _ => return Err(byzantine_fault!(FaultType::InvalidPacket, false)), - } - } - /// Helper function for sending the empty string over the session. Useful for keep-alives. - /// - /// * `session` - The session to send to - /// * `send` - Function to call to send physical packet(s); the buffer passed to `send` is a - /// slice of `data` - /// * `current_time` - Current time in milliseconds - pub fn send_empty(&self, session: &Arc>, send: impl FnMut(&mut [u8]) -> bool, current_time: i64) -> Result<(), SendError> { - self.send(session, send, &mut [0u8; MIN_TRANSPORT_MTU], &[], current_time) - } - /// Send data over the session. - /// - /// * `session` - The session to send to - /// * `send` - Function to call to send physical packet(s); the buffer passed to `send` is a - /// slice of `data` - /// * `mtu_sized_buffer` - A writable work buffer whose size equals the MTU - /// * `data` - Data to send - /// * `current_time` - Current time in milliseconds - pub fn send( - &self, - session: &Arc>, - mut send: impl FnMut(&mut [u8]) -> bool, - mtu_sized_buffer: &mut [u8], - mut data: &[u8], - current_time: i64, - ) -> Result<(), SendError> { - if mtu_sized_buffer.len() < MIN_TRANSPORT_MTU { - return Err(SendError::InvalidParameter); - } - let state = session.state.read().unwrap(); - let key = state.cipher_states[state.current_key].as_ref().ok_or(SendError::SessionNotEstablished)?; - let counter = session.get_next_outgoing_counter()?; - - let mut c = key.get_send_cipher(counter)?; - c.set_iv(&create_message_nonce(PACKET_TYPE_DATA, counter)); - - let fragment_max_chunk_size = mtu_sized_buffer.len() - HEADER_SIZE; - let fragment_count = (data.len() + AES_GCM_TAG_SIZE + (fragment_max_chunk_size - 1)) / fragment_max_chunk_size; - if fragment_count > MAX_FRAGMENTS { - return Err(SendError::DataTooLarge); - } - let last_fragment_no = fragment_count - 1; - - for fragment_no in 0..fragment_count { - let chunk_size = fragment_max_chunk_size.min(data.len()); - let mut fragment_size = chunk_size + HEADER_SIZE; - - set_packet_header( - mtu_sized_buffer, - fragment_count as u8, - fragment_no as u8, - PACKET_TYPE_DATA, - key.remote_key_id.get(), - counter, - ); - - c.encrypt(&data[..chunk_size], &mut mtu_sized_buffer[HEADER_SIZE..fragment_size]); - data = &data[chunk_size..]; - - if fragment_no == last_fragment_no { - debug_assert!(data.is_empty()); - let tagged_fragment_size = fragment_size + AES_GCM_TAG_SIZE; - c.finish_encrypt((&mut mtu_sized_buffer[fragment_size..tagged_fragment_size]).try_into().unwrap()); - fragment_size = tagged_fragment_size; - } - - session.header_send_cipher.encrypt_in_place( - (&mut mtu_sized_buffer[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]) - .try_into() - .unwrap(), - ); - if !send(&mut mtu_sized_buffer[..fragment_size]) { - break; - } - } - drop(c); - if counter >= key.rekey_at_counter { - if let OfferStateMachine::Normal { .. } = &state.outgoing_offer { - drop(state); - if let Ok(timer) = initiate_rekey(&self.0, session, send, current_time) { - self.0.session_queue.lock().unwrap().change_priority(session.queue_idx, Reverse(timer)); - } - } - } - Ok(()) - } - /// Update the challenge window, returning true if the challenge is still valid. - fn check_challenge_window(&self, counter: u64) -> bool { - let slot = &self.0.challenge_antireplay_window[(counter as usize) % self.0.challenge_antireplay_window.len()]; - let counter = counter.wrapping_add(1); - let prev_counter = slot.load(Ordering::Relaxed); - prev_counter < counter - } - /// Update the challenge window, returning true if the challenge is still valid. - fn update_challenge_window(&self, counter: u64) -> bool { - let slot = &self.0.challenge_antireplay_window[(counter as usize) % self.0.challenge_antireplay_window.len()]; - let counter = counter.wrapping_add(1); - let prev_counter = slot.fetch_max(counter, Ordering::Relaxed); - prev_counter < counter - } -} -/// Initiate the rekeying protocol. This session will now begin attempting to rekey this session -/// with its peer, if it was not already. -fn initiate_rekey( - context: &Arc>, - session: &Arc>, - send: impl FnOnce(&mut [u8]) -> bool, - current_time: i64, -) -> Result { - let mut message = [0u8; NoiseKKPattern1or2::SIZE]; - - let kex_lock = session.state_machine_lock.lock().unwrap(); - let state = session.state.read().unwrap(); - // We may only attempt to rekey if we are not already doing so. - match &state.outgoing_offer { - OfferStateMachine::Normal { .. } => (), - _ => return Err(()), - } - let sha512 = &mut Application::Hash::new(); - let hmac = &mut Application::HmacHash::new(); - // Start of Noise KKpsk0 pattern1. - // Noise process pattern1 psk0 token. - let mut noise_ck = SymmetricState::new(INITIAL_H_REKEY); - let noise_temp_h = noise_ck.mix_key_and_hash(hmac, state.ratchet_states[0].key().unwrap()); - let noise_h_psk = mix_hash(sha512, &session.noise_kk_local_init_h, &noise_temp_h); - // Noise process pattern1 e token. - let noise_e_secret = Application::KeyPair::generate(&mut context.rng.lock().unwrap()); - let noise_h_pske = mix_hash(sha512, &noise_h_psk, noise_e_secret.public_key_bytes()); - noise_ck.mix_key(hmac, noise_e_secret.public_key_bytes()); - - let noise_pattern1: &mut NoiseKKPattern1or2 = byte_array_as_proto_buffer_mut(&mut message); - noise_pattern1.noise_e = *noise_e_secret.public_key_bytes(); - // Noise process pattern1 es token. - let mut noise_es = Secret::new(); - if !noise_e_secret.agree(&session.remote_static_key, noise_es.as_mut()) { - return Err(()); - } - noise_ck.mix_key(hmac, noise_es.as_ref()); - drop(noise_es); - // Noise process pattern1 ss token. - let noise_k_pskesss = noise_ck.mix_key_initialize_key(hmac, session.noise_kk_ss.as_ref()); - // Noise process pattern1 payload token. - let mut session_map = context.session_map.write().unwrap(); - let new_key_id = generate_key_id(&session_map, &mut context.rng.lock().unwrap()); - let next_key_index = state.current_key ^ 1; - session_map.insert(new_key_id, (Arc::downgrade(session), next_key_index > 0)); - drop(session_map); - - let noise_pattern1: &mut NoiseKKPattern1or2 = byte_array_as_proto_buffer_mut(&mut message); - noise_pattern1.key_id = new_key_id.get().to_ne_bytes(); - let noise_h_pskep = encrypt_and_hash::( - sha512, - &noise_k_pskesss, - &noise_h_pske, - PACKET_TYPE_NOISE_KK_PATTERN_1, - 0, - &mut message[NoiseKKPattern1or2::ENC_START..NoiseKKPattern1or2::AUTH_END], - ); - drop(noise_k_pskesss); - - drop(state); - let mut state = session.state.write().unwrap(); - state.outgoing_offer = OfferStateMachine::NoiseKKPattern1 { - next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), - timeout: current_time.saturating_add(Application::EXPIRATION_TIMEOUT_MS), - new_key_id, - noise_e_secret, - noise_message: message.clone(), - noise_h_pskep, - noise_ck: noise_ck.clone(), - }; - drop(state); - drop(kex_lock); - let _ = session.send_control(&session.state.read().unwrap(), send, PACKET_TYPE_NOISE_KK_PATTERN_1, &message); - Ok(current_time.saturating_add(Application::RETRY_INTERVAL_MS)) -} -fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mut [u8]) -> bool>( - context: &Context, - session: Arc>, - app: &Application, - mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, - packet_type: u8, - counter: u64, - fragment: &mut [u8], - current_time: i64, -) -> Result, ReceiveError> { - let kex_lock = session.state_machine_lock.lock().unwrap(); - let state = session.state.read().unwrap(); - let mut c = session.kex_receive_cipher.lock().unwrap(); - let message = decrypt_control( - c.as_mut().ok_or(byzantine_fault!(FaultType::OutOfSequence, false))?, - packet_type, - counter, - fragment, - )?; - drop(c); - session.update_receive_window(counter); - use OfferStateMachine::*; - return match packet_type { - PACKET_TYPE_SESSION_REJECTED => { - if let NoiseXKPattern1or3(_) = &state.outgoing_offer { - drop(state); - let mut state = session.state.write().unwrap(); - state.outgoing_offer = OfferStateMachine::Null; - drop(state); - drop(kex_lock); - Ok(ReceiveResult::Session(session, SessionEvent::Rejected)) - } else { - // This can occur naturally because of control packet resends. - Err(byzantine_fault!(FaultType::OutOfSequence, true)) - } - } - PACKET_TYPE_KEY_CONFIRM => { - drop(state); - app.event_log(LogEvent::ReceiveValidKeyConfirm(&session), current_time); - let mut state = session.state.write().unwrap(); - // We only want to stop sending NoiseKKPattern2 offers when the latest derived - // key is confirmed. And we only want to do that once. - let (used_latest_key, try_delete, ret) = match &state.outgoing_offer { - NoiseKKPattern2 { .. } => (true, true, SessionEvent::Control), - NoiseXKPattern1or3(handshake_state) => { - if let NoiseXKAliceHandshakeState::NoiseXKPattern3 { .. } = &handshake_state.offer { - (true, true, SessionEvent::Established) - } else { - (false, false, SessionEvent::Control) - } - } - Null => (false, false, SessionEvent::Control), - _ => (true, false, SessionEvent::Control), - }; - if try_delete { - let result = if !state.ratchet_states[1].is_null() { - app.save_ratchet_state( - &session.remote_static_key, - &session.application_data, - [&state.ratchet_states[0], &state.ratchet_states[1]], - [&state.ratchet_states[0], &RatchetState::Null], - current_time, - ) - } else { - Ok(()) - }; - if let Err(e) = result { - return Err(ReceiveError::RatchetIoError(e)); - } - if let NoiseKKPattern2 { kex_send_key, .. } = &state.outgoing_offer { - session - .kex_send_cipher - .lock() - .unwrap() - .replace(Application::AeadEnc::new(kex_send_key.as_ref())); - } - state.ratchet_states[1] = RatchetState::Null; - state.current_key ^= 1; - state.outgoing_offer = new_normal_state(context.0.rng.lock().unwrap().next_u64(), current_time); - } - drop(state); - drop(kex_lock); - if used_latest_key { - if let Some((send, _)) = send_to(&session) { - let _ = session.send_control(&session.state.read().unwrap(), send, PACKET_TYPE_ACK, &[]); - } - } - Ok(ReceiveResult::Session(session, ret)) - } - PACKET_TYPE_ACK => { - if let KeyConfirm { .. } = &state.outgoing_offer { - drop(state); - app.event_log(LogEvent::ReceiveValidAck(&session), current_time); - let mut state = session.state.write().unwrap(); - // Check if we should end any current offers and transition back to Normal state - state.outgoing_offer = new_normal_state(context.0.rng.lock().unwrap().next_u64(), current_time); - drop(kex_lock); - drop(state); - Ok(ReceiveResult::Session(session, SessionEvent::Control)) - } else { - // This can occur naturally because of control packet resends. - Err(byzantine_fault!(FaultType::OutOfSequence, true)) - } - } - PACKET_TYPE_NOISE_KK_PATTERN_1 => { - app.event_log(LogEvent::ReceiveUncheckedKK1, current_time); - let message = &mut message[..NoiseKKPattern1or2::SIZE]; - let noise_pattern1: &NoiseKKPattern1or2 = byte_array_as_proto_buffer(message); - // We need the following operation to be atomic with the change of offer type - let (should_rekey_as_bob, chosen_id) = match &state.outgoing_offer { - // Check rekey rate limits. - Normal { .. } => (true, None), - // In the following situation, both parties are in state NoiseKKPattern1, - // we need to deterministically allow only one of them to transition to - // NoiseKKPattern2. - NoiseKKPattern1 { new_key_id, .. } => (session.was_bob, Some(*new_key_id)), - _ => (false, None), - }; - if !should_rekey_as_bob { - // This can be triggered if both parties attempt rekeying simultaneously, or if the - // remote party sent us a duplicate rekey request. - // The code above handles this case and only lets one party through to rekeying. - drop(state); - drop(kex_lock); - return Ok(ReceiveResult::Session(session, SessionEvent::Control)); - } - // Noise process pattern1 psk0 token. - let sha512 = &mut Application::Hash::new(); - let hmac = &mut Application::HmacHash::new(); - let mut noise_ck = SymmetricState::new(INITIAL_H_REKEY); - let noise_temp_h = noise_ck.mix_key_and_hash(hmac, state.ratchet_states[0].key().unwrap()); - let noise_h_psk = mix_hash(sha512, &session.noise_kk_remote_init_h, &noise_temp_h); - // Noise process pattern1 e token. - // Get public key validation out of the way early - let mut noise_es = Secret::new(); - let mut noise_ee = Secret::new(); - let mut noise_se = Secret::new(); - if let Some(alice_e) = from_bytes_agreement::(&noise_pattern1.noise_e, &context.0.static_keypair, noise_es.as_mut()) { - let bob_e_secret = Application::KeyPair::generate(&mut context.0.rng.lock().unwrap()); - if bob_e_secret.agree(&alice_e, noise_ee.as_mut()) && bob_e_secret.agree(&session.remote_static_key, noise_se.as_mut()) { - let noise_h_pske = mix_hash(sha512, &noise_h_psk, alice_e.as_bytes()); - noise_ck.mix_key(hmac, alice_e.as_bytes()); - // Noise process pattern1 es token. - noise_ck.mix_key(hmac, noise_es.as_ref()); - drop(noise_es); - // Noise process pattern1 ss token. - let noise_k_pskesss = noise_ck.mix_key_initialize_key(hmac, session.noise_kk_ss.as_ref()); - - // Noise process pattern1 payload. - let (is_auth, noise_h_pskep) = decrypt_and_hash::( - sha512, - &noise_k_pskesss, - &noise_h_pske, - packet_type, - 0, - &mut message[NoiseKKPattern1or2::ENC_START..NoiseKKPattern1or2::AUTH_END], - ); - let noise_pattern1: &NoiseKKPattern1or2 = byte_array_as_proto_buffer(message); - if let (true, Some(remote_key_id)) = (is_auth, NonZeroU32::new(u32::from_ne_bytes(noise_pattern1.key_id))) { - // Alice fully authenticated. - // Start of Noise KKpsk0 pattern2. - // Noise process pattern2 e token. - let noise_h_pskepe = mix_hash(sha512, &noise_h_pskep, bob_e_secret.public_key_bytes()); - noise_ck.mix_key(hmac, bob_e_secret.public_key_bytes()); - // Noise process pattern2 ee token. - noise_ck.mix_key(hmac, noise_ee.as_ref()); - drop(noise_ee); - // Noise process pattern2 se token. - let noise_k_pskessseese = noise_ck.mix_key_initialize_key(hmac, noise_se.as_ref()); - drop(noise_se); - // Noise process pattern2 payload. - let mut message2 = [0u8; NoiseKKPattern1or2::SIZE]; - let noise_pattern2: &mut NoiseKKPattern1or2 = byte_array_as_proto_buffer_mut(&mut message2); - noise_pattern2.noise_e = *bob_e_secret.public_key_bytes(); - let mut session_map = context.0.session_map.write().unwrap(); - // If we already generated a new key id mapping reuse it. - let new_key_id = chosen_id.unwrap_or_else(|| generate_key_id(&session_map, &mut context.0.rng.lock().unwrap())); - noise_pattern2.key_id = new_key_id.get().to_ne_bytes(); - - let noise_h_pskepep = encrypt_and_hash::( - sha512, - &noise_k_pskessseese, - &noise_h_pskepe, - PACKET_TYPE_NOISE_KK_PATTERN_2, - 0, - &mut message2[NoiseKKPattern1or2::ENC_START..NoiseKKPattern1or2::AUTH_END], - ); - drop(noise_k_pskessseese); - // Bob finished Noise KKpsk0 handshake. - let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_pskepep); - let new_ratchet_state = RatchetState::new_nonempty(rk, rf, NonZeroU64::new(state.ratchet_states[0].chain_len() + 1).unwrap()); - let result = app.save_ratchet_state( - &session.remote_static_key, - &session.application_data, - [&state.ratchet_states[0], &state.ratchet_states[1]], - [&new_ratchet_state, &state.ratchet_states[0]], - current_time, - ); - if let Err(e) = result { - drop(state); - drop(kex_lock); - return Err(ReceiveError::RatchetIoError(e)); - } - let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(hmac, LABEL_KEX_KEY, &noise_h_pskepep); - // The new "Bob" doesn't know yet if Alice has received the new key, so the - // new key is recorded as the "alt" (key_index ^ 1) but the current key is - // not advanced yet. - let next_key_index = state.current_key ^ 1; - session_map.insert(new_key_id, (Arc::downgrade(&session), next_key_index > 0)); - if let Some(pre_id) = state.cipher_states[next_key_index].as_ref().map(|k| k.local_key_id) { - session_map.remove(&pre_id); - } - drop(session_map); - drop(state); - let mut state = session.state.write().unwrap(); - let current_counter = session.send_counter.load(Ordering::Relaxed); - session - .kex_receive_cipher - .lock() - .unwrap() - .replace(Application::AeadDec::new(kex_key_a2b.as_ref())); - state.ratchet_states[1] = state.ratchet_states[0].clone(); - state.ratchet_states[0] = new_ratchet_state.clone(); - - state.cipher_states[next_key_index].replace(SessionKey::new( - hmac, - noise_ck, - new_key_id, - remote_key_id, - current_counter, - true, - )); - let timer = current_time.saturating_add(Application::RETRY_INTERVAL_MS); - state.outgoing_offer = NoiseKKPattern2 { - next_retry_time: AtomicI64::new(timer), - timeout: current_time.saturating_add(Application::EXPIRATION_TIMEOUT_MS), - noise_message: message2, - kex_send_key: kex_key_b2a.clone(), - }; - drop(state); - drop(kex_lock); - context.0.session_queue.lock().unwrap().change_priority(session.queue_idx, Reverse(timer)); - - if let Some((send, _)) = send_to(&session) { - let _ = session.send_control(&session.state.read().unwrap(), send, PACKET_TYPE_NOISE_KK_PATTERN_2, &message2); - } - app.event_log(LogEvent::ReceiveValidKK1(&session), current_time); - return Ok(ReceiveResult::Session(session, SessionEvent::Control)); - } - } - } - Err(byzantine_fault!(FaultType::FailedAuthentication, false)) - } - PACKET_TYPE_NOISE_KK_PATTERN_2 => { - app.event_log(LogEvent::ReceiveUncheckedKK2, current_time); - let message = &mut message[..NoiseKKPattern1or2::SIZE]; - let noise_pattern2: &NoiseKKPattern1or2 = byte_array_as_proto_buffer(message); - - if let NoiseKKPattern1 { new_key_id, noise_e_secret, noise_ck, noise_h_pskep, .. } = &state.outgoing_offer { - // Noise process pattern2 e token. - let mut noise_ee = Secret::new(); - let mut noise_se = Secret::new(); - if let Some(bob_e) = from_bytes_agreement::(&noise_pattern2.noise_e, noise_e_secret, noise_ee.as_mut()) { - if context.0.static_keypair.agree(&bob_e, noise_se.as_mut()) { - let sha512 = &mut Application::Hash::new(); - let hmac = &mut Application::HmacHash::new(); - let mut noise_ck = noise_ck.clone(); - let noise_h_pskepe = mix_hash(sha512, noise_h_pskep, bob_e.as_bytes()); - noise_ck.mix_key(hmac, bob_e.as_bytes()); - // Noise process pattern2 ee token. - noise_ck.mix_key(hmac, noise_ee.as_ref()); - drop(noise_ee); - // Noise process pattern2 se token. - let noise_k_pskessseese = noise_ck.mix_key_initialize_key(hmac, noise_se.as_ref()); - drop(noise_se); - // Noise process pattern2 payload. - let (is_auth, noise_h_pskepep) = decrypt_and_hash::( - sha512, - &noise_k_pskessseese, - &noise_h_pskepe, - packet_type, - 0, - &mut message[NoiseKKPattern1or2::ENC_START..NoiseKKPattern1or2::AUTH_END], - ); - let noise_pattern2: &NoiseKKPattern1or2 = byte_array_as_proto_buffer(message); - if let (true, Some(remote_key_id)) = (is_auth, NonZeroU32::new(u32::from_ne_bytes(noise_pattern2.key_id))) { - // Bob fully authenticated. - // Alice finished Noise KKpsk0 handshake. - let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_pskepep); - let new_ratchet_state = - RatchetState::new_nonempty(rk, rf, NonZeroU64::new(state.ratchet_states[0].chain_len() + 1).unwrap()); - let result = app.save_ratchet_state( - &session.remote_static_key, - &session.application_data, - [&state.ratchet_states[0], &state.ratchet_states[1]], - [&new_ratchet_state, &RatchetState::Null], - current_time, - ); - if let Err(e) = result { - drop(state); - drop(kex_lock); - return Err(ReceiveError::RatchetIoError(e)); - } - let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(hmac, LABEL_KEX_KEY, &noise_h_pskepep); - - let new_key_id = *new_key_id; - drop(state); - let mut state = session.state.write().unwrap(); - let next_key_index = state.current_key ^ 1; - state.current_key = next_key_index; - if let Some(key) = state.cipher_states[next_key_index].as_ref() { - context.0.session_map.write().unwrap().remove(&key.local_key_id); - } - session - .kex_receive_cipher - .lock() - .unwrap() - .replace(Application::AeadDec::new(kex_key_b2a.as_ref())); - session - .kex_send_cipher - .lock() - .unwrap() - .replace(Application::AeadEnc::new(kex_key_a2b.as_ref())); - state.ratchet_states[1] = RatchetState::Null; - state.ratchet_states[0] = new_ratchet_state.clone(); - - state.cipher_states[next_key_index].replace(SessionKey::new( - hmac, - noise_ck, - new_key_id, - remote_key_id, - session.send_counter.load(Ordering::Relaxed), - false, - )); - state.outgoing_offer = KeyConfirm { - next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), - timeout: current_time.saturating_add(Application::EXPIRATION_TIMEOUT_MS), - }; - drop(state); - drop(kex_lock); - // Let Bob know we got the key. - if let Some((send, _)) = send_to(&session) { - let _ = session.send_control(&session.state.read().unwrap(), send, PACKET_TYPE_KEY_CONFIRM, &[]); - } - app.event_log(LogEvent::ReceiveValidKK2(&session), current_time); - return Ok(ReceiveResult::Session(session, SessionEvent::Control)); - } - } - } - // Bob failed authentication so according to Noise we must terminate this - // handshake. - // This should not happen in practice since this packet will have already passed - // authentication under the current key. - session.expire(); - Err(byzantine_fault!(FaultType::FailedAuthentication, false)) - } else { - drop(state); - drop(kex_lock); - Ok(ReceiveResult::Session(session, SessionEvent::Control)) - } - } - _ => Err(byzantine_fault!(FaultType::InvalidPacket, false)), - }; -} - -impl Session { - /// This can only fail with `MaxKeyLifetimeExceeded` or `SessionNotEstablished`. - fn send_control( - &self, - state: &SessionMutableState, - send: impl FnOnce(&mut [u8]) -> bool, - packet_type: u8, - packet: &[u8], - ) -> Result<(), SendError> { - let key = state.cipher_states[state.current_key].as_ref().ok_or(SendError::SessionNotEstablished)?; - let counter = self.get_next_outgoing_counter()?; - let mut c = self.kex_send_cipher.lock().unwrap(); - let (mut fragment, len) = encrypt_control( - c.as_mut().ok_or(SendError::SessionNotEstablished)?, - &self.header_send_cipher, - packet_type, - counter, - key.remote_key_id.get(), - packet, - ); - send(&mut fragment[..len]); - Ok(()) - } - /// Check whether this session is established. - pub fn established(&self) -> bool { - let state = self.state.read().unwrap(); - !matches!(&state.outgoing_offer, OfferStateMachine::NoiseXKPattern1or3(_) | OfferStateMachine::Null) - } - /// The static public key of the remote peer. - pub fn remote_s_public_key(&self) -> &Application::PublicKey { - &self.remote_static_key - } - /// The current ratchet state of this session. - /// The returned values are sensitive and should be securely erased before being dropped. - pub fn ratchet_states(&self) -> [RatchetState; 2] { - let state = self.state.read().unwrap(); - state.ratchet_states.clone() - } - /// The current ratchet count of this session. - pub fn ratchet_count(&self) -> u64 { - self.state.read().unwrap().ratchet_states[0].chain_len() - } - /// 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. - pub fn expire(&self) { - if let Some(context) = self.context.upgrade() { - self.expire_inner(&context, &mut context.session_queue.lock().unwrap()); - } - } - fn expire_inner( - &self, - context: &Arc>, - session_queue: &mut IndexedBinaryHeap>, Reverse>, - ) { - // Prevent this session from being updated. - session_queue.remove(self.queue_idx); - self.session_has_expired.store(true, Ordering::Relaxed); - let _kex_lock = self.state_machine_lock.lock().unwrap(); - let mut state = self.state.write().unwrap(); - let mut session_map = context.session_map.write().unwrap(); - for key in &state.cipher_states { - if let Some(pre_id) = key.as_ref().map(|k| k.local_key_id) { - session_map.remove(&pre_id); - } - } - use OfferStateMachine::*; - match &state.outgoing_offer { - NoiseXKPattern1or3(handshake_state) => session_map.remove(&handshake_state.local_key_id), - NoiseKKPattern1 { new_key_id, .. } => session_map.remove(new_key_id), - _ => None, - }; - state.outgoing_offer = OfferStateMachine::Null; - } - - /// Get the next outgoing counter value. - fn get_next_outgoing_counter(&self) -> Result { - if self.session_has_expired.load(Ordering::Relaxed) { - Err(SendError::SessionExpired) - } else { - let counter = self.send_counter.fetch_add(1, Ordering::Relaxed); - if counter > THREAD_SAFE_COUNTER_HARD_EXPIRE { - // Because this thread sets the flag itself it will never be able to increment the - // counter again. - // For that reason the other atomic orderings can be `Relaxed`. - self.session_has_expired.store(true, Ordering::SeqCst) - } - Ok(counter) - } - } - /// Check the receive window without mutating state. - fn check_receive_window(&self, counter: u64) -> bool { - let slot = &self.counter_antireplay_window[(counter as usize) % self.counter_antireplay_window.len()]; - let counter = counter.wrapping_add(1); - let prev_counter = slot.load(Ordering::Relaxed); - prev_counter < counter && counter.wrapping_sub(prev_counter) <= COUNTER_WINDOW_MAX_SKIP_AHEAD - } - /// Update the receive window, returning true if the packet is still valid. - /// This should only be called after the packet is authenticated. - fn update_receive_window(&self, counter: u64) -> bool { - let slot = &self.counter_antireplay_window[(counter as usize) % self.counter_antireplay_window.len()]; - let counter = counter.wrapping_add(1); - let prev_counter = slot.fetch_max(counter, Ordering::Relaxed); - prev_counter < counter && counter.wrapping_sub(prev_counter) <= COUNTER_WINDOW_MAX_SKIP_AHEAD - } -} -impl Drop for Session { - fn drop(&mut self) { - if let Some(context) = self.context.upgrade() { - self.expire_inner(&context, &mut context.session_queue.lock().unwrap()); - } - } -} - -impl NoiseXKAliceHandshake { - /// Can only fail with `OpenError::InvalidPublicKey` because of remote_s_public_key. - /// Corresponds to Noise `Initialize`. - fn initialize( - local_key_id: NonZeroU32, - remote_s_public_key: &Application::PublicKey, - ratchet_state: &[RatchetState; 2], - rng: &mut Application::Rng, - ) -> Result< - ( - NoiseXKAliceHandshakeState, - Secret, - Secret, - ), - OpenError, - > { - let mut message = [0u8; NoiseXKPattern1::MAX_SIZE]; - let sha512 = &mut Application::Hash::new(); - let hmac = &mut Application::HmacHash::new(); - // Start of Noise XKhfs+psk2 pattern1. - let noise_pattern1: &mut NoiseXKPattern1 = byte_array_as_proto_buffer_mut(&mut message); - let noise_e_secret = Application::KeyPair::generate(rng); - let noise_e1_secret = pqc_kyber::keypair(rng); - noise_pattern1.alice_key_id = local_key_id.get().to_ne_bytes(); - noise_pattern1.noise_e = *noise_e_secret.public_key_bytes(); - noise_pattern1.noise_e1 = noise_e1_secret.public; - // Noise process prologue. - let noise_h = mix_hash( - sha512, - &INITIAL_H, - &message[NoiseXKPattern1::PROLOGUE_START..NoiseXKPattern1::PROLOGUE_END], - ); - let noise_h = mix_hash(sha512, &noise_h, remote_s_public_key.as_bytes()); - // Noise process pattern1 e token. - let mut noise_ck = SymmetricState::new(INITIAL_H); - let noise_h_e = mix_hash(sha512, &noise_h, noise_e_secret.public_key_bytes()); - noise_ck.mix_key(hmac, noise_e_secret.public_key_bytes()); - // Noise process pattern1 es token. - let mut noise_es = Secret::new(); - if !noise_e_secret.agree(remote_s_public_key, noise_es.as_mut()) { - return Err(OpenError::InvalidPublicKey); - } - let noise_k_es = noise_ck.mix_key_initialize_key(hmac, noise_es.as_ref()); - drop(noise_es); - // Noise process pattern1 e1 token. - let noise_h_ee1 = encrypt_and_hash::( - sha512, - &noise_k_es, - &noise_h_e, - PACKET_TYPE_NOISE_XK_PATTERN_1, - 0, - &mut message[NoiseXKPattern1::E1_ENC_START..NoiseXKPattern1::P_ENC_START], - ); - // Noise process pattern1 payload. - let noise_pattern1: &mut NoiseXKPattern1 = byte_array_as_proto_buffer_mut(&mut message); - let mut idx = 0; - for rs in ratchet_state { - if let Some(rf) = rs.fingerprint() { - let next_idx = idx + RATCHET_SIZE; - noise_pattern1.payload[idx..next_idx].copy_from_slice(rf); - idx = next_idx; - } - } - let p_auth_end = NoiseXKPattern1::P_ENC_START + idx + AES_GCM_TAG_SIZE; - let noise_message_len = p_auth_end + ChallengeResponse::SIZE; - - let noise_h_ee1p = encrypt_and_hash::( - sha512, - &noise_k_es, - &noise_h_ee1, - PACKET_TYPE_NOISE_XK_PATTERN_1, - 1, - &mut message[NoiseXKPattern1::P_ENC_START..p_auth_end], - ); - drop(noise_k_es); - let (header_b2a_key, header_a2b_key) = noise_ck.get_ask2(hmac, LABEL_HEADER_KEY, &noise_h_ee1p); - let message_id = u64::from_be_bytes(message[p_auth_end - 8..p_auth_end].try_into().unwrap()); - - message[noise_message_len - CHALLENGE_POW_SIZE..noise_message_len].copy_from_slice(&rng.next_u64().to_ne_bytes()); - Ok(( - NoiseXKAliceHandshakeState::NoiseXKPattern1 { - noise_h_ee1p, - noise_e_secret, - noise_e1_secret: Secret(noise_e1_secret.secret), - noise_ck_es: noise_ck, - noise_message_len, - noise_message: message, - message_id, - }, - header_a2b_key, - header_b2a_key, - )) - } - /// Should not fail unless Bob's public key is adversarial. - fn reinitialize( - &mut self, - session: &Arc>, - ratchet_state: &[RatchetState; 2], - session_map: &mut HashMap>, bool)>, - rng: &mut Application::Rng, - current_time: i64, - ) -> bool { - let local_key_id = generate_key_id(session_map, rng); - if let Ok((offer, a2b_header_key, b2a_header_key)) = Self::initialize(local_key_id, &session.remote_static_key, ratchet_state, rng) { - self.timeout = current_time.saturating_add(Application::INITIAL_OFFER_TIMEOUT_MS); - session_map.remove(&self.local_key_id); - session_map.insert(local_key_id, (Arc::downgrade(session), false)); - self.local_key_id = local_key_id; - self.offer = offer; - session.header_send_cipher.reset(a2b_header_key.as_ref()); - session.header_receive_cipher.reset(b2a_header_key.as_ref()); - true - } else { - false - } - } -} - -/// Create the normal state of the offer state machine, with the correct timestamps. -fn new_normal_state(rand: u64, current_time: i64) -> OfferStateMachine { - OfferStateMachine::Normal { - timeout: current_time - .saturating_add(Application::REKEY_AFTER_TIME_MS) - .saturating_sub(rand as i64 % Application::REKEY_AFTER_TIME_MAX_JITTER_MS), - } -} -/// Get a timestamp of when this timer should trigger next, or None if it should trigger now. -fn process_timer(timer: &AtomicI64, wait_time: i64, current_time: i64) -> Option { - let ts = timer.load(Ordering::Relaxed); - if ts <= current_time && timer.fetch_max(ts.saturating_add(wait_time), Ordering::Relaxed) == ts { - None - } else { - Some(ts) - } -} - -/// Corresponds to Noise `EncryptAndHash`. -fn encrypt_and_hash( - sha512: &mut Application::Hash, - noise_k: &Secret, - noise_h: &[u8; HASHLEN], - packet_type: u8, - noise_k_uses: u64, - message: &mut [u8], -) -> [u8; HASHLEN] { - let auth_start = message.len() - AES_GCM_TAG_SIZE; - let mut gcm = Application::AeadEnc::new(noise_k.as_ref()); - // Encrypt and add authentication tag. - gcm.set_iv(&create_message_nonce(packet_type, INIT_COUNTER + noise_k_uses)); - gcm.set_aad(noise_h); - if auth_start > 0 { - gcm.encrypt_in_place(&mut message[..auth_start]); - } - gcm.finish_encrypt((&mut message[auth_start..]).try_into().unwrap()); - mix_hash(sha512, noise_h, message) -} -/// Corresponds to Noise `DecryptAndHash`. -fn decrypt_and_hash( - sha512: &mut Application::Hash, - noise_k: &Secret, - noise_h: &[u8; HASHLEN], - packet_type: u8, - noise_k_uses: u64, - message: &mut [u8], -) -> (bool, [u8; HASHLEN]) { - let auth_start = message.len() - AES_GCM_TAG_SIZE; - let noise_h_c = mix_hash(sha512, noise_h, message); - let mut gcm = Application::AeadDec::new(noise_k.as_ref()); - gcm.set_iv(&create_message_nonce(packet_type, INIT_COUNTER + noise_k_uses)); - gcm.set_aad(noise_h); - if auth_start > 0 { - gcm.decrypt_in_place(&mut message[..auth_start]); - } - (gcm.finish_decrypt((&message[auth_start..]).try_into().unwrap()), noise_h_c) -} -/// Encrypt a standardized control packet. -fn encrypt_control( - c: &mut impl AesGcmEnc, - header_cipher: &impl AesEnc, - packet_type: u8, - counter: u64, - remote_key_id: u32, - packet: &[u8], -) -> ([u8; CONTROL_PACKET_MAX_SIZE], usize) { - let mut fragment = [0u8; CONTROL_PACKET_MAX_SIZE]; - let fragment_len = packet.len() + HEADER_SIZE + AES_GCM_TAG_SIZE; - - c.set_iv(&create_message_nonce(packet_type, counter)); - if !packet.is_empty() { - fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE].copy_from_slice(packet); - c.encrypt_in_place(&mut fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE]); - } - c.finish_encrypt((&mut fragment[fragment_len - AES_GCM_TAG_SIZE..fragment_len]).try_into().unwrap()); - set_packet_header(&mut fragment, 1, 0, packet_type, remote_key_id, counter); - header_cipher.encrypt_in_place((&mut fragment[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]).try_into().unwrap()); - (fragment, fragment_len) -} -fn decrypt_control<'a, IoError>( - c: &mut impl AesGcmDec, - packet_type: u8, - counter: u64, - fragment: &'a mut [u8], -) -> Result<&'a mut [u8], ReceiveError> { - let fragment_len = fragment.len(); - if !(CONTROL_PACKET_MIN_SIZE..=CONTROL_PACKET_MAX_SIZE).contains(&fragment_len) { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - c.set_iv(&create_message_nonce(packet_type, counter)); - c.decrypt_in_place(&mut fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE]); - if !c.finish_decrypt((&fragment[fragment_len - AES_GCM_TAG_SIZE..fragment_len]).try_into().unwrap()) { - // This can occur naturally if one of the remote peers resent a - // control packet that got delayed and arrived out of order. - return Err(byzantine_fault!(FaultType::FailedAuthentication, true)); - } - Ok(&mut fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE]) -} - -fn set_packet_header(packet: &mut [u8], fragment_count: u8, fragment_no: u8, packet_type: u8, remote_key_id: u32, counter_or_id: u64) { - debug_assert!(packet.len() >= MIN_PACKET_SIZE); - debug_assert!(fragment_count > 0); - debug_assert!(fragment_count <= MAX_FRAGMENTS as u8); - debug_assert!(fragment_no < MAX_FRAGMENTS as u8); - debug_assert_eq!((packet_type << 1) >> 1, packet_type); - // [0..4] recipient key id - // -- start AES(ck_es * h_e_e1_p) encrypted block -- - // [4] fragment count (1..255) - // [5] fragment number (0..254) - // [6] reserved zero - // -- start of AES-GCM Nonce -- - // [7] packet type - // [8..16] 64-bit counter or packet id (big endian) - packet[4..16].copy_from_slice(&create_message_nonce(packet_type, counter_or_id)); - packet[0..4].copy_from_slice(&remote_key_id.to_ne_bytes()); - packet[4] = fragment_count; - packet[5] = fragment_no; - packet[6] = 0; -} -/// 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. -fn create_message_nonce(packet_type: u8, counter: u64) -> [u8; AES_GCM_IV_SIZE] { - let mut ret = [0u8; AES_GCM_IV_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 -} -/// returns `(fragment_count, fragment_no, packet_type, counter, header_nonce)`. -fn parse_packet_header(packet: &[u8]) -> (u8, u8, u8, u64, [u8; 10]) { - let header_nonce = packet[6..16].try_into().unwrap(); - let counter = packet[8..16].try_into().unwrap(); - // We intentionally ignore the version number for future revisions. - (packet[4], packet[5], packet[7], u64::from_be_bytes(counter), header_nonce) -} - -/// Break a packet into fragments and send them all. -/// -/// The contents of packet[] are mangled during this operation, so it should be discarded after. -/// This is only used for key exchange and control packets. For data packets this is done inline -/// for better performance with encryption and fragmentation happening at the same time. -fn send_with_fragmentation( - send: &mut impl FnMut(&mut [u8]) -> bool, - mtu: usize, - packet: &mut [u8], - packet_type: u8, - remote_key_id: Option, - counter_or_id: u64, - header_cipher: Option<&impl AesEnc>, -) -> bool { - let packet_len = packet.len(); - let fragment_count = (packet_len.saturating_add(mtu - 1)) / mtu; // integer ceiling divide - debug_assert!(fragment_count <= MAX_FRAGMENTS); - let mut fragment_start = 0; - let mut fragment_end = packet_len.min(mtu); - let mut fragment_no = 0; - loop { - let fragment = &mut packet[fragment_start..fragment_end]; - set_packet_header( - fragment, - fragment_count as u8, - fragment_no as u8, - packet_type, - remote_key_id.map_or(0, |n| n.get()), - counter_or_id, - ); - if let Some(hcc) = header_cipher { - hcc.encrypt_in_place((&mut fragment[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]).try_into().unwrap()); - } - if !send(fragment) { - return false; - } - fragment_no += 1; - if fragment_no < fragment_count { - fragment_start = fragment_end - HEADER_SIZE; - fragment_end = (fragment_start.saturating_add(mtu)).min(packet_len); - } else { - break; - } - } - true -} - -/// Assemble a series of fragments into a buffer and return the length of the assembled packet in -/// bytes. -/// -/// This is also only used for key exchange and control packets. For data packets decryption and -/// assembly happen in one pass for better performance. -fn assemble_fragments_into(fragments: &[A::IncomingPacketBuffer], d: &mut [u8]) -> Result> { - let mut l = 0; - for i in 0..fragments.len() { - let mut ff = fragments[i].as_ref(); - if i > 0 { - ff = &ff[HEADER_SIZE..]; - } - let j = l + ff.len(); - if j > d.len() { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - d[l..j].copy_from_slice(ff); - l = j; - } - Ok(l) -} -/// Generate a random local key id that is currently unused. -fn generate_key_id( - session_map: &HashMap>, bool)>, - rng: &mut Application::Rng, -) -> NonZeroU32 { - loop { - if let Some(local_key_id) = NonZeroU32::new(rng.next_u32()) { - if !session_map.contains_key(&local_key_id) { - return local_key_id; - } - } - } -} - -impl SessionKey { - fn new( - hmac: &mut Application::HmacHash, - ck: SymmetricState, - local_key_id: NonZeroU32, - remote_key_id: NonZeroU32, - current_counter: u64, - is_bob: bool, - ) -> Self { - let (b2a, a2b) = ck.split(hmac); - let (receive_key, send_key) = if is_bob { - (&a2b, &b2a) - } else { - (&b2a, &a2b) - }; - let send_cipher_pool = std::array::from_fn(|_| Mutex::new(Application::AeadEnc::new(send_key.as_ref()))); - let receive_cipher_pool = std::array::from_fn(|_| Mutex::new(Application::AeadDec::new(receive_key.as_ref()))); - Self { - local_key_id, - remote_key_id, - send_cipher_pool, - receive_cipher_pool, - rekey_at_counter: current_counter.saturating_add(Application::REKEY_AFTER_USES), - expire_at_counter: current_counter.saturating_add(Application::EXPIRE_AFTER_USES), - } - } - - fn get_send_cipher(&self, counter: u64) -> Result, SendError> { - if counter < self.expire_at_counter { - Ok(self.send_cipher_pool[(counter as usize) % self.send_cipher_pool.len()].lock().unwrap()) - } else { - Err(SendError::SessionExpired) - } - } - - fn get_receive_cipher(&self, counter: u64) -> MutexGuard { - let idx = (counter as usize) % self.receive_cipher_pool.len(); - self.receive_cipher_pool[idx].lock().unwrap() - } -} - -/// MixHash to update 'h' during negotiation. -fn mix_hash(hasher: &mut impl Sha512, h: &[u8; HASHLEN], m: &[u8]) -> [u8; HASHLEN] { - let mut output = [0u8; HASHLEN]; - hasher.reset(); - hasher.update(h); - hasher.update(m); - hasher.finish(&mut output); - output -} -/// Check if the proof of work attached to the first message contains the correct number of leading -/// zeros. -fn verify_pow(hasher: &mut Application::Hash, response: &[u8]) -> bool { - if Application::PROOF_OF_WORK_BIT_DIFFICULTY == 0 { - return true; - } - hasher.reset(); - hasher.update(response); - let mut output = [0u8; HASHLEN]; - hasher.finish(&mut output); - let n = u32::from_be_bytes(output[..4].try_into().unwrap()); - n.leading_zeros() >= Application::PROOF_OF_WORK_BIT_DIFFICULTY -} -fn from_bytes_agreement( - public: &[u8], - private: &Application::KeyPair, - output: &mut [u8; P384_ECDH_SHARED_SECRET_SIZE], -) -> Option { - Application::PublicKey::from_bytes(public.try_into().unwrap()).and_then(|e| private.agree(&e, output).then_some(e)) -} diff --git a/src/zssp.rs b/src/zssp.rs index b0ba9f8..ff7eed5 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -21,11 +21,8 @@ use arrayvec::ArrayVec; use zeroize::Zeroizing; use crate::challenge::ChallengeContext; -use crate::crypto::aes::{AesDec, AesEnc, AES_256_KEY_SIZE, AES_GCM_IV_SIZE, AES_GCM_TAG_SIZE}; -use crate::crypto::p384::{P384KeyPair, P384PublicKey, P384_ECDH_SHARED_SECRET_SIZE, P384_PUBLIC_KEY_SIZE}; -use crate::crypto::pqc_kyber::KYBER_SECRETKEYBYTES; -use crate::crypto::rand_core::RngCore; -use crate::crypto::sha512::{HashSha512, HmacSha512}; +use crate::crypto::*; +use rand_core::RngCore; use crate::zeta::*; use crate::frag_cache::UnassociatedFragCache; @@ -36,7 +33,7 @@ use crate::log_event::LogEvent; use crate::proto::*; use crate::result::{byzantine_fault, FaultType, OpenError, ReceiveError, ReceiveOk, SendError, SessionEvent}; use crate::symmetric_state::SymmetricState; -use crate::{applicationlayer::*, ratchet_state::RatchetState}; +use crate::application::*; /// Macro to turn off logging at compile time. macro_rules! log { From d4c07cd7a3271511f3c54890dd6c98ed807f9d8f Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 8 Aug 2023 09:20:16 -0400 Subject: [PATCH 21/50] reorganized --- src/application.rs | 12 +- src/challenge.rs | 2 +- src/handshake_cache.rs | 2 +- src/log_event.rs | 2 +- src/result.rs | 1 + src/symmetric_state.rs | 2 +- src/zeta.rs | 412 +++++++++++++++++++++-------------------- src/zssp.rs | 124 +++++++------ 8 files changed, 293 insertions(+), 264 deletions(-) diff --git a/src/application.rs b/src/application.rs index 0a28af9..705cdad 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,9 +1,9 @@ -use std::sync::Arc; use rand_core::{CryptoRng, RngCore}; +use std::sync::Arc; use crate::crypto::*; -use crate::zeta::Session; use crate::ratchet_state::RatchetState; +use crate::zeta::Session; pub use crate::proto::RATCHET_SIZE; pub use crate::ratchet_state::*; @@ -156,6 +156,7 @@ pub trait ApplicationLayer: Sized { /// should rekey. fn time(&self) -> i64; + fn incoming_session(&self) -> IncomingSessionAction; /// 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 /// then on they should be using non-empty ratchet states. @@ -249,6 +250,13 @@ pub trait ApplicationLayer: Sized { fn event_log(&self, event: LogEvent<'_, Self>); } +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum IncomingSessionAction { + Allow, + Challenge, + Drop, +} + /// 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/challenge.rs b/src/challenge.rs index c63c916..cc4de21 100644 --- a/src/challenge.rs +++ b/src/challenge.rs @@ -9,7 +9,7 @@ use crate::proto::*; pub struct ChallengeContext { counter: AtomicU64, - antireplay_window: Window, + antireplay_window: Window, salt: [u8; SALT_SIZE], } diff --git a/src/handshake_cache.rs b/src/handshake_cache.rs index f5cd728..75b4aa4 100644 --- a/src/handshake_cache.rs +++ b/src/handshake_cache.rs @@ -11,7 +11,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, RwLock}; use crate::zeta::StateB2; -use crate::{proto::MAX_UNASSOCIATED_HANDSHAKE_STATES, application::ApplicationLayer}; +use crate::{application::ApplicationLayer, proto::MAX_UNASSOCIATED_HANDSHAKE_STATES}; pub(crate) struct UnassociatedHandshakeCache { has_pending: AtomicBool, // Allowed to be falsely positive diff --git a/src/log_event.rs b/src/log_event.rs index a160ad9..1cb6522 100644 --- a/src/log_event.rs +++ b/src/log_event.rs @@ -7,7 +7,7 @@ */ use std::sync::Arc; -use crate::{zeta::Session, application::ApplicationLayer}; +use crate::{application::ApplicationLayer, zeta::Session}; /// ZSSP events that might be interesting to log or aggregate into metrics. pub enum LogEvent<'a, Application: ApplicationLayer> { diff --git a/src/result.rs b/src/result.rs index 15b023d..f78e035 100644 --- a/src/result.rs +++ b/src/result.rs @@ -102,6 +102,7 @@ pub enum ReceiveError { /// The associated session will no longer function and has to be dropped. MaxKeyLifetimeExceeded, + Rejected, /// One of the ratchet saving or lookup functions returned an error, so the packet had to be /// dropped. StorageError(StorageError), diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index a780b72..7349d63 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -2,9 +2,9 @@ use std::marker::PhantomData; use zeroize::Zeroizing; +use crate::application::ApplicationLayer; use crate::crypto::*; use crate::proto::*; -use crate::application::ApplicationLayer; pub struct SymmetricState { k: Zeroizing<[u8; AES_256_KEY_SIZE]>, diff --git a/src/zeta.rs b/src/zeta.rs index 0278fb7..957faeb 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -6,13 +6,12 @@ use std::io::Write; use std::num::NonZeroU32; use std::ops::{Deref, DerefMut}; use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering}; -use std::sync::{Arc, Mutex, RwLock, Weak}; +use std::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockReadGuard, Weak}; use zeroize::Zeroizing; use crate::antireplay::Window; use crate::application::*; use crate::challenge::{gen_null_response, respond_to_challenge_in_place}; -use crate::zssp::{log, ContextInner, SessionQueue}; use crate::crypto::*; use crate::fragged::Fragged; use crate::indexed_heap::BinaryHeapIndex; @@ -20,6 +19,7 @@ use crate::proto::*; use crate::ratchet_state::{RatchetState, RatchetStates}; use crate::result::{byzantine_fault, FaultType, OpenError, ReceiveError, SendError}; use crate::symmetric_state::SymmetricState; +use crate::zssp::{log, ContextInner, SessionQueue}; #[cfg(feature = "logging")] use crate::LogEvent::*; @@ -175,7 +175,6 @@ pub(crate) struct StateA3 { x3: ArrayVec, } - /// Corresponds to the ZKE Automata found in Section 4.1 - Definition 2. pub(crate) enum ZetaAutomata { Null, @@ -259,7 +258,12 @@ impl SymmetricState { None } } - fn mix_dh_no_init(&mut self, hmac: &mut App::HmacHash, secret: &App::KeyPair, remote: &App::PublicKey) -> Option<()> { + fn mix_dh_no_init( + &mut self, + hmac: &mut App::HmacHash, + secret: &App::KeyPair, + remote: &App::PublicKey, + ) -> Option<()> { let mut ecdh_secret = Zeroizing::new([0u8; P384_ECDH_SHARED_SECRET_SIZE]); if secret.agree(&remote, &mut ecdh_secret) { self.mix_key_no_init(hmac, ecdh_secret.as_ref()); @@ -362,9 +366,10 @@ pub(crate) fn trans_to_a1( identity: &[u8], send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Result>, OpenError> { - let RatchetStates{state1, state2} = app + let RatchetStates { state1, state2 } = app .restore_by_identity(&s_remote, &session_data) - .map_err(|e| OpenError::RatchetIoError(e))?.unwrap_or_default(); + .map_err(|e| OpenError::RatchetIoError(e))? + .unwrap_or_default(); let mut session_queue = ctx.session_queue.lock().unwrap(); let mut session_map = ctx.session_map.write().unwrap(); @@ -750,12 +755,13 @@ pub(crate) fn received_x2_trans( let mut nk_send = Zeroizing::new([0u8; HASHLEN]); noise.get_ask(hmac, LABEL_KEX_KEY, &mut kek_recv, &mut kek_send); noise.split(hmac, &mut nk_recv, &mut nk_send); + let nonce = to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 0); + set_header(&mut x3, kid_send.get(), &nonce); drop(state); let resend_timer = { let mut state = session.state.write().unwrap(); - state.key_mut(true).send.kid = Some(kid_send); state.key_mut(true).send.replace_kek(&kek_send); state.key_mut(true).recv.replace_kek(&kek_recv); @@ -773,10 +779,7 @@ pub(crate) fn received_x2_trans( // This return is unreachable. return Err(byzantine_fault!(FailedAuth, true)); }; - state.beta = ZetaAutomata::A3(Box::new(StateA3 { - identity: a1.identity.clone(), - x3: x3.clone(), - })); + state.beta = ZetaAutomata::A3(Box::new(StateA3 { identity: a1.identity.clone(), x3: x3.clone() })); resend_timer }; drop(kex_lock); @@ -787,15 +790,40 @@ pub(crate) fn received_x2_trans( Ok(x3) })(); + match result { Err(ReceiveError::ByzantineFault { .. }) => { - process_timers(app, ctx, session, app.time(), true, false, send); + let kex_lock = session.state_machine_lock.lock().unwrap(); + let state = session.state.read().unwrap(); + timeout_trans(app, ctx, session, kex_lock, state, app.time(), send); } Ok(ref mut packet) => send(packet, Some(&session.hk_send)), _ => {} } result.map(|_| ()) } +fn send_control( + session: &Arc>, + state: &MutableState, + packet_type: u8, + mut payload: ArrayVec, + send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), +) -> bool { + if let Some((c, _)) = get_counter(session, &state) { + if let (Some(kek), Some(kid)) = (state.key_ref(false).send.kek.as_ref(), state.key_ref(false).send.kid) { + let nonce = to_nonce(packet_type, c); + let tag = App::Aead::encrypt_in_place(kek, &nonce, &[], &mut payload[HEADER_SIZE..]); + payload.extend(tag); + set_header(&mut payload, kid.get(), &nonce); + send(&mut payload, Some(&session.hk_send)); + true + } else { + false + } + } else { + false + } +} /// Corresponds to Transition Algorithm 4 found in Section 4.3. pub(crate) fn received_x3_trans( app: &App, @@ -909,7 +937,7 @@ pub(crate) fn received_x3_trans( return Err(ReceiveError::StorageError(e)); } - let (session, current_time) = { + let session = { let mut session_map = ctx.session_map.write().unwrap(); use std::collections::hash_map::Entry::*; let entry = match session_map.entry(zeta.kid_recv) { @@ -959,9 +987,13 @@ pub(crate) fn received_x3_trans( session_queue.push_reserved(queue_idx, Arc::downgrade(&session), Reverse(resend_timer)); entry.insert(Arc::downgrade(&session)); - (session, current_time) + session }; - process_timers(app, ctx, &session, current_time, false, true, send); + let state = session.state.read().unwrap(); + let mut c1 = ArrayVec::::new(); + c1.extend([0u8; HEADER_SIZE]); + send_control(&session, &state, PACKET_TYPE_KEY_CONFIRM, c1, send); + drop(state); Ok(session) } @@ -969,9 +1001,9 @@ pub(crate) fn received_x3_trans( } } else { if !responder_silently_rejects { - //send(&create_reject(), Some(&zeta.hk_send)) + send(&mut create_reject(), Some(&App::PrpEnc::new(&zeta.hk_send))) } - Err(byzantine_fault!(FailedAuth, true)) + Err(ReceiveError::Rejected) } } /// Corresponds to Transition Algorithm 5 found in Section 4.3. @@ -1003,12 +1035,8 @@ pub(crate) fn received_c1_trans( return Err(byzantine_fault!(OutOfSequence, false)); }; - let specified_key = state - .key_ref(is_other) - .recv - .kek - .as_ref() - .ok_or(byzantine_fault!(OutOfSequence, true))?; + let specified_key = state.key_ref(is_other).recv.kek.as_ref(); + let specified_key = specified_key.ok_or(byzantine_fault!(OutOfSequence, true))?; let tag = c1[..].try_into().unwrap(); if !App::Aead::decrypt_in_place(specified_key, n, &[], &mut [], tag) { return Err(byzantine_fault!(FailedAuth, true)); @@ -1060,25 +1088,12 @@ pub(crate) fn received_c1_trans( } } - let mut c2 = ArrayVec::::new(); + let mut c2 = ArrayVec::::new(); c2.extend([0u8; HEADER_SIZE]); - let (c, _) = get_counter(session, &state).ok_or(byzantine_fault!(ExpiredCounter, false))?; - let nonce = to_nonce(PACKET_TYPE_ACK, c); - let latest_confirmed_key = state - .key_ref(false) - .send - .kek - .as_ref() - .ok_or(byzantine_fault!(OutOfSequence, true))?; - c2.extend(App::Aead::encrypt_in_place(latest_confirmed_key, &nonce, &[], &mut [])); - let kid_send = state - .key_ref(false) - .send - .kid - .ok_or(byzantine_fault!(OutOfSequence, false))?; - set_header(&mut c2, kid_send.get(), &nonce); + if !send_control(session, &state, PACKET_TYPE_ACK, c2, send) { + return Err(byzantine_fault!(OutOfSequence, true)); + } - send(&mut c2, Some(&session.hk_send)); Ok(just_establised) } /// Corresponds to the trivial Transition Algorithm described for processing C_2 packets found in @@ -1171,157 +1186,155 @@ pub(crate) fn received_d_trans( session.expire(); Ok(()) } +// Corresponds to the timeout timer Transition Algorithm described in Section 4.1 - Definition 3. +fn timeout_trans( + app: &App, + ctx: &Arc>, + session: &Arc>, + kex_lock: MutexGuard<'_, ()>, + state: RwLockReadGuard<'_, MutableState>, + current_time: i64, + send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), +) -> Option { + match &state.beta { + ZetaAutomata::Null => None, + ZetaAutomata::A1(_) | ZetaAutomata::A3 { .. } => { + let identity = match &state.beta { + ZetaAutomata::A1(a1) => &a1.identity, + ZetaAutomata::A3(a3) => &a3.identity, + _ => unreachable!(), + }; + if matches!(&state.beta, ZetaAutomata::A1(_)) { + log!(app, TimeoutX1(session)); + } else { + log!(app, TimeoutX3(session)); + } + let new_kid_recv = remap(ctx, session, &state); + + let hash = &mut App::Hash::new(); + let hmac = &mut App::HmacHash::new(); + if let Some(a1) = create_a1_state( + hash, + hmac, + &ctx.rng, + &session.s_remote, + new_kid_recv, + &state.ratchet_state1, + state.ratchet_state2.as_ref(), + identity, + ) { + let mut hk_recv = Zeroizing::new([0u8; HASHLEN]); + let mut hk_send = Zeroizing::new([0u8; HASHLEN]); + a1.noise.get_ask(hmac, LABEL_HEADER_KEY, &mut hk_recv, &mut hk_send); + let mut x1 = a1.x1.clone(); + + drop(state); + let resend_timer = { + let mut state = session.state.write().unwrap(); + session + .hk_recv + .reset((&hk_recv[..AES_256_KEY_SIZE]).try_into().unwrap()); + session + .hk_send + .reset((&hk_send[..AES_256_KEY_SIZE]).try_into().unwrap()); + *state.key_mut(true) = DuplexKey::default(); + state.key_mut(true).recv.kid = Some(new_kid_recv); + let resend_timer = current_time + App::SETTINGS.resend_time as i64; + state.resend_timer = AtomicI64::new(resend_timer); + state.timeout_timer = current_time + App::SETTINGS.initial_offer_timeout as i64; + state.beta = ZetaAutomata::A1(a1); + resend_timer + }; + drop(kex_lock); + + send(&mut x1, None); + Some(resend_timer) + } else { + None + } + } + ZetaAutomata::S2 => { + // Corresponds to Transition Algorithm 6 found in Section 4.3. + log!(app, StartedRekeyingSentK1(session)); + let new_kid_recv = remap(ctx, session, &state); + // -> s + // <- s + // ... + // -> psk, e, es, ss + let mut noise = SymmetricState::initialize(PROTOCOL_NAME_NOISE_KK); + let hash = &mut App::Hash::new(); + let hmac = &mut App::HmacHash::new(); + let mut k1 = ArrayVec::::new(); + k1.extend([0u8; HEADER_SIZE]); + // Noise process prologue. + noise.mix_hash(hash, &ctx.s_secret.public_key_bytes()); + noise.mix_hash(hash, &session.s_remote.to_bytes()); + // Process message pattern 1 psk0 token. + noise.mix_key_and_hash(hash, hmac, state.ratchet_state1.key.as_ref()); + // Process message pattern 1 e token. + let e_secret = noise.write_e(hash, hmac, &ctx.rng, &mut k1); + // Process message pattern 1 es token. + if noise.mix_dh(hmac, &e_secret, &session.s_remote).is_none() { + return None; + } + // Process message pattern 1 ss token. + noise.mix_key(hmac, session.noise_kk_ss.as_ref()); + // Process message pattern 1 payload. + let i = k1.len(); + k1.extend(new_kid_recv.get().to_be_bytes()); + let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_REKEY_INIT, 0), &mut k1[i..]); + k1.extend(tag); + + drop(state); + let resend_timer = { + let mut state = session.state.write().unwrap(); + state.key_mut(true).recv.kid = Some(new_kid_recv); + state.timeout_timer = current_time + App::SETTINGS.rekey_timeout as i64; + let resend_timer = current_time + App::SETTINGS.resend_time as i64; + state.resend_timer = AtomicI64::new(resend_timer); + state.beta = ZetaAutomata::R1 { noise, e_secret, k1: k1.clone() }; + resend_timer + }; + drop(kex_lock); + let state = session.state.read().unwrap(); + + send_control(session, &state, PACKET_TYPE_REKEY_INIT, k1, send); + Some(resend_timer) + } + ZetaAutomata::S1 { .. } => { + log!(app, TimeoutKeyConfirm(session)); + None + } + ZetaAutomata::R1 { .. } => { + log!(app, TimeoutK1(session)); + None + } + ZetaAutomata::R2 { .. } => { + log!(app, TimeoutK2(session)); + None + } + } +} /// Corresponds to the timer rules of the Zeta State Machine found in Section 4.1 - Definition 3. pub(crate) fn process_timers( app: &App, ctx: &Arc>, session: &Arc>, current_time: i64, - force_timeout: bool, - force_resend: bool, send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Option { let kex_lock = session.state_machine_lock.lock().unwrap(); let state = session.state.read().unwrap(); - if force_timeout || state.timeout_timer <= current_time { + if state.timeout_timer <= current_time { // Corresponds to the timeout timer Transition Algorithm described in Section 4.1 - Definition 3. - match &state.beta { - ZetaAutomata::Null => None, - ZetaAutomata::A1(_) | ZetaAutomata::A3 { .. } => { - let identity = match &state.beta { - ZetaAutomata::A1(a1) => &a1.identity, - ZetaAutomata::A3(a3) => &a3.identity, - _ => unreachable!(), - }; - if matches!(&state.beta, ZetaAutomata::A1(_)) { - log!(app, TimeoutX1(session)); - } else { - log!(app, TimeoutX3(session)); - } - let new_kid_recv = remap(ctx, session, &state); - - let hash = &mut App::Hash::new(); - let hmac = &mut App::HmacHash::new(); - if let Some(a1) = create_a1_state( - hash, - hmac, - &ctx.rng, - &session.s_remote, - new_kid_recv, - &state.ratchet_state1, - state.ratchet_state2.as_ref(), - identity, - ) { - let mut hk_recv = Zeroizing::new([0u8; HASHLEN]); - let mut hk_send = Zeroizing::new([0u8; HASHLEN]); - a1.noise.get_ask(hmac, LABEL_HEADER_KEY, &mut hk_recv, &mut hk_send); - let mut x1 = a1.x1.clone(); - - drop(state); - let resend_timer = { - let mut state = session.state.write().unwrap(); - session - .hk_recv - .reset((&hk_recv[..AES_256_KEY_SIZE]).try_into().unwrap()); - session - .hk_send - .reset((&hk_send[..AES_256_KEY_SIZE]).try_into().unwrap()); - *state.key_mut(true) = DuplexKey::default(); - state.key_mut(true).recv.kid = Some(new_kid_recv); - let resend_timer = current_time + App::SETTINGS.resend_time as i64; - state.resend_timer = AtomicI64::new(resend_timer); - state.timeout_timer = current_time + App::SETTINGS.initial_offer_timeout as i64; - state.beta = ZetaAutomata::A1(a1); - resend_timer - }; - drop(kex_lock); - - send(&mut x1, None); - Some(resend_timer) - } else { - None - } - } - ZetaAutomata::S2 => { - // Corresponds to Transition Algorithm 6 found in Section 4.3. - log!(app, StartedRekeyingSentK1(session)); - let new_kid_recv = remap(ctx, session, &state); - // -> s - // <- s - // ... - // -> psk, e, es, ss - let mut noise = SymmetricState::initialize(PROTOCOL_NAME_NOISE_KK); - let hash = &mut App::Hash::new(); - let hmac = &mut App::HmacHash::new(); - let mut k1 = ArrayVec::::new(); - k1.extend([0u8; HEADER_SIZE]); - // Noise process prologue. - noise.mix_hash(hash, &ctx.s_secret.public_key_bytes()); - noise.mix_hash(hash, &session.s_remote.to_bytes()); - // Process message pattern 1 psk0 token. - noise.mix_key_and_hash(hash, hmac, state.ratchet_state1.key.as_ref()); - // Process message pattern 1 e token. - let e_secret = noise.write_e(hash, hmac, &ctx.rng, &mut k1); - // Process message pattern 1 es token. - if noise.mix_dh(hmac, &e_secret, &session.s_remote).is_none() { - return None; - } - // Process message pattern 1 ss token. - noise.mix_key(hmac, session.noise_kk_ss.as_ref()); - // Process message pattern 1 payload. - let i = k1.len(); - k1.extend(new_kid_recv.get().to_be_bytes()); - let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_REKEY_INIT, 0), &mut k1[i..]); - k1.extend(tag); - - drop(state); - let resend_timer = { - let mut state = session.state.write().unwrap(); - state.key_mut(true).recv.kid = Some(new_kid_recv); - state.timeout_timer = current_time + App::SETTINGS.rekey_timeout as i64; - let resend_timer = current_time + App::SETTINGS.resend_time as i64; - state.resend_timer = AtomicI64::new(resend_timer); - state.beta = ZetaAutomata::R1 { noise, e_secret, k1: k1.clone() }; - resend_timer - }; - drop(kex_lock); - let state = session.state.read().unwrap(); - - if let Some((c, _)) = get_counter(session, &state) { - let nonce = to_nonce(PACKET_TYPE_REKEY_INIT, c); - let tag = App::Aead::encrypt_in_place( - state.key_ref(false).send.kek.as_ref().unwrap(), - &nonce, - &[], - &mut k1, - ); - k1.extend(tag); - set_header(&mut k1, state.key_ref(false).send.kid.unwrap().get(), &nonce); - - send(&mut k1, Some(&session.hk_send)); - } - Some(resend_timer) - } - ZetaAutomata::S1 { .. } => { - log!(app, TimeoutKeyConfirm(session)); - None - } - ZetaAutomata::R1 { .. } => { - log!(app, TimeoutK1(session)); - None - } - ZetaAutomata::R2 { .. } => { - log!(app, TimeoutK2(session)); - None - } - } + timeout_trans(app, ctx, session, kex_lock, state, current_time, send) } else { let ts = state.resend_timer.load(Ordering::Relaxed); let resend_next = current_time + App::SETTINGS.resend_time as i64; - if force_resend || (ts <= current_time && state.resend_timer.fetch_max(resend_next, Ordering::Relaxed) == ts) { + if ts <= current_time && state.resend_timer.fetch_max(resend_next, Ordering::Relaxed) == ts { // Corresponds to the resend timer rules found in Section 4.1 - Definition 3. - let (packet_type, mut control_payload) = match &state.beta { + let (packet_type, control_payload) = match &state.beta { ZetaAutomata::Null => return None, ZetaAutomata::A1(a1) => { log!(app, ResentX1(session)); @@ -1349,23 +1362,8 @@ pub(crate) fn process_timers( (PACKET_TYPE_REKEY_COMPLETE, k2.clone()) } }; - if let Some((c, _)) = get_counter(session, &state) { - let nonce = to_nonce(packet_type, c); - let tag = App::Aead::encrypt_in_place( - state.key_ref(false).send.kek.as_ref().unwrap(), - &nonce, - &[], - &mut control_payload, - ); - control_payload.extend(tag); - set_header( - &mut control_payload, - state.key_ref(false).send.kid.unwrap().get(), - &nonce, - ); - send(&mut control_payload, Some(&session.hk_send)); - } + send_control(session, &state, packet_type, control_payload, send); Some(resend_next) } else { Some(ts) @@ -1537,13 +1535,10 @@ pub(crate) fn received_k1_trans( .change_priority(session.queue_idx, Reverse(resend_timer)); let state = session.state.read().unwrap(); - let (c, _) = get_counter(session, &state).ok_or(byzantine_fault!(ExpiredCounter, false))?; - let nonce = to_nonce(PACKET_TYPE_REKEY_COMPLETE, c); - let tag = App::Aead::encrypt_in_place(state.key_ref(false).send.kek.as_ref().unwrap(), &nonce, &[], &mut k2); - k2.extend(tag); - set_header(&mut k2, state.key_ref(false).send.kid.unwrap().get(), &nonce); + if !send_control(session, &state, PACKET_TYPE_REKEY_COMPLETE, k2, send) { + return Err(byzantine_fault!(OutOfSequence, true)); + } - send(&mut k2, Some(&session.hk_send)); Ok(()) })(); @@ -1646,7 +1641,7 @@ pub(crate) fn received_k2_trans( noise.split(hmac, &mut nk_recv, &mut nk_send); drop(state); - let (current_time, resend_timer) = { + let resend_timer = { let mut state = session.state.write().unwrap(); state.key_mut(true).replace_nk(&nk_send, &nk_recv); state.key_mut(true).send.kid = Some(kid_send); @@ -1660,14 +1655,20 @@ pub(crate) fn received_k2_trans( state.timeout_timer = current_time + App::SETTINGS.rekey_timeout as i64; state.resend_timer = AtomicI64::new(resend_timer); state.beta = ZetaAutomata::S1; - (current_time, resend_timer) + resend_timer }; drop(kex_lock); ctx.session_queue .lock() .unwrap() .change_priority(session.queue_idx, Reverse(resend_timer)); - process_timers(app, ctx, session, current_time, false, true, send); + let state = session.state.read().unwrap(); + + let mut c1 = ArrayVec::::new(); + c1.extend([0u8; HEADER_SIZE]); + if !send_control(&session, &state, PACKET_TYPE_KEY_CONFIRM, c1, send) { + return Err(byzantine_fault!(OutOfSequence, true)); + } Ok(()) } else { @@ -1726,9 +1727,16 @@ pub(crate) fn send_payload( let j = i + fragment_len; mtu_sized_buffer[FRAGMENT_NO_IDX] = fragment_no as u8; - cipher.encrypt(&payload[i..j], &mut mtu_sized_buffer[HEADER_SIZE..HEADER_SIZE + fragment_len]); + cipher.encrypt( + &payload[i..j], + &mut mtu_sized_buffer[HEADER_SIZE..HEADER_SIZE + fragment_len], + ); - session.hk_send.encrypt_in_place((&mut mtu_sized_buffer[HEADER_AUTH_START..HEADER_AUTH_END]).try_into().unwrap()); + session.hk_send.encrypt_in_place( + (&mut mtu_sized_buffer[HEADER_AUTH_START..HEADER_AUTH_END]) + .try_into() + .unwrap(), + ); if !send(&mtu_sized_buffer[..HEADER_SIZE + fragment_len]) { return Ok(()); diff --git a/src/zssp.rs b/src/zssp.rs index ff7eed5..6df421b 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -12,28 +12,24 @@ use std::cmp::Reverse; use std::collections::HashMap; use std::hash::Hash; use std::io::Write; -use std::num::{NonZeroU32, NonZeroU64}; -use std::ops::DerefMut; -use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering}; -use std::sync::{Arc, Mutex, MutexGuard, RwLock, Weak}; +use std::num::NonZeroU32; +use std::sync::{Arc, Mutex, RwLock, Weak}; use arrayvec::ArrayVec; -use zeroize::Zeroizing; +use rand_core::RngCore; use crate::challenge::ChallengeContext; use crate::crypto::*; -use rand_core::RngCore; use crate::zeta::*; +use crate::application::*; use crate::frag_cache::UnassociatedFragCache; -use crate::fragged::{Assembled, Fragged}; +use crate::fragged::Assembled; use crate::handshake_cache::UnassociatedHandshakeCache; -use crate::indexed_heap::{BinaryHeapIndex, IndexedBinaryHeap}; +use crate::indexed_heap::IndexedBinaryHeap; use crate::log_event::LogEvent; use crate::proto::*; use crate::result::{byzantine_fault, FaultType, OpenError, ReceiveError, ReceiveOk, SendError, SessionEvent}; -use crate::symmetric_state::SymmetricState; -use crate::application::*; /// Macro to turn off logging at compile time. macro_rules! log { @@ -72,13 +68,6 @@ pub struct ContextInner { pub(crate) challenge: ChallengeContext, } -#[derive(Debug, PartialEq, Eq)] -pub enum IncomingSessionAction { - Allow, - Challenge, - Drop, -} - fn parse_fragment_header( incoming_fragment: &[u8], ) -> Result<(usize, usize, [u8; AES_GCM_IV_SIZE]), ReceiveError> { @@ -161,7 +150,7 @@ impl Context { pub fn open( &self, app: App, - mut send: impl FnMut(&mut [u8]) -> bool, + send: impl FnMut(&mut [u8]) -> bool, mut mtu: usize, static_remote_key: App::PublicKey, session_data: App::SessionData, @@ -217,9 +206,7 @@ impl Context { /// to put in-flight. pub fn receive<'a, SendFn: FnMut(&mut [u8]) -> bool>( &self, - app: &App, - check_allow_incoming_session: impl FnOnce() -> IncomingSessionAction, - check_accept_session: impl FnOnce(&App::PublicKey, &[u8], u64) -> (Option<(bool, App::SessionData)>, bool), + app: App, mut send_unassociated_reply: impl FnMut(&mut [u8]) -> bool, mut send_unassociated_mtu: usize, mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, @@ -350,14 +337,22 @@ impl Context { match packet_type { PACKET_TYPE_HANDSHAKE_RESPONSE => { log!(app, ReceivedRawX2); - received_x2_trans(app, ctx, &session, kid_recv, &nonce, assembled_packet, send_associated)?; + received_x2_trans( + &app, + ctx, + &session, + kid_recv, + &nonce, + assembled_packet, + send_associated, + )?; log!(app, X2IsAuthSentX3(&session)); SessionEvent::Control } PACKET_TYPE_KEY_CONFIRM => { log!(app, ReceivedRawKeyConfirm); let result = received_c1_trans( - app, + &app, ctx, &session, kid_recv, @@ -374,19 +369,35 @@ impl Context { } PACKET_TYPE_ACK => { log!(app, ReceivedRawAck); - received_c2_trans(app, ctx, &session, kid_recv, &nonce, assembled_packet)?; + received_c2_trans(&app, ctx, &session, kid_recv, &nonce, assembled_packet)?; log!(app, AckIsAuth(&session)); SessionEvent::Control } PACKET_TYPE_REKEY_INIT => { log!(app, ReceivedRawK1); - received_k1_trans(app, ctx, &session, kid_recv, &nonce, assembled_packet, send_associated)?; + received_k1_trans( + &app, + ctx, + &session, + kid_recv, + &nonce, + assembled_packet, + send_associated, + )?; log!(app, K1IsAuthSentK2(&session)); SessionEvent::Control } PACKET_TYPE_REKEY_COMPLETE => { log!(app, ReceivedRawK2); - received_k2_trans(app, ctx, &session, kid_recv, &nonce, assembled_packet, send_associated)?; + received_k2_trans( + &app, + ctx, + &session, + kid_recv, + &nonce, + assembled_packet, + send_associated, + )?; log!(app, K2IsAuthSentKeyConfirm(&session)); SessionEvent::Control } @@ -454,7 +465,7 @@ impl Context { } log!(app, ReceivedRawX3); - let session = received_x3_trans(app, ctx, zeta, kid_recv, assembled_packet, |packet, hk_send| { + let session = received_x3_trans(&app, ctx, zeta, kid_recv, assembled_packet, |packet, hk_send| { send_with_fragmentation(send_unassociated_reply, send_unassociated_mtu, packet, hk_send); })?; log!(app, X3IsAuthSentKeyConfirm(&session)); @@ -514,32 +525,38 @@ impl Context { return Err(byzantine_fault!(InvalidPacket, true)); } // Process recv challenge layer. - let challenge_start = assembled_packet.len() - CHALLENGE_SIZE; - let result = ctx.challenge.process_hello::( - remote_address, - (&assembled_packet[challenge_start..]).try_into().unwrap(), - ); - if let Err(challenge) = result { - log!(app, X1FailedChallengeSentNewChallenge); - let mut challenge_packet = ArrayVec::::new(); - challenge_packet.extend([0u8; HEADER_SIZE]); - challenge_packet - .try_extend_from_slice(&assembled_packet[..KID_SIZE]) - .unwrap(); - challenge_packet.extend(challenge); - let nonce = to_nonce(PACKET_TYPE_CHALLENGE, ctx.rng.lock().unwrap().next_u64()); - challenge_packet[FRAGMENT_COUNT_IDX] = 1; - challenge_packet[PACKET_NONCE_START..HEADER_SIZE].copy_from_slice(&nonce); + match app.incoming_session() { + IncomingSessionAction::Allow => {} + IncomingSessionAction::Challenge => { + let challenge_start = assembled_packet.len() - CHALLENGE_SIZE; + let result = ctx.challenge.process_hello::( + remote_address, + (&assembled_packet[challenge_start..]).try_into().unwrap(), + ); + if let Err(challenge) = result { + log!(app, X1FailedChallengeSentNewChallenge); + let mut challenge_packet = ArrayVec::::new(); + challenge_packet.extend([0u8; HEADER_SIZE]); + challenge_packet + .try_extend_from_slice(&assembled_packet[..KID_SIZE]) + .unwrap(); + challenge_packet.extend(challenge); + let nonce = to_nonce(PACKET_TYPE_CHALLENGE, ctx.rng.lock().unwrap().next_u64()); + challenge_packet[FRAGMENT_COUNT_IDX] = 1; + challenge_packet[PACKET_NONCE_START..HEADER_SIZE].copy_from_slice(&nonce); - send_unassociated_reply(&mut challenge_packet); - // If we issue a challenge the first hello packet will always fail. - return Err(byzantine_fault!(FailedAuth, false)); - } else { - log!(app, X1SucceededChallenge); + send_unassociated_reply(&mut challenge_packet); + // If we issue a challenge the first hello packet will always fail. + return Err(byzantine_fault!(FailedAuth, false)); + } else { + log!(app, X1SucceededChallenge); + } + } + IncomingSessionAction::Drop => return Err(ReceiveError::Rejected), } // Process recv zeta layer. - received_x1_trans(app, ctx, &nonce, assembled_packet, |packet, hk_send| { + received_x1_trans(&app, ctx, &nonce, assembled_packet, |packet, hk_send| { send_with_fragmentation(send_unassociated_reply, send_unassociated_mtu, packet, hk_send); })?; log!(app, X1IsAuthSentX2); @@ -619,15 +636,10 @@ impl Context { continue; } }; - let result = process_timers(&app, ctx, &session, current_time, false, false, |packet, hk_send| { + let result = process_timers(&app, ctx, &session, current_time, |packet, hk_send| { if let Some((send_fragment, mut mtu)) = send_to(&session) { mtu = mtu.max(MIN_TRANSPORT_MTU); - send_with_fragmentation( - send_fragment, - mtu, - packet, - hk_send - ); + send_with_fragmentation(send_fragment, mtu, packet, hk_send); } }); if let Some(next_timer) = result { From ccea68abe64c0633d96061e2edfb9a400555e34b Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 8 Aug 2023 11:05:01 -0400 Subject: [PATCH 22/50] refactor --- Cargo.lock | 238 +++++++++++++++++++++++++++++++++++ Cargo.toml | 18 ++- src/application.rs | 2 +- src/crypto/aes.rs | 14 +-- src/crypto/mod.rs | 1 + src/crypto/sha512.rs | 4 + src/crypto_impl/kyber1024.rs | 51 ++++++++ src/crypto_impl/mod.rs | 21 ++++ src/crypto_impl/p384_impl.rs | 39 ++++++ src/crypto_impl/sha512.rs | 34 +++++ src/frag_cache.rs | 4 +- src/fragged.rs | 4 +- src/lib.rs | 7 +- src/log_event.rs | 137 ++++++++++---------- src/proto.rs | 2 +- src/symmetric_state.rs | 4 +- src/zeta.rs | 62 +++++---- src/zssp.rs | 36 +++--- 18 files changed, 549 insertions(+), 129 deletions(-) create mode 100644 src/crypto_impl/kyber1024.rs create mode 100644 src/crypto_impl/mod.rs create mode 100644 src/crypto_impl/p384_impl.rs create mode 100644 src/crypto_impl/sha512.rs diff --git a/Cargo.lock b/Cargo.lock index e0caaa4..81b8afa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,181 @@ dependencies = [ "zeroize", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "const-oid" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747" + +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "p384" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +dependencies = [ + "elliptic-curve", + "primeorder", +] + [[package]] name = "pqc_kyber" version = "0.6.0" @@ -20,11 +195,71 @@ dependencies = [ "rand_core", ] +[[package]] +name = "primeorder" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c2fcef82c0ec6eefcc179b978446c399b3cdf73c392c35604e399eee6df1ee3" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "subtle", + "zeroize", +] + +[[package]] +name = "sha2" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "zeroize" @@ -37,7 +272,10 @@ name = "zssp" version = "0.0.3" dependencies = [ "arrayvec", + "hmac", + "p384", "pqc_kyber", "rand_core", + "sha2", "zeroize", ] diff --git a/Cargo.toml b/Cargo.toml index ca0967b..9b02171 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,21 @@ path = "src/lib.rs" doc = true [dependencies] -pqc_kyber = { version = "0.6.0", default-features = false, features = ["kyber1024", "std"] } -rand_core = "0.6.4" +rand_core = { version = "0.6.4" } zeroize = { version = "1.6.0" } arrayvec = { version = "0.7.4", default-features = false, features = ["std", "zeroize"] } + +pqc_kyber = { version = "0.6.0", default-features = false, features = ["kyber1024", "std"], 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", "p384", "hmac", "pqc_kyber"] +sha2 = ["dep:sha2"] +hmac = ["dep:hmac", "sha2"] +logging = [] +debug = ["logging"] + +[dev-dependencies] +rand_core = { version = "0.6.4", features = ["getrandom"] } diff --git a/src/application.rs b/src/application.rs index 705cdad..48dc269 100644 --- a/src/application.rs +++ b/src/application.rs @@ -247,7 +247,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(&self, event: crate::LogEvent<'_, Self>); } #[derive(Debug, PartialEq, Eq, Clone)] diff --git a/src/crypto/aes.rs b/src/crypto/aes.rs index 9a4e242..8368e08 100644 --- a/src/crypto/aes.rs +++ b/src/crypto/aes.rs @@ -3,7 +3,7 @@ pub const AES_256_KEY_SIZE: usize = 32; pub const AES_256_BLOCK_SIZE: usize = 16; pub const AES_GCM_TAG_SIZE: usize = 16; -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 @@ -15,7 +15,7 @@ pub trait AesEnc: Send + Sync { /// Change the encryption key to `key` so that all future encryption is performed with it. /// This function is very rarely called so it does not have to be particularly efficient. - fn reset(&self, key: &[u8; AES_256_KEY_SIZE]); + fn reset(&mut self, key: &[u8; AES_256_KEY_SIZE]); /// Decrypt the given `block` of plaintext directly using the AES block cipher /// (i.e. AES-256 in zero-padding ECB mode). @@ -31,7 +31,7 @@ pub trait AesDec: Send + Sync { /// Change the decryption key to `key` so that all future decryption is performed with it. /// This function is very rarely called so it does not have to be particularly efficient. - fn reset(&self, key: &[u8; AES_256_KEY_SIZE]); + fn reset(&mut self, key: &[u8; AES_256_KEY_SIZE]); /// Decrypt the given `block` of ciphertext directly using the AES 256 block cipher /// (i.e. AES-256 in zero-padding ECB mode). @@ -62,21 +62,21 @@ pub trait HighThroughputAesGcmPool: Send + Sync { fn new(encrypt_key: &[u8; AES_256_KEY_SIZE], decrypt_key: &[u8; AES_256_KEY_SIZE]) -> Self; - fn start_enc<'a>(&'a self, iv: &[u8; AES_GCM_IV_SIZE]) -> Self::EncContext<'a>; - fn start_dec<'a>(&'a self, iv: &[u8; AES_GCM_IV_SIZE]) -> Self::DecContext<'a>; + fn start_enc<'a>(&'a self, iv: &[u8; AES_GCM_NONCE_SIZE]) -> Self::EncContext<'a>; + fn start_dec<'a>(&'a self, iv: &[u8; AES_GCM_NONCE_SIZE]) -> Self::DecContext<'a>; } pub trait LowThroughputAesGcm { fn encrypt_in_place( key: &[u8; AES_256_KEY_SIZE], - iv: &[u8; AES_GCM_IV_SIZE], + iv: &[u8; AES_GCM_NONCE_SIZE], aad: &[u8], data: &mut [u8], ) -> [u8; AES_GCM_TAG_SIZE]; #[must_use] fn decrypt_in_place( key: &[u8; AES_256_KEY_SIZE], - iv: &[u8; AES_GCM_IV_SIZE], + iv: &[u8; AES_GCM_NONCE_SIZE], aad: &[u8], data: &mut [u8], tag: &[u8; AES_GCM_TAG_SIZE], 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/crypto/sha512.rs b/src/crypto/sha512.rs index 94178c1..32a0a82 100644 --- a/src/crypto/sha512.rs +++ b/src/crypto/sha512.rs @@ -17,6 +17,10 @@ pub trait HashSha512 { /// Does not need to be threadsafe. pub trait HmacSha512 { /// Allocate space on the stack or heap for repeated Hmac invocations. + /// + /// Many FIPS compliant libraries, namely OpenSSL, require initializing an Hmac context on the + /// heap before operating on it. + /// If you are using a more sane library feel free to make this return an empty type. fn new() -> Self; /// Pure function for computing a single HMAC Hash. Repeat invocations of this function should /// have no effect on each other. diff --git a/src/crypto_impl/kyber1024.rs b/src/crypto_impl/kyber1024.rs new file mode 100644 index 0000000..d2db360 --- /dev/null +++ b/src/crypto_impl/kyber1024.rs @@ -0,0 +1,51 @@ +use rand_core::{CryptoRng, RngCore}; +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 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) + } + + fn encapsulate( + rng: &mut Rng, + public_key: &[u8; KYBER_PUBLIC_KEY_SIZE], + plaintext_out: &mut [u8; KYBER_PLAINTEXT_SIZE], + ) -> Option<[u8; KYBER_CIPHERTEXT_SIZE]> { + let ret; + (ret, *plaintext_out) = pqc_kyber::encapsulate(public_key, rng).ok()?; + Some(ret) + } + + fn decapsulate( + &self, + ciphertext: &[u8; KYBER_CIPHERTEXT_SIZE], + plaintext_out: &mut [u8; KYBER_PLAINTEXT_SIZE], + ) -> bool { + if let Ok(result) = pqc_kyber::decapsulate(ciphertext, self.as_ref()) { + *plaintext_out = result; + true + } else { + false + } + } +} + +//impl Kyber1024PrivateKey for RustKyber1024PrivateKey { +// fn generate(rng: &mut Rng) -> (Self, [u8; KYBER_PUBLIC_KEY_SIZE]) { +// } + +// fn encapsulate( +// rng: &mut Rng, +// public_key: &[u8; KYBER_PUBLIC_KEY_SIZE], +// ) -> Option<([u8; KYBER_CIPHERTEXT_SIZE], [u8; KYBER_PLAINTEXT_SIZE])> { +// } + +// fn decapsulate(&self, ciphertext: &[u8; KYBER_CIPHERTEXT_SIZE]) -> Option<[u8; KYBER_PLAINTEXT_SIZE]> { +// } +//} diff --git a/src/crypto_impl/mod.rs b/src/crypto_impl/mod.rs new file mode 100644 index 0000000..e24f28d --- /dev/null +++ b/src/crypto_impl/mod.rs @@ -0,0 +1,21 @@ +#[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 = "hmac")] +pub use hmac; +#[cfg(feature = "p384")] +pub use p384; +#[cfg(feature = "pqc_kyber")] +pub use pqc_kyber; +#[cfg(feature = "sha2")] +pub use sha2; diff --git a/src/crypto_impl/p384_impl.rs b/src/crypto_impl/p384_impl.rs new file mode 100644 index 0000000..d65028a --- /dev/null +++ b/src/crypto_impl/p384_impl.rs @@ -0,0 +1,39 @@ +use p384::{ecdh::EphemeralSecret, CompressedPoint, PublicKey}; +use rand_core::{CryptoRng, RngCore}; + +use crate::crypto::*; + +pub type RustP384PublicKey = PublicKey; +impl P384PublicKey for PublicKey { + fn from_bytes(raw_key: &[u8; P384_PUBLIC_KEY_SIZE]) -> Option { + PublicKey::from_sec1_bytes(raw_key).ok() + } + + fn to_bytes(&self) -> [u8; P384_PUBLIC_KEY_SIZE] { + let k = CompressedPoint::from(self); + k.as_slice().try_into().unwrap() + } +} + +pub type RustP384KeyPair = EphemeralSecret; +impl P384KeyPair for RustP384KeyPair { + type PublicKey = PublicKey; + + fn generate(rng: &mut Rng) -> Self { + EphemeralSecret::random(rng) + } + + fn public_key_bytes(&self) -> [u8; P384_PUBLIC_KEY_SIZE] { + CompressedPoint::from(self.public_key()).as_slice().try_into().unwrap() + } + + fn agree(&self, public_key: &Self::PublicKey, output: &mut [u8; P384_ECDH_SHARED_SECRET_SIZE]) -> bool { + *output = self + .diffie_hellman(public_key) + .raw_secret_bytes() + .as_slice() + .try_into() + .unwrap(); + true + } +} diff --git a/src/crypto_impl/sha512.rs b/src/crypto_impl/sha512.rs new file mode 100644 index 0000000..10dc3b5 --- /dev/null +++ b/src/crypto_impl/sha512.rs @@ -0,0 +1,34 @@ +use hmac::{Hmac, Mac}; +use sha2::{Digest, Sha512}; + +use crate::crypto::*; + +pub type RustSha512 = Sha512; +impl HashSha512 for RustSha512 { + fn new() -> Self { + Digest::new() + } + + fn update(&mut self, data: &[u8]) { + Digest::update(self, data) + } + + fn finish_and_reset(&mut self, output: &mut [u8; SHA512_HASH_SIZE]) { + let mut hasher = Digest::new(); + std::mem::swap(self, &mut hasher); + *output = hasher.finalize().into(); + } +} + +pub struct RustHmac; +impl HmacSha512 for RustHmac { + fn new() -> Self { + RustHmac + } + + fn hash(&mut self, key: &[u8], full_input: &[u8], output: &mut [u8; SHA512_HASH_SIZE]) { + let mut hm = Hmac::::new_from_slice(key).unwrap(); + hm.update(full_input); + *output = hm.finalize().into_bytes().into() + } +} diff --git a/src/frag_cache.rs b/src/frag_cache.rs index cbe95df..2423cda 100644 --- a/src/frag_cache.rs +++ b/src/frag_cache.rs @@ -10,7 +10,7 @@ use std::collections::hash_map::RandomState; use std::hash::{BuildHasher, Hash, Hasher}; use std::mem::MaybeUninit; -use crate::crypto::AES_GCM_IV_SIZE; +use crate::crypto::AES_GCM_NONCE_SIZE; use crate::fragged::Assembled; use crate::proto::{MAX_FRAGMENTS, MAX_UNASSOCIATED_FRAGMENTS, MAX_UNASSOCIATED_PACKETS, MAX_UNASSOCIATED_PACKET_SIZE}; @@ -56,7 +56,7 @@ impl UnassociatedFragCache { /// Will check that aad is the same for all fragments. pub(crate) fn assemble( &mut self, - nonce: &[u8; AES_GCM_IV_SIZE], + nonce: &[u8; AES_GCM_NONCE_SIZE], remote_address: impl Hash, fragment_size: usize, fragment: Fragment, diff --git a/src/fragged.rs b/src/fragged.rs index 38a0e18..54b90d9 100644 --- a/src/fragged.rs +++ b/src/fragged.rs @@ -9,7 +9,7 @@ use arrayvec::ArrayVec; use std::mem::{needs_drop, zeroed, MaybeUninit}; -use crate::crypto::AES_GCM_IV_SIZE; +use crate::crypto::AES_GCM_NONCE_SIZE; use crate::proto::{MAX_FRAGMENTS, NONCE_SIZE_DIFF}; pub type Assembled = ArrayVec; @@ -37,7 +37,7 @@ impl Fragged { /// Will check that aad is the same for all fragments. pub(crate) fn assemble( &mut self, - nonce: &[u8; AES_GCM_IV_SIZE], + nonce: &[u8; AES_GCM_NONCE_SIZE], fragment: Fragment, fragment_no: usize, fragment_count: usize, diff --git a/src/lib.rs b/src/lib.rs index bd202c4..36c2fcd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ * https://www.zerotier.com/ */ pub mod crypto; +pub mod crypto_impl; mod antireplay; pub mod application; @@ -14,14 +15,16 @@ mod frag_cache; mod fragged; mod handshake_cache; mod indexed_heap; -pub mod log_event; + +mod log_event; +pub use log_event::*; + pub mod proto; pub mod ratchet_state; pub mod result; mod symmetric_state; pub mod zeta; pub mod zssp; -//mod context; //pub mod error; //pub use crate::applicationlayer::ApplicationLayer; diff --git a/src/log_event.rs b/src/log_event.rs index 1cb6522..283ae3a 100644 --- a/src/log_event.rs +++ b/src/log_event.rs @@ -1,77 +1,88 @@ -/* 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::sync::Arc; -use crate::{application::ApplicationLayer, zeta::Session}; +use crate::application::ApplicationLayer; +use crate::zeta::Session; /// ZSSP events that might be interesting to log or aggregate into metrics. -pub enum LogEvent<'a, Application: ApplicationLayer> { - ServiceXK1Resend(&'a Arc>), - ServiceXK3Resend(&'a Arc>), - ServiceXKTimeout(&'a Arc>), - ServiceKKStart(&'a Arc>), - ServiceKK1Resend(&'a Arc>), - ServiceKK2Resend(&'a Arc>), - ServiceKKTimeout(&'a Arc>), - ServiceKeyConfirmResend(&'a Arc>), - ServiceKeyConfirmTimeout(&'a Arc>), - /// `(fragment_count, fragment_no, packet_type)` - ReceiveUnassociatedFragment(u8, u8, u8), - ReceiveUncheckedXK1, - ReceiveCheckXK1Challenge(bool), - ReceiveValidXK1, - ReceiveUncheckedDOSChallenge, - ReceiveValidDOSChallenge(&'a Arc>), - ReceiveUncheckedXK2, - ReceiveValidXK2(&'a Arc>), - ReceiveUncheckedXK3, - ReceiveValidXK3(&'a Application::SessionData), - ReceiveUncheckedKK1, - ReceiveValidKK1(&'a Arc>), - ReceiveUncheckedKK2, - ReceiveValidKK2(&'a Arc>), - ReceiveValidKeyConfirm(&'a Arc>), - ReceiveValidAck(&'a Arc>), +pub enum LogEvent<'a, App: ApplicationLayer> { + 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>), + /// `(packet_type, packet_counter, fragment_no, fragment_count)` + ReceivedRawFragment(u8, u64, usize, usize), + ReceivedRawX1, + X1FailedChallengeSentNewChallenge, + X1SucceededChallenge, + X1IsAuthSentX2, + ReceivedRawChallenge, + ChallengeIsAuth(&'a Arc>), + ReceivedRawX2, + X2IsAuthSentX3(&'a Arc>), + ReceivedRawX3, + X3IsAuthSentKeyConfirm(&'a Arc>), + ReceivedRawKeyConfirm, + KeyConfirmIsAuthSentAck(&'a Arc>), + ReceivedRawAck, + AckIsAuth(&'a Arc>), + ReceivedRawK1, + K1IsAuthSentK2(&'a Arc>), + ReceivedRawK2, + K2IsAuthSentKeyConfirm(&'a Arc>), + ReceivedRawD, + DIsAuthClosedSession(&'a Arc>), } -impl<'a, Application: ApplicationLayer> std::fmt::Debug for LogEvent<'a, Application> { + +impl<'a, App: ApplicationLayer> std::fmt::Debug for LogEvent<'a, App> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use LogEvent::*; match self { - ServiceXK1Resend(_) => write!(f, "ServiceXK1Resend"), - ServiceXK3Resend(_) => write!(f, "ServiceXK3Resend"), - ServiceXKTimeout(_) => write!(f, "ServiceXKTimeout"), - ServiceKKStart(_) => write!(f, "ServiceKKStart"), - ServiceKK1Resend(_) => write!(f, "ServiceKK1Resend"), - ServiceKK2Resend(_) => write!(f, "ServiceKK2Resend"), - ServiceKKTimeout(_) => write!(f, "ServiceKKTimeout"), - ServiceKeyConfirmResend(_) => write!(f, "ServiceKeyConfirmResend"), - ServiceKeyConfirmTimeout(_) => write!(f, "ServiceKeyConfirmTimeout"), - ReceiveUnassociatedFragment(arg0, arg1, arg2) => f - .debug_tuple("ReceiveUnassociatedFragment") + Self::ResentX1(_) => f.debug_tuple("ResentX1").finish(), + Self::TimeoutX1(_) => f.debug_tuple("TimeoutX1").finish(), + Self::TimeoutX2 => write!(f, "TimeoutX2"), + Self::ResentX3(_) => f.debug_tuple("ResentX3").finish(), + Self::TimeoutX3(_) => f.debug_tuple("TimeoutX3").finish(), + Self::ResentKeyConfirm(_) => f.debug_tuple("ResentKeyConfirm").finish(), + Self::TimeoutKeyConfirm(_) => f.debug_tuple("TimeoutKeyConfirm").finish(), + Self::StartedRekeyingSentK1(_) => f.debug_tuple("StartedRekeyingSentK1").finish(), + Self::ResentK1(_) => f.debug_tuple("ResentK1").finish(), + Self::TimeoutK1(_) => f.debug_tuple("TimeoutK1").finish(), + Self::ResentK2(_) => f.debug_tuple("ResentK2").finish(), + Self::TimeoutK2(_) => f.debug_tuple("TimeoutK2").finish(), + Self::ReceivedRawFragment(arg0, arg1, arg2, arg3) => f + .debug_tuple("ReceivedRawFragment") .field(arg0) .field(arg1) .field(arg2) + .field(arg3) .finish(), - ReceiveUncheckedXK1 => write!(f, "ReceiveUncheckedXK1"), - ReceiveCheckXK1Challenge(arg0) => f.debug_tuple("ReceiveCheckXK1Challenge").field(arg0).finish(), - ReceiveValidXK1 => write!(f, "ReceiveValidXK1"), - ReceiveUncheckedDOSChallenge => write!(f, "ReceiveUncheckedDOSChallenge"), - ReceiveValidDOSChallenge(_) => write!(f, "ReceiveValidDOSChallenge"), - ReceiveUncheckedXK2 => write!(f, "ReceiveUncheckedXK2"), - ReceiveValidXK2(_) => write!(f, "ReceiveValidXK2"), - ReceiveUncheckedXK3 => write!(f, "ReceiveUncheckedXK3"), - ReceiveValidXK3(_) => write!(f, "ReceiveValidXK3"), - ReceiveUncheckedKK1 => write!(f, "ReceiveUncheckedKK1"), - ReceiveValidKK1(_) => write!(f, "ReceiveValidKK1"), - ReceiveUncheckedKK2 => write!(f, "ReceiveUncheckedKK2"), - ReceiveValidKK2(_) => write!(f, "ReceiveValidKK2"), - ReceiveValidKeyConfirm(_) => write!(f, "ReceiveValidKeyConfirm"), - ReceiveValidAck(_) => write!(f, "ReceiveValidAck"), + Self::ReceivedRawX1 => write!(f, "ReceivedRawX1"), + Self::X1FailedChallengeSentNewChallenge => write!(f, "X1FailedChallengeSentNewChallenge"), + Self::X1SucceededChallenge => write!(f, "X1SucceededChallenge"), + Self::X1IsAuthSentX2 => write!(f, "X1IsAuthSentX2"), + Self::ReceivedRawChallenge => write!(f, "ReceivedRawChallenge"), + Self::ChallengeIsAuth(_) => f.debug_tuple("ChallengeIsAuth").finish(), + Self::ReceivedRawX2 => write!(f, "ReceivedRawX2"), + Self::X2IsAuthSentX3(_) => f.debug_tuple("X2IsAuthSentX3").finish(), + Self::ReceivedRawX3 => write!(f, "ReceivedRawX3"), + Self::X3IsAuthSentKeyConfirm(_) => f.debug_tuple("X3IsAuthSentKeyConfirm").finish(), + Self::ReceivedRawKeyConfirm => write!(f, "ReceivedRawKeyConfirm"), + Self::KeyConfirmIsAuthSentAck(_) => f.debug_tuple("KeyConfirmIsAuthSentAck").finish(), + Self::ReceivedRawAck => write!(f, "ReceivedRawAck"), + Self::AckIsAuth(_) => f.debug_tuple("AckIsAuth").finish(), + Self::ReceivedRawK1 => write!(f, "ReceivedRawK1"), + Self::K1IsAuthSentK2(_) => f.debug_tuple("K1IsAuthSentK2").finish(), + Self::ReceivedRawK2 => write!(f, "ReceivedRawK2"), + Self::K2IsAuthSentKeyConfirm(_) => f.debug_tuple("K2IsAuthSentKeyConfirm").finish(), + Self::ReceivedRawD => write!(f, "ReceivedRawD"), + Self::DIsAuthClosedSession(_) => f.debug_tuple("DIsAuthClosedSession").finish(), } } } diff --git a/src/proto.rs b/src/proto.rs index 8c1d252..74ae800 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -51,7 +51,7 @@ pub(crate) const FRAGMENT_COUNT_IDX: usize = 5; /// into this number of fragments it will be dropped. pub(crate) const MAX_FRAGMENTS: usize = 48; -pub(crate) const NONCE_SIZE_DIFF: usize = AES_GCM_IV_SIZE - PACKET_NONCE_SIZE; +pub(crate) const NONCE_SIZE_DIFF: usize = AES_GCM_NONCE_SIZE - PACKET_NONCE_SIZE; /* Key exchange constants */ /* diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index 7349d63..6f23da2 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -165,7 +165,7 @@ impl SymmetricState { pub fn encrypt_and_hash_in_place( &mut self, hash: &mut App::Hash, - iv: [u8; AES_GCM_IV_SIZE], + iv: [u8; AES_GCM_NONCE_SIZE], data: &mut [u8], ) -> [u8; AES_GCM_TAG_SIZE] { let tag = App::Aead::encrypt_in_place(&self.k, &iv, &self.h, data); @@ -180,7 +180,7 @@ impl SymmetricState { pub fn decrypt_and_hash_in_place( &mut self, hash: &mut App::Hash, - iv: [u8; AES_GCM_IV_SIZE], + iv: [u8; AES_GCM_NONCE_SIZE], data: &mut [u8], tag: [u8; AES_GCM_TAG_SIZE], ) -> bool { diff --git a/src/zeta.rs b/src/zeta.rs index 957faeb..3b71ec4 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -32,8 +32,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()); @@ -90,8 +90,6 @@ pub struct Session { pub window: Window, pub(crate) defrag: [Mutex>; SESSION_MAX_FRAGMENTS_OOO], - pub(crate) hk_send: App::PrpEnc, - pub(crate) hk_recv: App::PrpDec, /// `session_queue -> state_machine_lock -> state -> session_map` state_machine_lock: Mutex<()>, @@ -105,13 +103,15 @@ pub(crate) struct MutableState { ratchet_state1: RatchetState, ratchet_state2: Option, + pub(crate) hk_send: App::PrpEnc, + pub(crate) hk_recv: App::PrpDec, key_creation_counter: u64, key_index: bool, keys: [DuplexKey; 2], resend_timer: AtomicI64, timeout_timer: i64, - pub beta: ZetaAutomata, + pub(crate) beta: ZetaAutomata, } /// Corresponds to State B_2 of the Zeta State Machine found in Section 4.1 - Definition 3. @@ -294,7 +294,7 @@ impl MutableState { } } -fn set_header(packet: &mut [u8], kid_send: u32, nonce: &[u8; AES_GCM_IV_SIZE]) { +fn set_header(packet: &mut [u8], kid_send: u32, nonce: &[u8; AES_GCM_NONCE_SIZE]) { packet[..KID_SIZE].copy_from_slice(&kid_send.to_be_bytes()); packet[PACKET_NONCE_START..HEADER_SIZE].copy_from_slice(&nonce[NONCE_SIZE_DIFF..]); } @@ -328,7 +328,7 @@ fn create_a1_state( let i = x1.len(); let (e1_secret, e1_public) = App::Kem::generate(rng.lock().unwrap().deref_mut()); x1.extend(e1_public); - x1.extend([0u8; AES_GCM_IV_SIZE]); + x1.extend([0u8; AES_GCM_NONCE_SIZE]); let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, 0), &mut x1[i..]); x1.extend(tag); // Process message pattern 1 payload. @@ -416,6 +416,8 @@ pub(crate) fn trans_to_a1( state: RwLock::new(MutableState { ratchet_state1: state1.clone(), ratchet_state2: state2.clone(), + hk_send: App::PrpEnc::new((&hk_send[..AES_256_KEY_SIZE]).try_into().unwrap()), + hk_recv: App::PrpDec::new((&hk_recv[..AES_256_KEY_SIZE]).try_into().unwrap()), key_creation_counter: 0, key_index: true, keys: [DuplexKey::default(), DuplexKey::default()], @@ -425,8 +427,6 @@ pub(crate) fn trans_to_a1( }), noise_kk_ss: noise_kk_ss.clone(), defrag: std::array::from_fn(|_| Mutex::new(Fragged::new())), - hk_send: App::PrpEnc::new((&hk_send[..AES_256_KEY_SIZE]).try_into().unwrap()), - hk_recv: App::PrpDec::new((&hk_recv[..AES_256_KEY_SIZE]).try_into().unwrap()), }); { let mut state = session.state.write().unwrap(); @@ -460,7 +460,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], x1: &mut [u8], send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Result<(), ReceiveError> { @@ -473,7 +473,7 @@ pub(crate) fn received_x1_trans( 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 hash = &mut App::Hash::new(); @@ -609,7 +609,7 @@ pub(crate) fn received_x2_trans( ctx: &Arc>, session: &Arc>, kid: NonZeroU32, - n: &[u8; AES_GCM_IV_SIZE], + n: &[u8; AES_GCM_NONCE_SIZE], x2: &mut [u8], send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Result<(), ReceiveError> { @@ -629,7 +629,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 mut result = (|| { @@ -797,7 +797,7 @@ pub(crate) fn received_x2_trans( let state = session.state.read().unwrap(); timeout_trans(app, ctx, session, kex_lock, state, app.time(), send); } - Ok(ref mut packet) => send(packet, Some(&session.hk_send)), + Ok(ref mut packet) => send(packet, Some(&session.state.read().unwrap().hk_send)), _ => {} } result.map(|_| ()) @@ -815,7 +815,7 @@ fn send_control( let tag = App::Aead::encrypt_in_place(kek, &nonce, &[], &mut payload[HEADER_SIZE..]); payload.extend(tag); set_header(&mut payload, kid.get(), &nonce); - send(&mut payload, Some(&session.hk_send)); + send(&mut payload, Some(&state.hk_send)); true } else { false @@ -961,6 +961,8 @@ pub(crate) fn received_x3_trans( state: RwLock::new(MutableState { ratchet_state1: new_ratchet_state.clone(), ratchet_state2: None, + hk_send: App::PrpEnc::new(&zeta.hk_send), + hk_recv: App::PrpDec::new(&zeta.hk_recv), key_creation_counter: c + 1, key_index: false, keys: [DuplexKey::default(), DuplexKey::default()], @@ -972,8 +974,6 @@ pub(crate) fn received_x3_trans( queue_idx, noise_kk_ss: noise_kk_ss.clone(), defrag: std::array::from_fn(|_| Mutex::new(Fragged::new())), - hk_send: App::PrpEnc::new(&zeta.hk_send), - hk_recv: App::PrpDec::new(&zeta.hk_recv), }); { let mut state = session.state.write().unwrap(); @@ -1012,7 +1012,7 @@ pub(crate) fn received_c1_trans( ctx: &Arc>, session: &Arc>, kid: NonZeroU32, - n: &[u8; AES_GCM_IV_SIZE], + n: &[u8; AES_GCM_NONCE_SIZE], c1: &[u8], send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Result> { @@ -1103,7 +1103,7 @@ pub(crate) fn received_c2_trans( ctx: &Arc>, session: &Arc>, kid: NonZeroU32, - n: &[u8; AES_GCM_IV_SIZE], + n: &[u8; AES_GCM_NONCE_SIZE], c2: &[u8], ) -> Result<(), ReceiveError> { use FaultType::*; @@ -1156,7 +1156,7 @@ pub(crate) fn received_c2_trans( pub(crate) fn received_d_trans( session: &Arc>, kid: NonZeroU32, - n: &[u8; AES_GCM_IV_SIZE], + n: &[u8; AES_GCM_NONCE_SIZE], d: &[u8], ) -> Result<(), ReceiveError> { use FaultType::*; @@ -1231,12 +1231,8 @@ fn timeout_trans( drop(state); let resend_timer = { let mut state = session.state.write().unwrap(); - session - .hk_recv - .reset((&hk_recv[..AES_256_KEY_SIZE]).try_into().unwrap()); - session - .hk_send - .reset((&hk_send[..AES_256_KEY_SIZE]).try_into().unwrap()); + state.hk_recv.reset((&hk_recv[..AES_256_KEY_SIZE]).try_into().unwrap()); + state.hk_send.reset((&hk_send[..AES_256_KEY_SIZE]).try_into().unwrap()); *state.key_mut(true) = DuplexKey::default(); state.key_mut(true).recv.kid = Some(new_kid_recv); let resend_timer = current_time + App::SETTINGS.resend_time as i64; @@ -1343,7 +1339,7 @@ pub(crate) fn process_timers( } ZetaAutomata::A3(a3) => { log!(app, ResentX3(session)); - send(&mut a3.x3.clone(), Some(&session.hk_send)); + send(&mut a3.x3.clone(), Some(&state.hk_send)); return Some(resend_next); } ZetaAutomata::S1 => { @@ -1391,7 +1387,7 @@ pub(crate) fn received_k1_trans( ctx: &Arc>, session: &Arc>, kid: NonZeroU32, - n: &[u8; AES_GCM_IV_SIZE], + n: &[u8; AES_GCM_NONCE_SIZE], k1: &mut [u8], send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Result<(), ReceiveError> { @@ -1553,7 +1549,7 @@ pub(crate) fn received_k2_trans( ctx: &Arc>, session: &Arc>, kid: NonZeroU32, - n: &[u8; AES_GCM_IV_SIZE], + n: &[u8; AES_GCM_NONCE_SIZE], k2: &mut [u8], send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Result<(), ReceiveError> { @@ -1732,7 +1728,7 @@ pub(crate) fn send_payload( &mut mtu_sized_buffer[HEADER_SIZE..HEADER_SIZE + fragment_len], ); - session.hk_send.encrypt_in_place( + state.hk_send.encrypt_in_place( (&mut mtu_sized_buffer[HEADER_AUTH_START..HEADER_AUTH_END]) .try_into() .unwrap(), @@ -1760,14 +1756,14 @@ pub(crate) fn send_payload( /// Corresponds to Algorithm 10 found in Section 4.3. pub(crate) fn receive_payload_in_place( session: &Arc>, + state: RwLockReadGuard<'_, MutableState>, kid: NonZeroU32, - n: &[u8; AES_GCM_IV_SIZE], + n: &[u8; AES_GCM_NONCE_SIZE], fragments: &mut [App::IncomingPacketBuffer], mut output_buffer: impl Write, ) -> Result<(), ReceiveError> { use FaultType::*; - let state = session.state.read().unwrap(); let is_other = if Some(kid) == state.key_ref(true).recv.kid { true } else if Some(kid) == state.key_ref(false).recv.kid { @@ -1792,7 +1788,7 @@ pub(crate) fn receive_payload_in_place( i += 1; } let fragment = &mut fragments[i].as_mut()[HEADER_SIZE..]; - let tag_idx = fragment.len() - AES_GCM_IV_SIZE; + let tag_idx = fragment.len() - AES_GCM_NONCE_SIZE; cipher.decrypt_in_place(&mut fragment[..tag_idx]); if cipher.finish((&fragment[tag_idx..]).try_into().unwrap()) { return Err(byzantine_fault!(FailedAuth, true)); diff --git a/src/zssp.rs b/src/zssp.rs index 6df421b..52e4361 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -27,9 +27,10 @@ use crate::frag_cache::UnassociatedFragCache; use crate::fragged::Assembled; use crate::handshake_cache::UnassociatedHandshakeCache; use crate::indexed_heap::IndexedBinaryHeap; -use crate::log_event::LogEvent; use crate::proto::*; use crate::result::{byzantine_fault, FaultType, OpenError, ReceiveError, ReceiveOk, SendError, SessionEvent}; +#[cfg(feature = "logging")] +use crate::LogEvent::*; /// Macro to turn off logging at compile time. macro_rules! log { @@ -70,13 +71,13 @@ pub struct ContextInner { fn parse_fragment_header( incoming_fragment: &[u8], -) -> Result<(usize, usize, [u8; AES_GCM_IV_SIZE]), ReceiveError> { +) -> Result<(usize, usize, [u8; AES_GCM_NONCE_SIZE]), ReceiveError> { let fragment_no = incoming_fragment[FRAGMENT_NO_IDX] as usize; let fragment_count = incoming_fragment[FRAGMENT_COUNT_IDX] as usize; if fragment_no >= fragment_count || fragment_count > MAX_FRAGMENTS { return Err(byzantine_fault!(FaultType::InvalidPacket, true)); } - let mut nonce = [0u8; AES_GCM_IV_SIZE]; + let mut nonce = [0u8; AES_GCM_NONCE_SIZE]; nonce[2..].copy_from_slice(&incoming_fragment[PACKET_NONCE_START..HEADER_SIZE]); Ok((fragment_no, fragment_count, nonce)) } @@ -230,7 +231,8 @@ impl Context { let session = ctx.session_map.read().unwrap().get(&kid_recv).map(|r| r.upgrade()); if let Some(Some(session)) = session { drop(session_map); - session.hk_recv.decrypt_in_place( + let state = session.state.read().unwrap(); + state.hk_recv.decrypt_in_place( (&mut incoming_fragment[HEADER_AUTH_START..HEADER_AUTH_END]) .try_into() .unwrap(), @@ -238,14 +240,17 @@ impl Context { let (fragment_no, fragment_count, nonce) = parse_fragment_header(incoming_fragment)?; let (packet_type, incoming_counter) = from_nonce(&nonce); + if packet_type != PACKET_TYPE_DATA { + log!( + app, + ReceivedRawFragment(packet_type, incoming_counter, fragment_no, fragment_count) + ); + } { //vrfy - if packet_type != PACKET_TYPE_DATA { - log!(app, ReceivedRawFragment(p, c, fragment_no, fragment_count)); - } if packet_type == PACKET_TYPE_HANDSHAKE_RESPONSE { - if !matches!(&session.state.read().unwrap().beta, ZetaAutomata::A1(_)) { + if !matches!(&state.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)); @@ -299,9 +304,12 @@ impl Context { } else { std::slice::from_mut(&mut incoming_fragment_buf) }; - receive_payload_in_place(&session, kid_recv, &nonce, fragments, output_buffer)?; + + receive_payload_in_place(&session, state, kid_recv, &nonce, fragments, output_buffer)?; + SessionEvent::Data } else { + drop(state); let mut buffer = ArrayVec::::new(); let assembled_packet = if fragment_count > 1 { let idx = incoming_counter as usize % session.defrag.len(); @@ -424,13 +432,13 @@ impl Context { let (fragment_no, fragment_count, nonce) = parse_fragment_header(incoming_fragment)?; let (packet_type, incoming_counter) = from_nonce(&nonce); + log!( + app, + ReceivedRawFragment(packet_type, incoming_counter, fragment_no, fragment_count) + ); { //vrfy - log!( - app, - ReceivedRawFragment(packet_type, incoming_counter, frag_no, frag_count) - ); if packet_type != PACKET_TYPE_HANDSHAKE_COMPLETION || incoming_counter != 0 { return Err(byzantine_fault!(InvalidPacket, true)); } @@ -480,10 +488,10 @@ impl Context { } else { let (fragment_no, fragment_count, nonce) = parse_fragment_header(incoming_fragment)?; let (packet_type, _c) = from_nonce(&nonce); + log!(app, ReceivedRawFragment(packet_type, _c, fragment_no, fragment_count)); { //vrfy - log!(app, ReceivedRawFragment(packet_type, _c, frag_no, frag_count)); if packet_type != PACKET_TYPE_HANDSHAKE_HELLO && packet_type != PACKET_TYPE_CHALLENGE { return Err(byzantine_fault!(InvalidPacket, true)); } From e7f0af7ab802876616b63c7af249ca1ef33a5b8b Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 8 Aug 2023 11:11:15 -0400 Subject: [PATCH 23/50] renamed traits --- src/application.rs | 8 ++++---- src/challenge.rs | 36 ++++++++++++++++++------------------ src/crypto/aes.rs | 4 ++-- src/crypto/sha512.rs | 4 ++-- src/crypto_impl/sha512.rs | 4 ++-- src/ratchet_state.rs | 4 ++-- src/symmetric_state.rs | 14 +++++++------- src/zeta.rs | 37 +++++++++++++++++++------------------ src/zssp.rs | 8 +++++--- 9 files changed, 61 insertions(+), 58 deletions(-) diff --git a/src/application.rs b/src/application.rs index 48dc269..dd48bae 100644 --- a/src/application.rs +++ b/src/application.rs @@ -102,11 +102,11 @@ pub trait ApplicationLayer: Sized { /// The implementation of AES-256 Encryption that ZSSP should use. /// /// FIPS compliance requires use of a FIPS certified implementation. - type PrpEnc: AesEnc; + type PrpEnc: Aes256Enc; /// The implementation of AES-256 Decryption that ZSSP should use. /// /// FIPS compliance requires use of a FIPS certified implementation. - type PrpDec: AesDec; + type PrpDec: Aes256Dec; type Aead: LowThroughputAesGcm; type AeadPool: HighThroughputAesGcmPool; @@ -114,8 +114,8 @@ pub trait ApplicationLayer: Sized { /// The implementation of SHA-512 that ZSSP should use. /// /// FIPS compliance requires use of a FIPS certified implementation. - type Hash: HashSha512; - type HmacHash: HmacSha512; + type Hash: Sha512Hash; + type Hmac: Sha512Hmac; /// The implementation of P-384 public keys that ZSSP should use. /// /// FIPS compliance requires a FIPS certified implementation. diff --git a/src/challenge.rs b/src/challenge.rs index cc4de21..faabbce 100644 --- a/src/challenge.rs +++ b/src/challenge.rs @@ -14,14 +14,15 @@ pub struct ChallengeContext { } /// Corresponds to Algorithm 11 found in Section 5. -pub fn gen_null_response(rng: &mut Rng) -> [u8; CHALLENGE_SIZE] { +pub fn gen_null_response(rng: &mut impl RngCore) -> [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, +pub fn respond_to_challenge_in_place( + rng: &mut impl RngCore, + hash: &mut impl Sha512Hash, challenge: &[u8; CHALLENGE_SIZE], pre_response: &mut [u8; CHALLENGE_SIZE], ) { @@ -31,7 +32,7 @@ pub fn respond_to_challenge_in_place let mut work_buf = [0u8; SHA512_HASH_SIZE]; loop { pre_response[POW_START..].copy_from_slice(&pow.to_be_bytes()); - if verify_pow::(pre_response, &mut work_buf) { + if verify_pow(hash, pre_response, &mut work_buf) { return; } pow = pow.wrapping_add(1); @@ -50,16 +51,17 @@ impl ChallengeContext { } } /// Corresponds to Algorithm 12 found in Section 5. - pub fn process_hello( + pub fn process_hello( &self, + hash: &mut impl Sha512Hash, addr: &impl std::hash::Hash, response: &[u8; CHALLENGE_SIZE], ) -> Result<(), [u8; CHALLENGE_SIZE]> { let c = u64::from_be_bytes(response[..COUNTER_SIZE].try_into().unwrap()); let mut work_buf = [0u8; SHA512_HASH_SIZE]; if self.antireplay_window.check(c) - && secure_eq(&response[COUNTER_SIZE..POW_START], &self.create_mac::(c, addr)) - && verify_pow::(response, &mut work_buf) + && secure_eq(&response[COUNTER_SIZE..POW_START], &self.create_mac(hash, c, addr)) + && verify_pow(hash, response, &mut work_buf) { self.antireplay_window.update(c); Ok(()) @@ -67,28 +69,27 @@ impl ChallengeContext { let mut challenge = [0u8; CHALLENGE_SIZE]; let d = self.counter.fetch_add(1, Ordering::Relaxed); challenge[..COUNTER_SIZE].copy_from_slice(&d.to_be_bytes()); - challenge[COUNTER_SIZE..POW_START].copy_from_slice(&self.create_mac::(d, addr)); + challenge[COUNTER_SIZE..POW_START].copy_from_slice(&self.create_mac(hash, d, addr)); challenge[POW_START..].copy_from_slice(&response[POW_START..]); Err(challenge) } } - 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); + fn create_mac(&self, hash: &mut impl Sha512Hash, c: u64, addr: &impl std::hash::Hash) -> [u8; MAC_SIZE] { + let mut hasher = ShaHasher(hash); hasher.write(&c.to_be_bytes()); addr.hash(&mut hasher); hasher.write(&self.salt); drop(hasher); let mut mac = [0u8; SHA512_HASH_SIZE]; - h.finish_and_reset(&mut mac); + hash.finish_and_reset(&mut mac); mac[..MAC_SIZE].try_into().unwrap() } } /// 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!() } @@ -99,10 +100,9 @@ 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], work_buf: &mut [u8; SHA512_HASH_SIZE]) -> bool { - let mut hasher = Hash::new(); - hasher.update(response); - hasher.finish_and_reset(work_buf); +fn verify_pow(hash: &mut impl Sha512Hash, response: &[u8], work_buf: &mut [u8; SHA512_HASH_SIZE]) -> bool { + hash.update(response); + hash.finish_and_reset(work_buf); let n = u32::from_be_bytes(work_buf[..4].try_into().unwrap()); n.leading_zeros() >= DIFFICULTY } diff --git a/src/crypto/aes.rs b/src/crypto/aes.rs index 8368e08..a06b85d 100644 --- a/src/crypto/aes.rs +++ b/src/crypto/aes.rs @@ -10,7 +10,7 @@ pub const AES_GCM_NONCE_SIZE: usize = 12; /// algorithm is secure. /// /// Instances must securely delete their keys when dropped or reset. -pub trait AesEnc: Send + Sync { +pub trait Aes256Enc: Send + Sync { fn new(key: &[u8; AES_256_KEY_SIZE]) -> Self; /// Change the encryption key to `key` so that all future encryption is performed with it. @@ -26,7 +26,7 @@ pub trait AesEnc: Send + Sync { /// A trait for decrypting individual blocks of plaintext using AES-256. /// /// Instances must securely delete their keys when dropped or reset. -pub trait AesDec: Send + Sync { +pub trait Aes256Dec: Send + Sync { fn new(key: &[u8; AES_256_KEY_SIZE]) -> Self; /// Change the decryption key to `key` so that all future decryption is performed with it. diff --git a/src/crypto/sha512.rs b/src/crypto/sha512.rs index 32a0a82..4e064d5 100644 --- a/src/crypto/sha512.rs +++ b/src/crypto/sha512.rs @@ -3,7 +3,7 @@ pub const SHA512_HASH_SIZE: usize = 64; /// A 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`. @@ -15,7 +15,7 @@ pub trait HashSha512 { /// Opaque HMAC-SHA-512 implementation. /// Does not need to be threadsafe. -pub trait HmacSha512 { +pub trait Sha512Hmac { /// Allocate space on the stack or heap for repeated Hmac invocations. /// /// Many FIPS compliant libraries, namely OpenSSL, require initializing an Hmac context on the diff --git a/src/crypto_impl/sha512.rs b/src/crypto_impl/sha512.rs index 10dc3b5..3b72b46 100644 --- a/src/crypto_impl/sha512.rs +++ b/src/crypto_impl/sha512.rs @@ -4,7 +4,7 @@ use sha2::{Digest, Sha512}; use crate::crypto::*; pub type RustSha512 = Sha512; -impl HashSha512 for RustSha512 { +impl Sha512Hash for RustSha512 { fn new() -> Self { Digest::new() } @@ -21,7 +21,7 @@ impl HashSha512 for RustSha512 { } pub struct RustHmac; -impl HmacSha512 for RustHmac { +impl Sha512Hmac for RustHmac { fn new() -> Self { RustHmac } diff --git a/src/ratchet_state.rs b/src/ratchet_state.rs index d90298b..72a4ca9 100644 --- a/src/ratchet_state.rs +++ b/src/ratchet_state.rs @@ -52,7 +52,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 = ArrayVec::::new(); buffer.push(1); buffer.extend(*LABEL_OTP_TO_RATCHET); @@ -102,7 +102,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 6f23da2..6a58549 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -40,7 +40,7 @@ impl SymmetricState { /// Corresponds to Noise `HKDF`. fn kbkdf( &self, - hmac: &mut App::HmacHash, + hmac: &mut App::Hmac, input_key_material: &[u8], label: &[u8; 4], num_outputs: u16, @@ -86,7 +86,7 @@ impl SymmetricState { } } /// Corresponds to Noise `MixKey`. - pub fn mix_key(&mut self, hmac: &mut App::HmacHash, input_key_material: &[u8]) { + pub fn mix_key(&mut self, hmac: &mut App::Hmac, input_key_material: &[u8]) { let mut next_ck = Zeroizing::new([0u8; HASHLEN]); let mut temp_k = Zeroizing::new([0u8; HASHLEN]); @@ -104,7 +104,7 @@ impl SymmetricState { self.k.clone_from_slice(&temp_k[..AES_256_KEY_SIZE]); } /// Corresponds to Noise `MixKey`. - pub fn mix_key_no_init(&mut self, hmac: &mut App::HmacHash, input_key_material: &[u8]) { + pub fn mix_key_no_init(&mut self, hmac: &mut App::Hmac, input_key_material: &[u8]) { let mut next_ck = Zeroizing::new([0u8; HASHLEN]); self.kbkdf(hmac, input_key_material, LABEL_KBKDF_CHAIN, 2, &mut next_ck, None, None); @@ -118,7 +118,7 @@ impl SymmetricState { hash.finish_and_reset(&mut self.h); } /// Corresponds to Noise `MixKeyAndHash`. - pub fn mix_key_and_hash(&mut self, hash: &mut App::Hash, hmac: &mut App::HmacHash, input_key_material: &[u8]) { + pub fn mix_key_and_hash(&mut self, hash: &mut App::Hash, hmac: &mut App::Hmac, input_key_material: &[u8]) { let mut next_ck = Zeroizing::new([0u8; HASHLEN]); let mut temp_h = [0u8; HASHLEN]; let mut temp_k = Zeroizing::new([0u8; HASHLEN]); @@ -141,7 +141,7 @@ impl SymmetricState { pub fn mix_key_and_hash_no_init( &mut self, hash: &mut App::Hash, - hmac: &mut App::HmacHash, + hmac: &mut App::Hmac, input_key_material: &[u8], ) { let mut next_ck = Zeroizing::new([0u8; HASHLEN]); @@ -192,7 +192,7 @@ impl SymmetricState { is_auth } /// Corresponds to Noise `Split`. - pub fn split(self, hmac: &mut App::HmacHash, key1: &mut [u8; HASHLEN], key2: &mut [u8; HASHLEN]) { + pub fn split(self, hmac: &mut App::Hmac, key1: &mut [u8; HASHLEN], key2: &mut [u8; HASHLEN]) { self.kbkdf(hmac, &[], LABEL_KBKDF_CHAIN, 2, key1, Some(key2), None); } /// Get an additional symmetric key (ASK) that is a collision resistant hash of the transcript, @@ -201,7 +201,7 @@ impl SymmetricState { /// https://github.com/noiseprotocol/noise_wiki/wiki/Additional-Symmetric-Keys. pub fn get_ask( &self, - hmac: &mut App::HmacHash, + hmac: &mut App::Hmac, label: &[u8; 4], key1: &mut [u8; HASHLEN], key2: &mut [u8; HASHLEN], diff --git a/src/zeta.rs b/src/zeta.rs index 3b71ec4..3819c25 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -46,7 +46,7 @@ pub(crate) fn from_nonce(n: &[u8]) -> (u8, u64) { (n[c_start - 1], u64::from_be_bytes(n[c_start..].try_into().unwrap())) } fn create_ratchet_state( - hmac: &mut App::HmacHash, + hmac: &mut App::Hmac, noise: &SymmetricState, pre_chain_len: u64, ) -> RatchetState { @@ -196,7 +196,7 @@ impl SymmetricState { fn write_e( &mut self, hash: &mut App::Hash, - hmac: &mut App::HmacHash, + hmac: &mut App::Hmac, rng: &Mutex, packet: &mut ArrayVec, ) -> App::KeyPair { @@ -210,7 +210,7 @@ impl SymmetricState { fn read_e( &mut self, hash: &mut App::Hash, - hmac: &mut App::HmacHash, + hmac: &mut App::Hmac, i: &mut usize, packet: &[u8], ) -> Option { @@ -224,7 +224,7 @@ impl SymmetricState { fn write_e_no_init( &mut self, hash: &mut App::Hash, - hmac: &mut App::HmacHash, + hmac: &mut App::Hmac, rng: &Mutex, packet: &mut ArrayVec, ) -> App::KeyPair { @@ -238,7 +238,7 @@ impl SymmetricState { fn read_e_no_init( &mut self, hash: &mut App::Hash, - hmac: &mut App::HmacHash, + hmac: &mut App::Hmac, i: &mut usize, packet: &[u8], ) -> Option { @@ -249,7 +249,7 @@ impl SymmetricState { *i = j; App::PublicKey::from_bytes((pub_key).try_into().unwrap()) } - fn mix_dh(&mut self, hmac: &mut App::HmacHash, secret: &App::KeyPair, remote: &App::PublicKey) -> Option<()> { + fn mix_dh(&mut self, hmac: &mut App::Hmac, secret: &App::KeyPair, remote: &App::PublicKey) -> Option<()> { let mut ecdh_secret = Zeroizing::new([0u8; P384_ECDH_SHARED_SECRET_SIZE]); if secret.agree(&remote, &mut ecdh_secret) { self.mix_key(hmac, ecdh_secret.as_ref()); @@ -260,7 +260,7 @@ impl SymmetricState { } fn mix_dh_no_init( &mut self, - hmac: &mut App::HmacHash, + hmac: &mut App::Hmac, secret: &App::KeyPair, remote: &App::PublicKey, ) -> Option<()> { @@ -301,7 +301,7 @@ fn set_header(packet: &mut [u8], kid_send: u32, nonce: &[u8; AES_GCM_NONCE_SIZE] fn create_a1_state( hash: &mut App::Hash, - hmac: &mut App::HmacHash, + hmac: &mut App::Hmac, rng: &Mutex, s_remote: &App::PublicKey, kid_recv: NonZeroU32, @@ -376,7 +376,7 @@ pub(crate) fn trans_to_a1( let kid_recv = gen_kid(session_map.deref(), ctx.rng.lock().unwrap().deref_mut()); let hash = &mut App::Hash::new(); - let hmac = &mut App::HmacHash::new(); + let hmac = &mut App::Hmac::new(); let a1 = create_a1_state( hash, hmac, @@ -449,8 +449,9 @@ pub(crate) fn respond_to_challenge( let mut state = session.state.write().unwrap(); if let ZetaAutomata::A1(a1) = &mut state.beta { let response_start = a1.x1.len() - CHALLENGE_SIZE; - respond_to_challenge_in_place::( + respond_to_challenge_in_place( ctx.rng.lock().unwrap().deref_mut(), + &mut App::Hash::new(), challenge, (&mut a1.x1[response_start..]).try_into().unwrap(), ); @@ -460,6 +461,7 @@ pub(crate) fn respond_to_challenge( pub(crate) fn received_x1_trans( app: &App, ctx: &ContextInner, + hash: &mut App::Hash, n: &[u8; AES_GCM_NONCE_SIZE], x1: &mut [u8], send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), @@ -476,8 +478,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 hash = &mut App::Hash::new(); - let hmac = &mut App::HmacHash::new(); + let hmac = &mut App::Hmac::new(); let mut noise = SymmetricState::::initialize(PROTOCOL_NAME_NOISE_XK); let mut i = 0; // Noise process prologue. @@ -623,7 +624,7 @@ pub(crate) fn received_x2_trans( let kex_lock = session.state_machine_lock.lock().unwrap(); let state = session.state.read().unwrap(); let hash = &mut App::Hash::new(); - let hmac = &mut App::HmacHash::new(); + let hmac = &mut App::Hmac::new(); if Some(kid) != state.key_ref(true).recv.kid { return Err(byzantine_fault!(UnknownLocalKeyId, true)); @@ -842,7 +843,7 @@ pub(crate) fn received_x3_trans( return Err(byzantine_fault!(UnknownLocalKeyId, true)); } let hash = &mut App::Hash::new(); - let hmac = &mut App::HmacHash::new(); + let hmac = &mut App::Hmac::new(); let mut noise = zeta.noise.clone(); let mut i = 0; @@ -1212,7 +1213,7 @@ fn timeout_trans( let new_kid_recv = remap(ctx, session, &state); let hash = &mut App::Hash::new(); - let hmac = &mut App::HmacHash::new(); + let hmac = &mut App::Hmac::new(); if let Some(a1) = create_a1_state( hash, hmac, @@ -1259,7 +1260,7 @@ fn timeout_trans( // -> psk, e, es, ss let mut noise = SymmetricState::initialize(PROTOCOL_NAME_NOISE_KK); let hash = &mut App::Hash::new(); - let hmac = &mut App::HmacHash::new(); + let hmac = &mut App::Hmac::new(); let mut k1 = ArrayVec::::new(); k1.extend([0u8; HEADER_SIZE]); // Noise process prologue. @@ -1438,7 +1439,7 @@ pub(crate) fn received_k1_trans( let mut i = 0; let mut noise = SymmetricState::::initialize(PROTOCOL_NAME_NOISE_KK); let hash = &mut App::Hash::new(); - let hmac = &mut App::HmacHash::new(); + let hmac = &mut App::Hmac::new(); // Noise process prologue. noise.mix_hash(hash, &session.s_remote.to_bytes()); noise.mix_hash(hash, &ctx.s_secret.public_key_bytes()); @@ -1591,7 +1592,7 @@ pub(crate) fn received_k2_trans( let mut noise = noise.clone(); let mut i = 0; let hash = &mut App::Hash::new(); - let hmac = &mut App::HmacHash::new(); + let hmac = &mut App::Hmac::new(); // Process message pattern 2 e token. let e_remote = noise .read_e(hash, hmac, &mut i, &k2) diff --git a/src/zssp.rs b/src/zssp.rs index 52e4361..78a7a30 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -85,7 +85,7 @@ fn parse_fragment_header( /// Fragments and sends the packet, destroying it in the process. /// /// Corresponds to the fragmentation algorithm described in Section 6. -fn send_with_fragmentation( +fn send_with_fragmentation( mut send: impl FnMut(&mut [u8]) -> bool, mtu: usize, headered_packet: &mut [u8], @@ -533,11 +533,13 @@ impl Context { return Err(byzantine_fault!(InvalidPacket, true)); } // Process recv challenge layer. + let hash = &mut App::Hash::new(); match app.incoming_session() { IncomingSessionAction::Allow => {} IncomingSessionAction::Challenge => { let challenge_start = assembled_packet.len() - CHALLENGE_SIZE; - let result = ctx.challenge.process_hello::( + let result = ctx.challenge.process_hello( + hash, remote_address, (&assembled_packet[challenge_start..]).try_into().unwrap(), ); @@ -564,7 +566,7 @@ impl Context { } // Process recv zeta layer. - received_x1_trans(&app, ctx, &nonce, assembled_packet, |packet, hk_send| { + received_x1_trans(&app, ctx, hash, &nonce, assembled_packet, |packet, hk_send| { send_with_fragmentation(send_unassociated_reply, send_unassociated_mtu, packet, hk_send); })?; log!(app, X1IsAuthSentX2); From a423f87554cd031ad63d25c7d74ecf3823a70e91 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 8 Aug 2023 12:51:53 -0400 Subject: [PATCH 24/50] added openssl-sys --- Cargo.lock | 34 +++ Cargo.toml | 1 + examples/basic_test.rs | 398 +++++++++++++++++++++++++++++++++++ src/application.rs | 5 - src/crypto/aes.rs | 8 +- src/crypto_impl/kyber1024.rs | 14 -- src/crypto_impl/mod.rs | 3 + src/crypto_impl/openssl.rs | 284 +++++++++++++++++++++++++ src/crypto_impl/p384_impl.rs | 8 +- src/crypto_impl/sha512.rs | 10 +- src/lib.rs | 10 +- src/symmetric_state.rs | 15 +- src/zeta.rs | 76 ++----- src/zssp.rs | 13 +- 14 files changed, 766 insertions(+), 113 deletions(-) create mode 100644 examples/basic_test.rs create mode 100644 src/crypto_impl/openssl.rs diff --git a/Cargo.lock b/Cargo.lock index 81b8afa..50f595c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "cc" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" +dependencies = [ + "libc", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -176,6 +185,18 @@ version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +[[package]] +name = "openssl-sys" +version = "0.9.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "866b5f16f90776b9bb8dc1e1802ac6f0513de3a7a7465867bfbc563dc737faac" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "p384" version = "0.13.0" @@ -186,6 +207,12 @@ dependencies = [ "primeorder", ] +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + [[package]] name = "pqc_kyber" version = "0.6.0" @@ -249,6 +276,12 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" @@ -273,6 +306,7 @@ version = "0.0.3" dependencies = [ "arrayvec", "hmac", + "openssl-sys", "p384", "pqc_kyber", "rand_core", diff --git a/Cargo.toml b/Cargo.toml index 9b02171..e291b16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ pqc_kyber = { version = "0.6.0", default-features = false, features = ["kyber102 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 } +openssl-sys = { version = "0.9.91", default-features = false } [features] default = ["debug", "p384", "hmac", "pqc_kyber"] diff --git a/examples/basic_test.rs b/examples/basic_test.rs new file mode 100644 index 0000000..71fde87 --- /dev/null +++ b/examples/basic_test.rs @@ -0,0 +1,398 @@ +/* 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 rand_core::OsRng; +use rand_core::RngCore; + +use zssp::application::{ + AcceptAction, ApplicationLayer, RatchetState, RatchetStates, RatchetUpdate, Settings, RATCHET_SIZE, IncomingSessionAction, +}; +use zssp::crypto::P384KeyPair; +use zssp::crypto_impl::*; +use zssp::Session; +use zssp::result::ReceiveError; + +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 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 PrpEnc = Aes256OpenSSLEnc; + type PrpDec = Aes256OpenSSLDec; + type Aead = AesGcmOpenSSL; + type AeadPool = AesGcmOpenSSLPool; + type Hash = Sha512Crate; + type Hmac = HmacSha512Crate; + type PublicKey = P384CratePublicKey; + type KeyPair = P384CrateKeyPair; + type Kem = RustKyber1024PrivateKey; + + type StorageError = std::convert::Infallible; + type SessionData = u128; + + type IncomingPacketBuffer = Vec; + + + fn incoming_session(&self) -> IncomingSessionAction { + IncomingSessionAction::Allow + } + + fn hello_requires_recognized_ratchet(&self) -> bool { + false + } + + fn initiator_disallows_downgrade(&self, session: &Arc>) -> bool { + 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> { + let ratchets = self.ratchets.lock().unwrap(); + Ok(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()) + } + + fn save_ratchet_state( + &self, + remote_static_key: &Self::PublicKey, + session_data: &Self::SessionData, + update_data: RatchetUpdate<'_>, + ) -> Result<(), Self::StorageError> { + let mut ratchets = self.ratchets.lock().unwrap(); + 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()); + println!("[{}] new ratchet #{}", self.name, update_data.state1.chain_len); + } + if let Some(rf) = update_data.deleted_fingerprint1() { + ratchets.rf_map.remove(rf); + } + if let Some(rf) = update_data.deleted_fingerprint2() { + ratchets.rf_map.remove(rf); + } + Ok(()) + } + + fn time(&self) -> i64 { + self.time.elapsed().as_millis() as i64 + } + + fn event_log(&self, event: zssp::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: P384CrateKeyPair, + bob_pubkey: P384CratePublicKey, +) { + let startup_time = std::time::Instant::now(); + let context = zssp::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.to_vec()).is_ok(), + TEST_MTU, + bob_pubkey.clone(), + 0, + &[], + ) + .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::result::ReceiveOk::*; + use zssp::result::SessionEvent::*; + let mut output_data = Vec::new(); + match context.receive( + alice_app, + |b| alice_out.send(b.to_vec()).is_ok(), + TEST_MTU, + |_| Some((|b: &mut [u8]| alice_out.send(b.to_vec()).is_ok(), TEST_MTU)), + &0, + pkt, + &mut output_data + ) { + Ok(Unassociated) => { + //println!("[alice] ok"); + } + Ok(Session(_, event)) => match event { + Established => { + up = true; + } + Data => { + assert!(!output_data.is_empty()); + //println!("[alice] received {}", data.len()); + } + NewSession => panic!(), + Rejected => panic!(), + Control => (), + }, + Err(e) => { + println!("[alice] ERROR {:?}", e); + if let ReceiveError::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.to_vec()).is_ok(), + &mut [0u8; TEST_MTU], + &test_data[..1400 + ((OsRng.next_u64() as usize) % (test_data.len() - 1400))], + ) + .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.000005) as u32 { + alice_session = None; + } + + if current_time >= next_service { + next_service = + current_time + context.service(alice_app, |_| Some((|b: &mut [u8]| alice_out.send(b.to_vec()).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: P384CrateKeyPair, +) { + let startup_time = std::time::Instant::now(); + let context = zssp::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::result::ReceiveOk::*; + use zssp::result::SessionEvent::*; + let mut output_data = Vec::new(); + match context.receive( + bob_app, + |b| bob_out.send(b.to_vec()).is_ok(), + TEST_MTU, + |_| Some((|b: &mut [u8]| bob_out.send(b.to_vec()).is_ok(), TEST_MTU)), + &0, + pkt, + &mut output_data + ) { + 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 => { + assert!(!output_data.is_empty()); + //println!("[bob] received {}", output_data.len()); + transferred += output_data.len() as u64 * 2; // *2 because we are also sending this many bytes back + context.send(&s, |b| bob_out.send(b.to_vec()).is_ok(), &mut [0u8; TEST_MTU], &output_data).unwrap(); + } + Established => panic!(), + Rejected => panic!(), + Control => (), + }, + Err(e) => { + println!("[bob] ERROR {:?}", e); + if let ReceiveError::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: &mut [u8]| bob_out.send(b.to_vec()).is_ok(), TEST_MTU))); + } + } +} + +fn core(time: u64, packet_success_rate: u32) { + let run = &AtomicBool::new(true); + + let alice_keypair = P384CrateKeyPair::generate(&mut OsRng); + let alice_app = TestApplication { + time: Instant::now(), + name: "alice", + ratchets: Mutex::new(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()), + }; + + 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/application.rs b/src/application.rs index dd48bae..d05aeb8 100644 --- a/src/application.rs +++ b/src/application.rs @@ -2,7 +2,6 @@ use rand_core::{CryptoRng, RngCore}; use std::sync::Arc; use crate::crypto::*; -use crate::ratchet_state::RatchetState; use crate::zeta::Session; pub use crate::proto::RATCHET_SIZE; @@ -145,10 +144,6 @@ pub trait ApplicationLayer: Sized { /// hold these for a short period of time when assembling fragmented packets on the receive /// path. type IncomingPacketBuffer: AsRef<[u8]> + AsMut<[u8]>; - /// Data type for giving ZSSP temporary ownership of a buffer containing the local party's - /// identity. - /// It will be dropped as soon as the session is established. - type LocalIdentityBlob: AsRef<[u8]>; /// 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). diff --git a/src/crypto/aes.rs b/src/crypto/aes.rs index a06b85d..9935564 100644 --- a/src/crypto/aes.rs +++ b/src/crypto/aes.rs @@ -62,21 +62,21 @@ pub trait HighThroughputAesGcmPool: Send + Sync { fn new(encrypt_key: &[u8; AES_256_KEY_SIZE], decrypt_key: &[u8; AES_256_KEY_SIZE]) -> Self; - fn start_enc<'a>(&'a self, iv: &[u8; AES_GCM_NONCE_SIZE]) -> Self::EncContext<'a>; - fn start_dec<'a>(&'a self, iv: &[u8; AES_GCM_NONCE_SIZE]) -> Self::DecContext<'a>; + fn start_enc<'a>(&'a self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> Self::EncContext<'a>; + fn start_dec<'a>(&'a self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> Self::DecContext<'a>; } pub trait LowThroughputAesGcm { fn encrypt_in_place( key: &[u8; AES_256_KEY_SIZE], - iv: &[u8; AES_GCM_NONCE_SIZE], + nonce: &[u8; AES_GCM_NONCE_SIZE], aad: &[u8], data: &mut [u8], ) -> [u8; AES_GCM_TAG_SIZE]; #[must_use] fn decrypt_in_place( key: &[u8; AES_256_KEY_SIZE], - iv: &[u8; AES_GCM_NONCE_SIZE], + nonce: &[u8; AES_GCM_NONCE_SIZE], aad: &[u8], data: &mut [u8], tag: &[u8; AES_GCM_TAG_SIZE], diff --git a/src/crypto_impl/kyber1024.rs b/src/crypto_impl/kyber1024.rs index d2db360..feffd5f 100644 --- a/src/crypto_impl/kyber1024.rs +++ b/src/crypto_impl/kyber1024.rs @@ -35,17 +35,3 @@ impl Kyber1024PrivateKey for RustKyber1024Private } } } - -//impl Kyber1024PrivateKey for RustKyber1024PrivateKey { -// fn generate(rng: &mut Rng) -> (Self, [u8; KYBER_PUBLIC_KEY_SIZE]) { -// } - -// fn encapsulate( -// rng: &mut Rng, -// public_key: &[u8; KYBER_PUBLIC_KEY_SIZE], -// ) -> Option<([u8; KYBER_CIPHERTEXT_SIZE], [u8; KYBER_PLAINTEXT_SIZE])> { -// } - -// fn decapsulate(&self, ciphertext: &[u8; KYBER_CIPHERTEXT_SIZE]) -> Option<[u8; KYBER_PLAINTEXT_SIZE]> { -// } -//} diff --git a/src/crypto_impl/mod.rs b/src/crypto_impl/mod.rs index e24f28d..d414a06 100644 --- a/src/crypto_impl/mod.rs +++ b/src/crypto_impl/mod.rs @@ -11,6 +11,9 @@ mod sha512; #[cfg(feature = "sha2")] pub use sha512::*; +mod openssl; +pub use openssl::*; + #[cfg(feature = "hmac")] pub use hmac; #[cfg(feature = "p384")] diff --git a/src/crypto_impl/openssl.rs b/src/crypto_impl/openssl.rs new file mode 100644 index 0000000..fb17bf9 --- /dev/null +++ b/src/crypto_impl/openssl.rs @@ -0,0 +1,284 @@ +use std::{ + ptr::{self, NonNull}, + sync::Mutex, +}; + +use openssl_sys::*; +use zeroize::Zeroizing; + +use crate::crypto::*; + +struct CipherCtx(NonNull); +impl Drop for CipherCtx { + fn drop(&mut self) { + unsafe { + EVP_CIPHER_CTX_free(self.0.as_ptr()); + } + } +} +impl CipherCtx { + /// Creates a new context. + pub fn new() -> Option { + unsafe { Some(CipherCtx(NonNull::new(EVP_CIPHER_CTX_new())?)) } + } + + pub unsafe fn cipher_init( + &self, + t: *const openssl_sys::EVP_CIPHER, + key: *const u8, + iv: *const u8, + ) -> bool { + let evp_f = if ENCRYPT { + EVP_EncryptInit_ex + } else { + EVP_DecryptInit_ex + }; + + // OpenSSL will usually leak a static amount of memory per cipher given here. + evp_f(self.0.as_ptr(), t, ptr::null_mut(), key, iv) > 0 + } + + pub unsafe fn update(&self, input: &[u8], output: *mut u8) -> bool { + let evp_f = if ENCRYPT { + EVP_EncryptUpdate + } else { + EVP_DecryptUpdate + }; + + let mut outlen = 0; + + evp_f( + self.0.as_ptr(), + output, + &mut outlen, + input.as_ptr(), + input.len() as c_int, + ) > 0 + } + + pub unsafe fn finalize(&self) -> bool { + let evp_f = if ENCRYPT { + EVP_EncryptFinal_ex + } else { + EVP_DecryptFinal_ex + }; + let mut outl = 0; + + evp_f(self.0.as_ptr(), ptr::null_mut(), &mut outl) > 0 + } + + pub unsafe fn get_tag(&self, tag: &mut [u8]) -> bool { + EVP_CIPHER_CTX_ctrl( + self.0.as_ptr(), + openssl_sys::EVP_CTRL_GCM_GET_TAG, + tag.len() as c_int, + tag.as_mut_ptr() as *mut _, + ) > 0 + } + + /// Sets the authentication tag for verification during decryption. + #[allow(unused)] + pub unsafe fn set_tag(&self, tag: &[u8]) -> bool { + EVP_CIPHER_CTX_ctrl( + self.0.as_ptr(), + openssl_sys::EVP_CTRL_GCM_SET_TAG, + tag.len() as c_int, + tag.as_ptr() as *mut _, + ) > 0 + } + pub fn as_ptr(&self) -> *mut openssl_sys::EVP_CIPHER_CTX { + self.0.as_ptr() + } +} + +pub struct Aes256OpenSSLEnc(Mutex); +unsafe impl Send for Aes256OpenSSLEnc {} +unsafe impl Sync for Aes256OpenSSLEnc {} + +impl Aes256Enc for Aes256OpenSSLEnc { + fn new(key: &[u8; AES_256_KEY_SIZE]) -> Self { + let ctx = CipherCtx::new().unwrap(); + unsafe { + let t = openssl_sys::EVP_aes_256_ecb(); + assert!(ctx.cipher_init::(t, key.as_ptr(), ptr::null())); + openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); + } + Self(Mutex::new(ctx)) + } + + fn reset(&mut self, key: &[u8; AES_256_KEY_SIZE]) { + let ctx = self.0.lock().unwrap(); + unsafe { + let t = openssl_sys::EVP_aes_256_ecb(); + assert!(ctx.cipher_init::(t, key.as_ptr(), ptr::null())); + openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); + } + } + + fn encrypt_in_place(&self, block: &mut [u8; AES_256_BLOCK_SIZE]) { + let ptr = block.as_mut_ptr(); + let ctx = self.0.lock().unwrap(); + unsafe { assert!(ctx.update::(block, ptr)) } + } +} +pub struct Aes256OpenSSLDec(Mutex); +unsafe impl Send for Aes256OpenSSLDec {} +unsafe impl Sync for Aes256OpenSSLDec {} + +impl Aes256Dec for Aes256OpenSSLDec { + fn new(key: &[u8; AES_256_KEY_SIZE]) -> Self { + let ctx = CipherCtx::new().unwrap(); + unsafe { + let t = openssl_sys::EVP_aes_256_ecb(); + assert!(ctx.cipher_init::(t, key.as_ptr(), ptr::null())); + openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); + } + Self(Mutex::new(ctx)) + } + + fn reset(&mut self, key: &[u8; AES_256_KEY_SIZE]) { + let ctx = self.0.lock().unwrap(); + unsafe { + let t = openssl_sys::EVP_aes_256_ecb(); + assert!(ctx.cipher_init::(t, key.as_ptr(), ptr::null())); + openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); + } + } + + fn decrypt_in_place(&self, block: &mut [u8; AES_256_BLOCK_SIZE]) { + let ptr = block.as_mut_ptr(); + let ctx = self.0.lock().unwrap(); + unsafe { assert!(ctx.update::(block, ptr)) } + } +} + +pub struct AesGcmOpenSSLEnc(CipherCtx); +impl AesGcmEncContext for AesGcmOpenSSLEnc { + fn encrypt(&mut self, input: &[u8], output: &mut [u8]) { + unsafe { assert!(self.0.update::(input, output.as_mut_ptr())) }; + } + + fn finish(&mut self) -> [u8; AES_GCM_TAG_SIZE] { + let mut output = [0u8; AES_GCM_TAG_SIZE]; + unsafe { + assert!(self.0.finalize::()); + assert!(self.0.get_tag(&mut output)); + } + output + } +} + +pub struct AesGcmOpenSSLDec(CipherCtx); +impl AesGcmDecContext for AesGcmOpenSSLDec { + fn decrypt_in_place(&mut self, data: &mut [u8]) { + let p = data.as_mut_ptr(); + unsafe { assert!(self.0.update::(data, p)) }; + } + + fn finish(&mut self, tag: &[u8; AES_GCM_TAG_SIZE]) -> bool { + unsafe { self.0.set_tag(tag) && self.0.finalize::() } + } +} + +pub struct AesGcmOpenSSLPool(Zeroizing<[u8; AES_256_KEY_SIZE]>, Zeroizing<[u8; AES_256_KEY_SIZE]>); +impl HighThroughputAesGcmPool for AesGcmOpenSSLPool { + type EncContext<'a> = AesGcmOpenSSLEnc; + + type DecContext<'a> = AesGcmOpenSSLDec; + + fn new(encrypt_key: &[u8; AES_256_KEY_SIZE], decrypt_key: &[u8; AES_256_KEY_SIZE]) -> Self { + AesGcmOpenSSLPool(Zeroizing::new(*encrypt_key), Zeroizing::new(*decrypt_key)) + } + + fn start_enc<'a>(&'a self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> AesGcmOpenSSLEnc { + let ctx = CipherCtx::new().unwrap(); + unsafe { + let t = openssl_sys::EVP_aes_256_gcm(); + assert!(ctx.cipher_init::(t, self.0.as_ptr(), nonce.as_ptr())); + openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); + } + AesGcmOpenSSLEnc(ctx) + } + + fn start_dec<'a>(&'a self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> AesGcmOpenSSLDec { + let ctx = CipherCtx::new().unwrap(); + unsafe { + let t = openssl_sys::EVP_aes_256_gcm(); + assert!(ctx.cipher_init::(t, self.0.as_ptr(), nonce.as_ptr())); + openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); + } + AesGcmOpenSSLDec(ctx) + } +} + +pub struct AesGcmOpenSSL; +impl LowThroughputAesGcm for AesGcmOpenSSL { + fn encrypt_in_place( + key: &[u8; AES_256_KEY_SIZE], + nonce: &[u8; AES_GCM_NONCE_SIZE], + aad: &[u8], + data: &mut [u8], + ) -> [u8; AES_GCM_TAG_SIZE] { + let mut output = [0u8; AES_GCM_TAG_SIZE]; + let ctx = CipherCtx::new().unwrap(); + unsafe { + let t = openssl_sys::EVP_aes_256_gcm(); + assert!(ctx.cipher_init::(t, key.as_ptr(), nonce.as_ptr())); + openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); + + assert!(ctx.update::(aad, ptr::null_mut())); + let p = data.as_mut_ptr(); + assert!(ctx.update::(data, p)); + + assert!(ctx.finalize::()); + assert!(ctx.get_tag(&mut output)); + } + output + } + + fn decrypt_in_place( + key: &[u8; AES_256_KEY_SIZE], + nonce: &[u8; AES_GCM_NONCE_SIZE], + aad: &[u8], + data: &mut [u8], + tag: &[u8; AES_GCM_TAG_SIZE], + ) -> bool { + let ctx = CipherCtx::new().unwrap(); + unsafe { + let t = openssl_sys::EVP_aes_256_gcm(); + assert!(ctx.cipher_init::(t, key.as_ptr(), nonce.as_ptr())); + openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); + + assert!(ctx.update::(aad, ptr::null_mut())); + let p = data.as_mut_ptr(); + assert!(ctx.update::(data, p)); + + ctx.set_tag(tag) && ctx.finalize::() + } + } +} + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn aes_128_ecb() { + let key = [1u8; 16]; + let ctx = CipherCtx::new().unwrap(); + unsafe { + assert!(ctx.cipher_init::(openssl_sys::EVP_aes_128_ecb(), key.as_ptr(), ptr::null())); + openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); + assert_eq!(openssl_sys::EVP_CIPHER_CTX_get_block_size(ctx.as_ptr()) as usize, 16); + + let origin = [2u8; 16]; + let mut val = origin; + let p = val.as_mut_ptr(); + + assert!(ctx.update::(&val, p)); + assert!(ctx.cipher_init::(ptr::null(), key.as_ptr(), ptr::null())); + assert!(ctx.update::(&val, p)); + + assert_eq!(val, origin); + } + } +} diff --git a/src/crypto_impl/p384_impl.rs b/src/crypto_impl/p384_impl.rs index d65028a..77c0938 100644 --- a/src/crypto_impl/p384_impl.rs +++ b/src/crypto_impl/p384_impl.rs @@ -3,8 +3,8 @@ use rand_core::{CryptoRng, RngCore}; use crate::crypto::*; -pub type RustP384PublicKey = PublicKey; -impl P384PublicKey for PublicKey { +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() } @@ -15,8 +15,8 @@ impl P384PublicKey for PublicKey { } } -pub type RustP384KeyPair = EphemeralSecret; -impl P384KeyPair for RustP384KeyPair { +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 3b72b46..ba932f8 100644 --- a/src/crypto_impl/sha512.rs +++ b/src/crypto_impl/sha512.rs @@ -3,8 +3,8 @@ use sha2::{Digest, Sha512}; use crate::crypto::*; -pub type RustSha512 = Sha512; -impl Sha512Hash for RustSha512 { +pub type Sha512Crate = Sha512; +impl Sha512Hash for Sha512Crate { fn new() -> Self { Digest::new() } @@ -20,10 +20,10 @@ impl Sha512Hash for RustSha512 { } } -pub struct RustHmac; -impl Sha512Hmac for RustHmac { +pub struct HmacSha512Crate; +impl Sha512Hmac for HmacSha512Crate { fn new() -> Self { - RustHmac + HmacSha512Crate } fn hash(&mut self, key: &[u8], full_input: &[u8], output: &mut [u8; SHA512_HASH_SIZE]) { diff --git a/src/lib.rs b/src/lib.rs index 36c2fcd..626e58c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,15 +20,17 @@ mod log_event; pub use log_event::*; pub mod proto; -pub mod ratchet_state; +mod ratchet_state; pub mod result; mod symmetric_state; -pub mod zeta; -pub mod zssp; +mod zeta; +mod zssp; +pub use zeta::*; +pub use zssp::*; //pub mod error; //pub use crate::applicationlayer::ApplicationLayer; //pub use crate::log_event::LogEvent; -//pub use crate::proto::{MAX_IDENTITY_BLOB_SIZE, MIN_PACKET_SIZE, MIN_TRANSPORT_MTU, RATCHET_SIZE}; +//pub use crate::proto::{IDENTITY_MAX_SIZE, MIN_PACKET_SIZE, MIN_TRANSPORT_MTU}; //pub use crate::ratchet_state::RatchetState; //pub use crate::zssp::{Context, ContextInner, IncomingSessionAction, ReceiveResult, Session, SessionEvent}; diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index 6a58549..71cf4d0 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -138,12 +138,7 @@ impl SymmetricState { self.k.clone_from_slice(&temp_k[..AES_256_KEY_SIZE]); } /// Corresponds to Noise `MixKeyAndHash`. - pub fn mix_key_and_hash_no_init( - &mut self, - hash: &mut App::Hash, - hmac: &mut App::Hmac, - input_key_material: &[u8], - ) { + pub fn mix_key_and_hash_no_init(&mut self, hash: &mut App::Hash, hmac: &mut App::Hmac, input_key_material: &[u8]) { let mut next_ck = Zeroizing::new([0u8; HASHLEN]); let mut temp_h = [0u8; HASHLEN]; @@ -199,13 +194,7 @@ impl SymmetricState { /// is forward secrect and is cryptographically independent from all other produced keys. /// Based on Noise's unstable ASK mechanism, using KBKDF instead of HKDF. /// https://github.com/noiseprotocol/noise_wiki/wiki/Additional-Symmetric-Keys. - pub fn get_ask( - &self, - hmac: &mut App::Hmac, - label: &[u8; 4], - key1: &mut [u8; HASHLEN], - key2: &mut [u8; HASHLEN], - ) { + pub fn get_ask(&self, hmac: &mut App::Hmac, label: &[u8; 4], key1: &mut [u8; HASHLEN], key2: &mut [u8; HASHLEN]) { self.kbkdf(hmac, &self.h, label, 2, key1, Some(key2), None); } /// Used for internally debugging a key exchange. diff --git a/src/zeta.rs b/src/zeta.rs index 3819c25..07f7c74 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -64,13 +64,13 @@ fn get_counter(session: &Session, state: &MutableSta None } else { let c = session.send_counter.fetch_add(1, Ordering::Relaxed); + if c > THREAD_SAFE_COUNTER_HARD_EXPIRE { + session.session_has_expired.store(true, Ordering::SeqCst); + } if c > state.key_creation_counter + EXPIRE_AFTER_USES { session.session_has_expired.store(true, Ordering::SeqCst); return None; } - if c > THREAD_SAFE_COUNTER_HARD_EXPIRE { - session.session_has_expired.store(true, Ordering::SeqCst); - } Some((c, c > state.key_creation_counter + App::SETTINGS.rekey_after_key_uses)) } } @@ -258,12 +258,7 @@ impl SymmetricState { None } } - fn mix_dh_no_init( - &mut self, - hmac: &mut App::Hmac, - secret: &App::KeyPair, - remote: &App::PublicKey, - ) -> Option<()> { + fn mix_dh_no_init(&mut self, hmac: &mut App::Hmac, secret: &App::KeyPair, remote: &App::PublicKey) -> Option<()> { let mut ecdh_secret = Zeroizing::new([0u8; P384_ECDH_SHARED_SECRET_SIZE]); if secret.agree(&remote, &mut ecdh_secret) { self.mix_key_no_init(hmac, ecdh_secret.as_ref()); @@ -1765,20 +1760,15 @@ pub(crate) fn receive_payload_in_place( ) -> Result<(), ReceiveError> { use FaultType::*; - let is_other = if Some(kid) == state.key_ref(true).recv.kid { - true - } else if Some(kid) == state.key_ref(false).recv.kid { - false + let specified_key = if Some(kid) == state.keys[0].recv.kid { + state.keys[0].nk.as_ref() + } else if Some(kid) == state.keys[1].recv.kid { + state.keys[1].nk.as_ref() } else { return Err(byzantine_fault!(OutOfSequence, true)); }; - let mut cipher = state - .key_ref(is_other) - .nk - .as_ref() - .ok_or(byzantine_fault!(OutOfSequence, true))? - .start_dec(n); + let mut cipher = specified_key.ok_or(byzantine_fault!(OutOfSequence, true))?.start_dec(n); // NOTE: This only works because we check the size of every received fragment in the receive // function, otherwise this could panic. @@ -1794,6 +1784,7 @@ pub(crate) fn receive_payload_in_place( if cipher.finish((&fragment[tag_idx..]).try_into().unwrap()) { return Err(byzantine_fault!(FailedAuth, true)); } + drop(cipher); let (_, c) = from_nonce(n); if !session.window.update(c) { @@ -1802,7 +1793,6 @@ pub(crate) fn receive_payload_in_place( return Err(byzantine_fault!(ExpiredCounter, true)); } - drop(cipher); for fragment in fragments { let result = output_buffer.write(&fragment.as_ref()[HEADER_SIZE..]); if let Err(e) = result { @@ -1859,46 +1849,14 @@ impl Session { /// ///// The current ratchet state of this session. ///// The returned values are sensitive and should be securely erased before being dropped. - //pub fn ratchet_states(&self) -> [RatchetState; 2] { - // let state = self.state.read().unwrap(); - // state.ratchet_states.clone() - //} + pub fn ratchet_states(&self) -> RatchetStates { + let state = self.state.read().unwrap(); + RatchetStates::new(state.ratchet_state1.clone(), state.ratchet_state2.clone()) + } /// The current ratchet count of this session. - //pub fn ratchet_count(&self) -> u64 { - // self.state.read().unwrap(). - //} - /// 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. - //pub fn expire(&self) { - // if let Some(context) = self.context.upgrade() { - // self.expire_inner(&context, &mut context.session_queue.lock().unwrap()); - // } - //} - //fn expire_inner( - // &self, - // context: &Arc>, - // session_queue: &mut IndexedBinaryHeap>, Reverse>, - //) { - // // Prevent this session from being updated. - // session_queue.remove(self.queue_idx); - // self.session_has_expired.store(true, Ordering::Relaxed); - // let _kex_lock = self.state_machine_lock.lock().unwrap(); - // let mut state = self.state.write().unwrap(); - // let mut session_map = context.session_map.write().unwrap(); - // for key in &state.cipher_states { - // if let Some(pre_id) = key.as_ref().map(|k| k.local_key_id) { - // session_map.remove(&pre_id); - // } - // } - // use OfferStateMachine::*; - // match &state.outgoing_offer { - // NoiseXKPattern1or3(handshake_state) => session_map.remove(&handshake_state.local_key_id), - // NoiseKKPattern1 { new_key_id, .. } => session_map.remove(new_key_id), - // _ => None, - // }; - // state.outgoing_offer = OfferStateMachine::Null; - //} + pub fn ratchet_count(&self) -> u64 { + self.state.read().unwrap().ratchet_state1.chain_len + } /// Check whether this session is established. pub fn established(&self) -> bool { let state = self.state.read().unwrap(); diff --git a/src/zssp.rs b/src/zssp.rs index 78a7a30..29b90b3 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -98,13 +98,16 @@ fn send_with_fragmentation( let fragment_base_size = payload_len / fragment_count; let fragment_size_remainder = payload_len % fragment_count; + let mut header: [u8; HEADER_SIZE] = headered_packet[..HEADER_SIZE].try_into().unwrap(); + header[FRAGMENT_COUNT_IDX] = fragment_count as u8; + let mut i = HEADER_SIZE; for fragment_no in 0..fragment_count { let j = i + fragment_base_size + (fragment_no < fragment_size_remainder) as usize; let fragment = &mut headered_packet[i - HEADER_SIZE..j]; - fragment[FRAGMENT_NO_IDX] = fragment_no as u8; - fragment[FRAGMENT_COUNT_IDX] = fragment_count as u8; + header[FRAGMENT_NO_IDX] = fragment_no as u8; + fragment[..HEADER_SIZE].copy_from_slice(&header); if let Some(hk_send) = hk_send { hk_send.encrypt_in_place((&mut fragment[HEADER_AUTH_START..HEADER_AUTH_END]).try_into().unwrap()); @@ -482,7 +485,7 @@ impl Context { // This can occur naturally because either Bob's incoming_sessions cache got // full so Alice's incoming session was dropped, or the session this packet // was for was dropped by the application. - return Err(byzantine_fault!(UnknownLocalKeyId, true)); + return Err(byzantine_fault!(UnknownLocalKeyId, false)); } } } else { @@ -626,10 +629,10 @@ impl Context { &self, app: App, mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, - current_time: i64, ) -> i64 { let ctx = &self.0; let mut session_queue = ctx.session_queue.lock().unwrap(); + let current_time = app.time(); let mut next_service_time = current_time + App::SETTINGS.fragment_assembly_timeout as i64; // This update system takes heavy advantage of the fact that sessions only need to be updated // either roughly every second or roughly every hour. That big gap allows for minor optimizations. @@ -668,6 +671,6 @@ impl Context { .check_for_expiry(App::SETTINGS.fragment_assembly_timeout as i64, current_time); self.0.unassociated_handshake_states.service(current_time); - next_service_time + next_service_time - current_time } } From 8e8aa4fa63695982ac1f3f44df9feb6cbcecf152 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 8 Aug 2023 14:40:04 -0400 Subject: [PATCH 25/50] got it working --- examples/basic_test.rs | 32 ++++++++++----- src/crypto/aes.rs | 4 +- src/crypto_impl/openssl.rs | 26 +++++++------ src/proto.rs | 6 +-- src/zeta.rs | 80 +++++++++++++++++++++++++------------- src/zssp.rs | 17 +++++--- 6 files changed, 108 insertions(+), 57 deletions(-) diff --git a/examples/basic_test.rs b/examples/basic_test.rs index 71fde87..e1d1a35 100644 --- a/examples/basic_test.rs +++ b/examples/basic_test.rs @@ -18,12 +18,13 @@ use rand_core::OsRng; use rand_core::RngCore; use zssp::application::{ - AcceptAction, ApplicationLayer, RatchetState, RatchetStates, RatchetUpdate, Settings, RATCHET_SIZE, IncomingSessionAction, + AcceptAction, ApplicationLayer, IncomingSessionAction, RatchetState, RatchetStates, RatchetUpdate, Settings, + RATCHET_SIZE, }; use zssp::crypto::P384KeyPair; use zssp::crypto_impl::*; -use zssp::Session; use zssp::result::ReceiveError; +use zssp::Session; const TEST_MTU: usize = 1500; @@ -71,7 +72,6 @@ impl ApplicationLayer for &TestApplication { type IncomingPacketBuffer = Vec; - fn incoming_session(&self) -> IncomingSessionAction { IncomingSessionAction::Allow } @@ -189,7 +189,7 @@ fn alice_main( |_| Some((|b: &mut [u8]| alice_out.send(b.to_vec()).is_ok(), TEST_MTU)), &0, pkt, - &mut output_data + &mut output_data, ) { Ok(Unassociated) => { //println!("[alice] ok"); @@ -234,13 +234,15 @@ 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.000005) as u32 { + if OsRng.next_u32() <= ((u32::MAX as f64) * 0.0000005) as u32 { alice_session = None; } if current_time >= next_service { - next_service = - current_time + context.service(alice_app, |_| Some((|b: &mut [u8]| alice_out.send(b.to_vec()).is_ok(), TEST_MTU))); + next_service = current_time + + context.service(alice_app, |_| { + Some((|b: &mut [u8]| alice_out.send(b.to_vec()).is_ok(), TEST_MTU)) + }); } } } @@ -278,7 +280,7 @@ fn bob_main( |_| Some((|b: &mut [u8]| bob_out.send(b.to_vec()).is_ok(), TEST_MTU)), &0, pkt, - &mut output_data + &mut output_data, ) { Ok(Unassociated) => {} Ok(Session(s, event)) => match event { @@ -290,7 +292,14 @@ fn bob_main( assert!(!output_data.is_empty()); //println!("[bob] received {}", output_data.len()); transferred += output_data.len() as u64 * 2; // *2 because we are also sending this many bytes back - context.send(&s, |b| bob_out.send(b.to_vec()).is_ok(), &mut [0u8; TEST_MTU], &output_data).unwrap(); + context + .send( + &s, + |b| bob_out.send(b.to_vec()).is_ok(), + &mut [0u8; TEST_MTU], + &output_data, + ) + .unwrap(); } Established => panic!(), Rejected => panic!(), @@ -319,7 +328,10 @@ fn bob_main( } if current_time >= next_service { - next_service = current_time + context.service(bob_app, |_| Some((|b: &mut [u8]| bob_out.send(b.to_vec()).is_ok(), TEST_MTU))); + next_service = current_time + + context.service(bob_app, |_| { + Some((|b: &mut [u8]| bob_out.send(b.to_vec()).is_ok(), TEST_MTU)) + }); } } } diff --git a/src/crypto/aes.rs b/src/crypto/aes.rs index 9935564..e0e3752 100644 --- a/src/crypto/aes.rs +++ b/src/crypto/aes.rs @@ -42,14 +42,14 @@ pub trait Aes256Dec: Send + Sync { pub trait AesGcmEncContext { fn encrypt(&mut self, input: &[u8], output: &mut [u8]); - fn finish(&mut self) -> [u8; AES_GCM_TAG_SIZE]; + fn finish(self) -> [u8; AES_GCM_TAG_SIZE]; } pub trait AesGcmDecContext { fn decrypt_in_place(&mut self, data: &mut [u8]); #[must_use] - fn finish(&mut self, tag: &[u8; AES_GCM_TAG_SIZE]) -> bool; + fn finish(self, tag: &[u8; AES_GCM_TAG_SIZE]) -> bool; } pub trait HighThroughputAesGcmPool: Send + Sync { diff --git a/src/crypto_impl/openssl.rs b/src/crypto_impl/openssl.rs index fb17bf9..596352f 100644 --- a/src/crypto_impl/openssl.rs +++ b/src/crypto_impl/openssl.rs @@ -75,8 +75,6 @@ impl CipherCtx { tag.as_mut_ptr() as *mut _, ) > 0 } - - /// Sets the authentication tag for verification during decryption. #[allow(unused)] pub unsafe fn set_tag(&self, tag: &[u8]) -> bool { EVP_CIPHER_CTX_ctrl( @@ -158,7 +156,7 @@ impl AesGcmEncContext for AesGcmOpenSSLEnc { unsafe { assert!(self.0.update::(input, output.as_mut_ptr())) }; } - fn finish(&mut self) -> [u8; AES_GCM_TAG_SIZE] { + fn finish(self) -> [u8; AES_GCM_TAG_SIZE] { let mut output = [0u8; AES_GCM_TAG_SIZE]; unsafe { assert!(self.0.finalize::()); @@ -175,26 +173,32 @@ impl AesGcmDecContext for AesGcmOpenSSLDec { unsafe { assert!(self.0.update::(data, p)) }; } - fn finish(&mut self, tag: &[u8; AES_GCM_TAG_SIZE]) -> bool { + fn finish(self, tag: &[u8; AES_GCM_TAG_SIZE]) -> bool { unsafe { self.0.set_tag(tag) && self.0.finalize::() } } } -pub struct AesGcmOpenSSLPool(Zeroizing<[u8; AES_256_KEY_SIZE]>, Zeroizing<[u8; AES_256_KEY_SIZE]>); +pub struct AesGcmOpenSSLPool { + enc_key: Zeroizing<[u8; AES_256_KEY_SIZE]>, + dec_key: Zeroizing<[u8; AES_256_KEY_SIZE]>, +} impl HighThroughputAesGcmPool for AesGcmOpenSSLPool { type EncContext<'a> = AesGcmOpenSSLEnc; type DecContext<'a> = AesGcmOpenSSLDec; fn new(encrypt_key: &[u8; AES_256_KEY_SIZE], decrypt_key: &[u8; AES_256_KEY_SIZE]) -> Self { - AesGcmOpenSSLPool(Zeroizing::new(*encrypt_key), Zeroizing::new(*decrypt_key)) + AesGcmOpenSSLPool { + enc_key: Zeroizing::new(*encrypt_key), + dec_key: Zeroizing::new(*decrypt_key), + } } fn start_enc<'a>(&'a self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> AesGcmOpenSSLEnc { let ctx = CipherCtx::new().unwrap(); unsafe { let t = openssl_sys::EVP_aes_256_gcm(); - assert!(ctx.cipher_init::(t, self.0.as_ptr(), nonce.as_ptr())); + assert!(ctx.cipher_init::(t, self.enc_key.as_ptr(), nonce.as_ptr())); openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); } AesGcmOpenSSLEnc(ctx) @@ -204,7 +208,7 @@ impl HighThroughputAesGcmPool for AesGcmOpenSSLPool { let ctx = CipherCtx::new().unwrap(); unsafe { let t = openssl_sys::EVP_aes_256_gcm(); - assert!(ctx.cipher_init::(t, self.0.as_ptr(), nonce.as_ptr())); + assert!(ctx.cipher_init::(t, self.dec_key.as_ptr(), nonce.as_ptr())); openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); } AesGcmOpenSSLDec(ctx) @@ -246,12 +250,12 @@ impl LowThroughputAesGcm for AesGcmOpenSSL { let ctx = CipherCtx::new().unwrap(); unsafe { let t = openssl_sys::EVP_aes_256_gcm(); - assert!(ctx.cipher_init::(t, key.as_ptr(), nonce.as_ptr())); + assert!(ctx.cipher_init::(t, key.as_ptr(), nonce.as_ptr())); openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); - assert!(ctx.update::(aad, ptr::null_mut())); + assert!(ctx.update::(aad, ptr::null_mut())); let p = data.as_mut_ptr(); - assert!(ctx.update::(data, p)); + assert!(ctx.update::(data, p)); ctx.set_tag(tag) && ctx.finalize::() } diff --git a/src/proto.rs b/src/proto.rs index 74ae800..8d20ca5 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -119,7 +119,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_HELLO_CHALLENGE_MIN_SIZE: usize = HANDSHAKE_HELLO_MIN_SIZE + CHALLENGE_SIZE; pub(crate) const HANDSHAKE_HELLO_CHALLENGE_MAX_SIZE: usize = HANDSHAKE_HELLO_MAX_SIZE + CHALLENGE_SIZE; @@ -145,7 +145,7 @@ pub(crate) const SESSION_REJECTED_SIZE: usize = AES_GCM_TAG_SIZE; pub(crate) const HEADERED_SESSION_REJECTED_SIZE: usize = SESSION_REJECTED_SIZE + HEADER_SIZE; pub(crate) const REKEY_SIZE: usize = P384_PUBLIC_KEY_SIZE + KID_SIZE + AES_GCM_TAG_SIZE + AES_GCM_TAG_SIZE; -pub(crate) const HEADERED_REKEY_SIZE: usize = P384_PUBLIC_KEY_SIZE + KID_SIZE + AES_GCM_TAG_SIZE + AES_GCM_TAG_SIZE; +pub(crate) const HEADERED_REKEY_SIZE: usize = REKEY_SIZE + HEADER_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. @@ -177,6 +177,6 @@ pub(crate) const MAX_UNASSOCIATED_FRAGMENTS: usize = 32 * 32; /// The maximum size a packet that is not associated to a session may be. /// Excludes the size of headers for fragmentation. -pub(crate) const MAX_UNASSOCIATED_PACKET_SIZE: usize = HANDSHAKE_HELLO_MAX_SIZE; +pub(crate) const MAX_UNASSOCIATED_PACKET_SIZE: usize = HANDSHAKE_HELLO_CHALLENGE_MAX_SIZE; pub(crate) const SESSION_MAX_FRAGMENTS_OOO: usize = 64; diff --git a/src/zeta.rs b/src/zeta.rs index 07f7c74..8bba2b6 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -185,10 +185,10 @@ pub(crate) enum ZetaAutomata { R1 { noise: SymmetricState, e_secret: App::KeyPair, - k1: ArrayVec, + k1: ArrayVec, }, R2 { - k2: ArrayVec, + k2: ArrayVec, }, } @@ -323,7 +323,6 @@ fn create_a1_state( let i = x1.len(); let (e1_secret, e1_public) = App::Kem::generate(rng.lock().unwrap().deref_mut()); x1.extend(e1_public); - x1.extend([0u8; AES_GCM_NONCE_SIZE]); let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, 0), &mut x1[i..]); x1.extend(tag); // Process message pattern 1 payload. @@ -466,7 +465,7 @@ pub(crate) fn received_x1_trans( // ... // -> e, es, e1 // <- e, ee, ekem1, psk - if !(HANDSHAKE_HELLO_CHALLENGE_MIN_SIZE..=HANDSHAKE_HELLO_CHALLENGE_MAX_SIZE).contains(&x1.len()) { + if !(HANDSHAKE_HELLO_MIN_SIZE..=HANDSHAKE_HELLO_MAX_SIZE).contains(&x1.len()) { return Err(byzantine_fault!(InvalidPacket, true)); } @@ -493,14 +492,14 @@ pub(crate) fn received_x1_trans( .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 1 e1 token. let j = i + KYBER_PUBLIC_KEY_SIZE; - let k = i + AES_GCM_TAG_SIZE; + let k = j + AES_GCM_TAG_SIZE; let tag = x1[j..k].try_into().unwrap(); if !noise.decrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, 0), &mut x1[i..j], tag) { return Err(byzantine_fault!(FailedAuth, true)); } let e1_start = i; let e1_end = j; - i = j; + i = k; // Process message pattern 1 payload. let k = x1.len(); let j = k - AES_GCM_TAG_SIZE; @@ -529,9 +528,10 @@ pub(crate) fn received_x1_trans( } RatchetState::empty() }; + let mut hk_recv = Zeroizing::new([0u8; HASHLEN]); let mut hk_send = Zeroizing::new([0u8; HASHLEN]); - noise.get_ask(hmac, LABEL_HEADER_KEY, &mut hk_recv, &mut hk_send); + noise.get_ask(hmac, LABEL_HEADER_KEY, &mut hk_send, &mut hk_recv); let mut x2 = ArrayVec::::new(); x2.extend([0u8; HEADER_SIZE]); @@ -660,7 +660,7 @@ pub(crate) fn received_x2_trans( } noise.mix_key(hmac, ekem1_secret.as_ref()); drop(ekem1_secret); - i = j; + i = k; // We attempt to decrypt the payload at most three times. First two times with // the ratchet key Alice remembers, and final time with a ratchet // key of zero if Alice allows ratchet downgrades. @@ -1705,19 +1705,22 @@ pub(crate) fn send_payload( let payload_mtu = mtu - HEADER_SIZE; debug_assert!(payload_mtu >= 4); - let fragment_count = payload.len().saturating_add(payload_mtu - 1) / payload_mtu; // Ceiling div. - let fragment_base_size = payload.len() / fragment_count; - let fragment_size_remainder = payload.len() % fragment_count; + let tagged_payload_len = payload.len() + AES_GCM_TAG_SIZE; + let fragment_count = tagged_payload_len.saturating_add(payload_mtu - 1) / payload_mtu; // Ceiling div. + let fragment_base_size = tagged_payload_len / fragment_count; + let fragment_size_remainder = tagged_payload_len % fragment_count; - mtu_sized_buffer[..KID_SIZE].copy_from_slice(&key.send.kid.unwrap().get().to_be_bytes()); - mtu_sized_buffer[FRAGMENT_COUNT_IDX] = fragment_count as u8; - mtu_sized_buffer[PACKET_NONCE_START..].copy_from_slice(&nonce[NONCE_SIZE_DIFF..]); + let mut header = [0u8; HEADER_SIZE]; + header[..KID_SIZE].copy_from_slice(&key.send.kid.unwrap().get().to_be_bytes()); + header[FRAGMENT_COUNT_IDX] = fragment_count as u8; + header[PACKET_NONCE_START..].copy_from_slice(&nonce[NONCE_SIZE_DIFF..]); let mut i = 0; - for fragment_no in 0..fragment_count { + for fragment_no in 0..fragment_count - 1 { let fragment_len = fragment_base_size + (fragment_no < fragment_size_remainder) as usize; let j = i + fragment_len; + mtu_sized_buffer[..HEADER_SIZE].copy_from_slice(&header); mtu_sized_buffer[FRAGMENT_NO_IDX] = fragment_no as u8; cipher.encrypt( &payload[i..j], @@ -1735,7 +1738,29 @@ pub(crate) fn send_payload( } i = j; } - drop(cipher); + let fragment_no = fragment_count - 1; + let payload_rem = payload.len() - i; + let fragment_len = payload_rem + AES_GCM_TAG_SIZE; + debug_assert_eq!(fragment_len, fragment_base_size); + + mtu_sized_buffer[..HEADER_SIZE].copy_from_slice(&header); + mtu_sized_buffer[FRAGMENT_NO_IDX] = fragment_no as u8; + cipher.encrypt( + &payload[i..], + &mut mtu_sized_buffer[HEADER_SIZE..HEADER_SIZE + payload_rem], + ); + mtu_sized_buffer[HEADER_SIZE + payload_rem..HEADER_SIZE + fragment_len].copy_from_slice(&cipher.finish()); + + state.hk_send.encrypt_in_place( + (&mut mtu_sized_buffer[HEADER_AUTH_START..HEADER_AUTH_END]) + .try_into() + .unwrap(), + ); + + if !send(&mtu_sized_buffer[..HEADER_SIZE + fragment_len]) { + return Ok(()); + } + drop(state); if should_rekey { @@ -1754,11 +1779,12 @@ pub(crate) fn receive_payload_in_place( session: &Arc>, state: RwLockReadGuard<'_, MutableState>, kid: NonZeroU32, - n: &[u8; AES_GCM_NONCE_SIZE], + nonce: &[u8; AES_GCM_NONCE_SIZE], fragments: &mut [App::IncomingPacketBuffer], mut output_buffer: impl Write, ) -> Result<(), ReceiveError> { use FaultType::*; + debug_assert!(!fragments.is_empty()); let specified_key = if Some(kid) == state.keys[0].recv.kid { state.keys[0].nk.as_ref() @@ -1768,25 +1794,27 @@ pub(crate) fn receive_payload_in_place( return Err(byzantine_fault!(OutOfSequence, true)); }; - let mut cipher = specified_key.ok_or(byzantine_fault!(OutOfSequence, true))?.start_dec(n); + let mut cipher = specified_key + .ok_or(byzantine_fault!(OutOfSequence, true))? + .start_dec(nonce); + let (_, c) = from_nonce(nonce); // NOTE: This only works because we check the size of every received fragment in the receive // function, otherwise this could panic. - let mut i = 0; - while i + 1 < fragments.len() { + for i in 0..fragments.len() - 1 { let fragment = &mut fragments[i].as_mut()[HEADER_SIZE..]; + debug_assert!(fragment.len() >= AES_GCM_TAG_SIZE); cipher.decrypt_in_place(fragment); - i += 1; } - let fragment = &mut fragments[i].as_mut()[HEADER_SIZE..]; - let tag_idx = fragment.len() - AES_GCM_NONCE_SIZE; + let fragment = &mut fragments[fragments.len() - 1].as_mut()[HEADER_SIZE..]; + debug_assert!(fragment.len() >= AES_GCM_TAG_SIZE); + let tag_idx = fragment.len() - AES_GCM_TAG_SIZE; cipher.decrypt_in_place(&mut fragment[..tag_idx]); - if cipher.finish((&fragment[tag_idx..]).try_into().unwrap()) { + + if !cipher.finish((&fragment[tag_idx..]).try_into().unwrap()) { return Err(byzantine_fault!(FailedAuth, true)); } - drop(cipher); - let (_, c) = from_nonce(n); if !session.window.update(c) { // This error is marked as not happening naturally, but it could occur if something about // the transport protocol is duplicating packets. diff --git a/src/zssp.rs b/src/zssp.rs index 29b90b3..17c24fe 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -106,8 +106,8 @@ fn send_with_fragmentation( let j = i + fragment_base_size + (fragment_no < fragment_size_remainder) as usize; let fragment = &mut headered_packet[i - HEADER_SIZE..j]; - header[FRAGMENT_NO_IDX] = fragment_no as u8; fragment[..HEADER_SIZE].copy_from_slice(&header); + fragment[FRAGMENT_NO_IDX] = fragment_no as u8; if let Some(hk_send) = hk_send { hk_send.encrypt_in_place((&mut fragment[HEADER_AUTH_START..HEADER_AUTH_END]).try_into().unwrap()); @@ -536,11 +536,11 @@ impl Context { return Err(byzantine_fault!(InvalidPacket, true)); } // Process recv challenge layer. + let challenge_start = assembled_packet.len() - CHALLENGE_SIZE; let hash = &mut App::Hash::new(); match app.incoming_session() { IncomingSessionAction::Allow => {} IncomingSessionAction::Challenge => { - let challenge_start = assembled_packet.len() - CHALLENGE_SIZE; let result = ctx.challenge.process_hello( hash, remote_address, @@ -569,9 +569,16 @@ impl Context { } // Process recv zeta layer. - received_x1_trans(&app, ctx, hash, &nonce, assembled_packet, |packet, hk_send| { - send_with_fragmentation(send_unassociated_reply, send_unassociated_mtu, packet, hk_send); - })?; + received_x1_trans( + &app, + ctx, + hash, + &nonce, + &mut assembled_packet[..challenge_start], + |packet, hk_send| { + send_with_fragmentation(send_unassociated_reply, send_unassociated_mtu, packet, hk_send); + }, + )?; log!(app, X1IsAuthSentX2); Ok(ReceiveOk::Unassociated) From de04653f606ead4947fe11b5fbdd2b6fb7efef0d Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 8 Aug 2023 14:52:47 -0400 Subject: [PATCH 26/50] refactored dependencies --- Cargo.toml | 7 +++---- examples/basic_test.rs | 8 -------- src/crypto/mod.rs | 1 + src/crypto_impl/mod.rs | 23 ++++++++++++++--------- src/frag_cache.rs | 8 -------- src/fragged.rs | 8 -------- src/handshake_cache.rs | 8 -------- src/lib.rs | 17 +++++------------ src/zssp.rs | 10 ---------- 9 files changed, 23 insertions(+), 67 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e291b16..48a75dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,12 +19,11 @@ pqc_kyber = { version = "0.6.0", default-features = false, features = ["kyber102 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 } -openssl-sys = { version = "0.9.91", default-features = false } +openssl-sys = { version = "0.9.91", default-features = false, optional = true } [features] -default = ["debug", "p384", "hmac", "pqc_kyber"] -sha2 = ["dep:sha2"] -hmac = ["dep:hmac", "sha2"] +default = ["debug", "p384", "sha2", "pqc_kyber", "openssl-sys"] +sha2 = ["dep:sha2", "dep:hmac"] logging = [] debug = ["logging"] diff --git a/examples/basic_test.rs b/examples/basic_test.rs index e1d1a35..8e45b0e 100644 --- a/examples/basic_test.rs +++ b/examples/basic_test.rs @@ -1,11 +1,3 @@ -/* 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; diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 16279f0..0bd3beb 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -14,6 +14,7 @@ pub use kyber1024::*; // exact version of them. pub use rand_core; pub use zeroize; +pub use arrayvec; /// Constant time byte slice equality. pub fn secure_eq + ?Sized, B: AsRef<[u8]> + ?Sized>(a: &A, b: &B) -> bool { diff --git a/src/crypto_impl/mod.rs b/src/crypto_impl/mod.rs index d414a06..cdd11cd 100644 --- a/src/crypto_impl/mod.rs +++ b/src/crypto_impl/mod.rs @@ -2,23 +2,28 @@ mod kyber1024; #[cfg(feature = "pqc_kyber")] pub use kyber1024::*; +#[cfg(feature = "pqc_kyber")] +pub use pqc_kyber; + #[cfg(feature = "p384")] mod p384_impl; #[cfg(feature = "p384")] pub use p384_impl::*; +#[cfg(feature = "p384")] +pub use p384; + #[cfg(feature = "sha2")] mod sha512; #[cfg(feature = "sha2")] pub use sha512::*; - -mod openssl; -pub use openssl::*; - -#[cfg(feature = "hmac")] +#[cfg(feature = "sha2")] pub use hmac; -#[cfg(feature = "p384")] -pub use p384; -#[cfg(feature = "pqc_kyber")] -pub use pqc_kyber; #[cfg(feature = "sha2")] pub use sha2; + +#[cfg(feature = "openssl-sys")] +mod openssl; +#[cfg(feature = "openssl-sys")] +pub use openssl::*; +#[cfg(feature = "openssl-sys")] +pub use openssl_sys; diff --git a/src/frag_cache.rs b/src/frag_cache.rs index 2423cda..e9f15b9 100644 --- a/src/frag_cache.rs +++ b/src/frag_cache.rs @@ -1,11 +1,3 @@ -/* 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::hash_map::RandomState; use std::hash::{BuildHasher, Hash, Hasher}; use std::mem::MaybeUninit; diff --git a/src/fragged.rs b/src/fragged.rs index 54b90d9..b3c293a 100644 --- a/src/fragged.rs +++ b/src/fragged.rs @@ -1,11 +1,3 @@ -/* 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 arrayvec::ArrayVec; use std::mem::{needs_drop, zeroed, MaybeUninit}; diff --git a/src/handshake_cache.rs b/src/handshake_cache.rs index 75b4aa4..3939cc7 100644 --- a/src/handshake_cache.rs +++ b/src/handshake_cache.rs @@ -1,11 +1,3 @@ -/* 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::num::NonZeroU32; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, RwLock}; diff --git a/src/lib.rs b/src/lib.rs index 626e58c..cb8e8c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,28 +9,21 @@ pub mod crypto; pub mod crypto_impl; mod antireplay; -pub mod application; mod challenge; mod frag_cache; mod fragged; mod handshake_cache; mod indexed_heap; - mod log_event; -pub use log_event::*; - -pub mod proto; mod ratchet_state; -pub mod result; mod symmetric_state; mod zeta; mod zssp; +pub mod proto; +pub mod result; +pub mod application; + +pub use log_event::*; pub use zeta::*; pub use zssp::*; -//pub mod error; -//pub use crate::applicationlayer::ApplicationLayer; -//pub use crate::log_event::LogEvent; -//pub use crate::proto::{IDENTITY_MAX_SIZE, MIN_PACKET_SIZE, MIN_TRANSPORT_MTU}; -//pub use crate::ratchet_state::RatchetState; -//pub use crate::zssp::{Context, ContextInner, IncomingSessionAction, ReceiveResult, Session, SessionEvent}; diff --git a/src/zssp.rs b/src/zssp.rs index 17c24fe..dd0961c 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -1,13 +1,3 @@ -/* 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/ -*/ -// ZSSP: ZeroTier Secure Session Protocol -// FIPS compliant Noise_XK with Jedi powers (Kyber1024) and built-in attack-resistant large payload (fragmentation) support. - use std::cmp::Reverse; use std::collections::HashMap; use std::hash::Hash; From 597e5b29b43499d315fba949b3ddf2ce0ebc3a59 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 8 Aug 2023 15:48:59 -0400 Subject: [PATCH 27/50] fixed wrong constant bug --- examples/basic_test.rs | 12 +++++++----- src/proto.rs | 2 +- src/zssp.rs | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/examples/basic_test.rs b/examples/basic_test.rs index 8e45b0e..537cc02 100644 --- a/examples/basic_test.rs +++ b/examples/basic_test.rs @@ -132,6 +132,7 @@ impl ApplicationLayer for &TestApplication { } } +#[allow(unused)] fn alice_main( run: &AtomicBool, packet_success_rate: u32, @@ -205,8 +206,8 @@ fn alice_main( } } } - } else if OsRng.next_u32() | 1 > 0 { - let _ = recursive_out.send(pkt); + //} else if OsRng.next_u32() | 1 > 0 { + // let _ = recursive_out.send(pkt); } } else { break; @@ -239,6 +240,7 @@ fn alice_main( } } +#[allow(unused)] fn bob_main( run: &AtomicBool, packet_success_rate: u32, @@ -304,8 +306,8 @@ fn bob_main( } } } - } else if OsRng.next_u32() | 1 > 0 { - let _ = recursive_out.try_send(pkt); + //} else if OsRng.next_u32() | 1 > 0 { + // let _ = recursive_out.try_send(pkt); } } @@ -387,7 +389,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/proto.rs b/src/proto.rs index 8d20ca5..e5a9249 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -92,7 +92,7 @@ pub(crate) const EXPIRE_AFTER_USES: u64 = 1 << 32 - 1; /// 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; +pub(crate) const COUNTER_WINDOW_MAX_OOO: usize = 128; /// 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; diff --git a/src/zssp.rs b/src/zssp.rs index dd0961c..95a2398 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -490,7 +490,7 @@ impl Context { } } - let mut buffer = ArrayVec::::new(); + let mut buffer = ArrayVec::::new(); let assembled_packet = if fragment_count > 1 { self.0.unassociated_defrag_cache.lock().unwrap().assemble( &nonce, From 23941c979765311e5831d9613987bdc6cf3a7381 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 8 Aug 2023 16:03:46 -0400 Subject: [PATCH 28/50] removed some key inits --- examples/basic_test.rs | 2 +- src/zeta.rs | 60 +++++++++++------------------------------- 2 files changed, 17 insertions(+), 45 deletions(-) diff --git a/examples/basic_test.rs b/examples/basic_test.rs index 537cc02..7befb32 100644 --- a/examples/basic_test.rs +++ b/examples/basic_test.rs @@ -389,7 +389,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/zeta.rs b/src/zeta.rs index 8bba2b6..5649a59 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -193,34 +193,6 @@ pub(crate) enum ZetaAutomata { } impl SymmetricState { - fn write_e( - &mut self, - hash: &mut App::Hash, - hmac: &mut App::Hmac, - rng: &Mutex, - packet: &mut ArrayVec, - ) -> App::KeyPair { - let e_secret = App::KeyPair::generate(rng.lock().unwrap().deref_mut()); - let pub_key = e_secret.public_key_bytes(); - packet.extend(pub_key); - self.mix_hash(hash, &pub_key); - self.mix_key(hmac, &pub_key); - e_secret - } - fn read_e( - &mut self, - hash: &mut App::Hash, - hmac: &mut App::Hmac, - i: &mut usize, - packet: &[u8], - ) -> Option { - let j = *i + P384_PUBLIC_KEY_SIZE; - let pub_key = &packet[*i..j]; - self.mix_hash(hash, pub_key); - self.mix_key(hmac, pub_key); - *i = j; - App::PublicKey::from_bytes((pub_key).try_into().unwrap()) - } fn write_e_no_init( &mut self, hash: &mut App::Hash, @@ -316,7 +288,7 @@ fn create_a1_state( noise.mix_hash(hash, &kid); noise.mix_hash(hash, &s_remote.to_bytes()); // Process message pattern 1 e token. - let e_secret = noise.write_e(hash, hmac, rng, &mut x1); + let e_secret = noise.write_e_no_init(hash, hmac, rng, &mut x1); // Process message pattern 1 es token. noise.mix_dh(hmac, &e_secret, s_remote)?; // Process message pattern 1 e1 token. @@ -484,7 +456,7 @@ pub(crate) fn received_x1_trans( i = j; // Process message pattern 1 e token. let e_remote = noise - .read_e(hash, hmac, &mut i, &x1) + .read_e_no_init(hash, hmac, &mut i, &x1) .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 1 es token. noise @@ -536,7 +508,7 @@ pub(crate) fn received_x1_trans( let mut x2 = ArrayVec::::new(); x2.extend([0u8; HEADER_SIZE]); // Process message pattern 2 e token. - let e_secret = noise.write_e(hash, hmac, &ctx.rng, &mut x2); + let e_secret = noise.write_e_no_init(hash, hmac, &ctx.rng, &mut x2); // Process message pattern 2 ee token. noise .mix_dh(hmac, &e_secret, &e_remote) @@ -554,7 +526,7 @@ pub(crate) fn received_x1_trans( x2.extend(ekem1); let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), &mut x2[i..]); x2.extend(tag); - noise.mix_key(hmac, ekem1_secret.as_ref()); + noise.mix_key_no_init(hmac, ekem1_secret.as_ref()); } // Process message pattern 2 psk2 token. noise.mix_key_and_hash(hash, hmac, ratchet_state.key.as_ref()); @@ -638,7 +610,7 @@ pub(crate) fn received_x2_trans( let mut i = 0; // Process message pattern 2 e token. let e_remote = noise - .read_e(hash, hmac, &mut i, &x2) + .read_e_no_init(hash, hmac, &mut i, &x2) .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 ee token. noise @@ -658,7 +630,7 @@ pub(crate) fn received_x2_trans( { return Err(byzantine_fault!(FailedAuth, true)); } - noise.mix_key(hmac, ekem1_secret.as_ref()); + noise.mix_key_no_init(hmac, ekem1_secret.as_ref()); drop(ekem1_secret); i = k; // We attempt to decrypt the payload at most three times. First two times with @@ -1262,11 +1234,11 @@ fn timeout_trans( noise.mix_hash(hash, &ctx.s_secret.public_key_bytes()); noise.mix_hash(hash, &session.s_remote.to_bytes()); // Process message pattern 1 psk0 token. - noise.mix_key_and_hash(hash, hmac, state.ratchet_state1.key.as_ref()); + noise.mix_key_and_hash_no_init(hash, hmac, state.ratchet_state1.key.as_ref()); // Process message pattern 1 e token. - let e_secret = noise.write_e(hash, hmac, &ctx.rng, &mut k1); + let e_secret = noise.write_e_no_init(hash, hmac, &ctx.rng, &mut k1); // Process message pattern 1 es token. - if noise.mix_dh(hmac, &e_secret, &session.s_remote).is_none() { + if noise.mix_dh_no_init(hmac, &e_secret, &session.s_remote).is_none() { return None; } // Process message pattern 1 ss token. @@ -1439,14 +1411,14 @@ pub(crate) fn received_k1_trans( noise.mix_hash(hash, &session.s_remote.to_bytes()); noise.mix_hash(hash, &ctx.s_secret.public_key_bytes()); // Process message pattern 1 psk0 token. - noise.mix_key_and_hash(hash, hmac, state.ratchet_state1.key.as_ref()); + noise.mix_key_and_hash_no_init(hash, hmac, state.ratchet_state1.key.as_ref()); // Process message pattern 1 e token. let e_remote = noise - .read_e(hash, hmac, &mut i, &k1) + .read_e_no_init(hash, hmac, &mut i, &k1) .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 1 es token. noise - .mix_dh(hmac, &ctx.s_secret, &e_remote) + .mix_dh_no_init(hmac, &ctx.s_secret, &e_remote) .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 1 ss token. noise.mix_key(hmac, session.noise_kk_ss.as_ref()); @@ -1463,10 +1435,10 @@ pub(crate) fn received_k1_trans( let mut k2 = ArrayVec::::new(); k2.extend([0u8; HEADER_SIZE]); // Process message pattern 2 e token. - let e_secret = noise.write_e(hash, hmac, &ctx.rng, &mut k2); + let e_secret = noise.write_e_no_init(hash, hmac, &ctx.rng, &mut k2); // Process message pattern 2 ee token. noise - .mix_dh(hmac, &e_secret, &e_remote) + .mix_dh_no_init(hmac, &e_secret, &e_remote) .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 se token. noise @@ -1590,11 +1562,11 @@ pub(crate) fn received_k2_trans( let hmac = &mut App::Hmac::new(); // Process message pattern 2 e token. let e_remote = noise - .read_e(hash, hmac, &mut i, &k2) + .read_e_no_init(hash, hmac, &mut i, &k2) .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 ee token. noise - .mix_dh(hmac, e_secret, &e_remote) + .mix_dh_no_init(hmac, e_secret, &e_remote) .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 se token. noise From 5cd2d8f45f8705646bd529915ef50f5a3cf0386f Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 9 Aug 2023 12:07:59 -0400 Subject: [PATCH 29/50] added downgrade warning --- examples/basic_test.rs | 16 +++++++--------- src/crypto/aes.rs | 12 ++++++++---- src/crypto/mod.rs | 2 +- src/crypto_impl/mod.rs | 8 ++++---- src/lib.rs | 8 ++++---- src/result.rs | 8 ++++++-- src/zeta.rs | 42 +++++++++++++++++++++--------------------- src/zssp.rs | 30 +++++++++++++++++++++--------- 8 files changed, 72 insertions(+), 54 deletions(-) diff --git a/examples/basic_test.rs b/examples/basic_test.rs index 7befb32..3bbf1f0 100644 --- a/examples/basic_test.rs +++ b/examples/basic_test.rs @@ -195,9 +195,8 @@ fn alice_main( assert!(!output_data.is_empty()); //println!("[alice] received {}", data.len()); } - NewSession => panic!(), - Rejected => panic!(), Control => (), + _ => panic!(), }, Err(e) => { println!("[alice] ERROR {:?}", e); @@ -206,8 +205,8 @@ fn alice_main( } } } - //} else if OsRng.next_u32() | 1 > 0 { - // let _ = recursive_out.send(pkt); + //} else if OsRng.next_u32() | 1 > 0 { + // let _ = recursive_out.send(pkt); } } else { break; @@ -278,7 +277,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); } @@ -295,9 +294,8 @@ fn bob_main( ) .unwrap(); } - Established => panic!(), - Rejected => panic!(), Control => (), + _ => panic!(), }, Err(e) => { println!("[bob] ERROR {:?}", e); @@ -306,8 +304,8 @@ fn bob_main( } } } - //} else if OsRng.next_u32() | 1 > 0 { - // let _ = recursive_out.try_send(pkt); + //} else if OsRng.next_u32() | 1 > 0 { + // let _ = recursive_out.try_send(pkt); } } diff --git a/src/crypto/aes.rs b/src/crypto/aes.rs index e0e3752..7921c30 100644 --- a/src/crypto/aes.rs +++ b/src/crypto/aes.rs @@ -10,12 +10,14 @@ pub const AES_GCM_NONCE_SIZE: usize = 12; /// algorithm is secure. /// /// Instances must securely delete their keys when dropped or reset. -pub trait Aes256Enc: Send + Sync { +pub trait Aes256Enc: Sized + Send + Sync { fn new(key: &[u8; AES_256_KEY_SIZE]) -> Self; /// Change the encryption key to `key` so that all future encryption is performed with it. /// This function is very rarely called so it does not have to be particularly efficient. - fn reset(&mut self, key: &[u8; AES_256_KEY_SIZE]); + fn reset(&mut self, key: &[u8; AES_256_KEY_SIZE]) { + *self = Self::new(key); + } /// Decrypt the given `block` of plaintext directly using the AES block cipher /// (i.e. AES-256 in zero-padding ECB mode). @@ -26,12 +28,14 @@ pub trait Aes256Enc: Send + Sync { /// A trait for decrypting individual blocks of plaintext using AES-256. /// /// Instances must securely delete their keys when dropped or reset. -pub trait Aes256Dec: Send + Sync { +pub trait Aes256Dec: Sized + Send + Sync { fn new(key: &[u8; AES_256_KEY_SIZE]) -> Self; /// Change the decryption key to `key` so that all future decryption is performed with it. /// This function is very rarely called so it does not have to be particularly efficient. - fn reset(&mut self, key: &[u8; AES_256_KEY_SIZE]); + fn reset(&mut self, key: &[u8; AES_256_KEY_SIZE]) { + *self = Self::new(key); + } /// Decrypt the given `block` of ciphertext directly using the AES 256 block cipher /// (i.e. AES-256 in zero-padding ECB mode). diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 0bd3beb..2b6ea78 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -12,9 +12,9 @@ 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 arrayvec; pub use rand_core; pub use zeroize; -pub use arrayvec; /// Constant time byte slice equality. pub fn secure_eq + ?Sized, B: AsRef<[u8]> + ?Sized>(a: &A, b: &B) -> bool { diff --git a/src/crypto_impl/mod.rs b/src/crypto_impl/mod.rs index cdd11cd..d481ff4 100644 --- a/src/crypto_impl/mod.rs +++ b/src/crypto_impl/mod.rs @@ -8,18 +8,18 @@ pub use pqc_kyber; #[cfg(feature = "p384")] mod p384_impl; #[cfg(feature = "p384")] -pub use p384_impl::*; -#[cfg(feature = "p384")] pub use p384; +#[cfg(feature = "p384")] +pub use p384_impl::*; #[cfg(feature = "sha2")] mod sha512; #[cfg(feature = "sha2")] -pub use sha512::*; -#[cfg(feature = "sha2")] pub use hmac; #[cfg(feature = "sha2")] pub use sha2; +#[cfg(feature = "sha2")] +pub use sha512::*; #[cfg(feature = "openssl-sys")] mod openssl; diff --git a/src/lib.rs b/src/lib.rs index cb8e8c0..caf7a4d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,10 +20,10 @@ mod symmetric_state; mod zeta; mod zssp; +pub mod application; pub mod proto; pub mod result; -pub mod application; -pub use log_event::*; -pub use zeta::*; -pub use zssp::*; +pub use crate::log_event::*; +pub use crate::zeta::*; +pub use crate::zssp::*; diff --git a/src/result.rs b/src/result.rs index f78e035..8c19d85 100644 --- a/src/result.rs +++ b/src/result.rs @@ -19,13 +19,15 @@ 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, + /// An invalid mtu was supplied to the function. The MTU can be no smaller than 128 bytes. + MtuTooSmall, /// 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. /// + /// The user can also explicitly cause this to occur by manually calling `expire` on a session. + /// /// The associated session will no longer send or receive data and must be immediately dropped. SessionExpired, @@ -144,6 +146,7 @@ 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, + 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. /// @@ -164,4 +167,5 @@ pub enum SessionEvent { Data, /// The received packet was some authentic protocol control packet. No action needs to be taken. Control, + DowngradedRatchetKey, } diff --git a/src/zeta.rs b/src/zeta.rs index 5649a59..3b077fb 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -580,7 +580,7 @@ pub(crate) fn received_x2_trans( n: &[u8; AES_GCM_NONCE_SIZE], x2: &mut [u8], send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), -) -> Result<(), ReceiveError> { +) -> Result> { use FaultType::*; // <- e, ee, ekem1, psk // -> s, se @@ -600,6 +600,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 mut result = (|| { let a1 = if let ZetaAutomata::A1(a1) = &state.beta { a1 @@ -672,7 +673,7 @@ 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; } } @@ -768,7 +769,7 @@ pub(crate) fn received_x2_trans( Ok(ref mut packet) => send(packet, Some(&session.state.read().unwrap().hk_send)), _ => {} } - result.map(|_| ()) + result.map(|_| should_warn_missing_ratchet) } fn send_control( session: &Arc>, @@ -800,7 +801,7 @@ pub(crate) fn received_x3_trans( kid: NonZeroU32, x3: &mut [u8], send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), -) -> Result>, ReceiveError> { +) -> Result<(Arc>, bool), ReceiveError> { use FaultType::*; // -> s, se if x3.len() < HANDSHAKE_COMPLETION_MIN_SIZE { @@ -868,9 +869,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(&mut create_reject(), Some(&App::PrpEnc::new(&zeta.hk_send))) @@ -963,7 +966,7 @@ pub(crate) fn received_x3_trans( send_control(&session, &state, PACKET_TYPE_KEY_CONFIRM, c1, send); drop(state); - Ok(session) + Ok((session, should_warn_missing_ratchet)) } Err(e) => Err(ReceiveError::StorageError(e)), } @@ -1650,30 +1653,27 @@ pub(crate) fn send_payload( ctx: &Arc>, session: &Arc>, payload: &[u8], - mut send: impl FnMut(&[u8]) -> bool, + mut send: impl FnMut(&mut [u8]) -> bool, mtu_sized_buffer: &mut [u8], ) -> Result<(), SendError> { use SendError::*; let mtu = mtu_sized_buffer.len(); if mtu < MIN_TRANSPORT_MTU { - return Err(InvalidParameter); + return Err(MtuTooSmall); } let state = session.state.read().unwrap(); - if matches!(&state.beta, ZetaAutomata::Null) { - return Err(SessionExpired); - } - if !matches!( - &state.beta, - ZetaAutomata::S1 | ZetaAutomata::S2 | ZetaAutomata::R1 { .. } | ZetaAutomata::R2 { .. } - ) { - return Err(SessionNotEstablished); - } let (c, should_rekey) = get_counter(session, &state).ok_or(SessionExpired)?; let nonce = to_nonce(PACKET_TYPE_DATA, c); let key = state.key_ref(false); - let mut cipher = key.nk.as_ref().unwrap().start_enc(&nonce); + let kid_send = key.send.kid.ok_or(SessionNotEstablished)?.get().to_be_bytes(); + let mut cipher = key.nk.as_ref().ok_or(SessionNotEstablished)?.start_enc(&nonce); + + debug_assert!(matches!( + &state.beta, + ZetaAutomata::S1 | ZetaAutomata::S2 | ZetaAutomata::R1 { .. } | ZetaAutomata::R2 { .. } + )); let payload_mtu = mtu - HEADER_SIZE; debug_assert!(payload_mtu >= 4); @@ -1683,7 +1683,7 @@ pub(crate) fn send_payload( let fragment_size_remainder = tagged_payload_len % fragment_count; let mut header = [0u8; HEADER_SIZE]; - header[..KID_SIZE].copy_from_slice(&key.send.kid.unwrap().get().to_be_bytes()); + header[..KID_SIZE].copy_from_slice(&kid_send); header[FRAGMENT_COUNT_IDX] = fragment_count as u8; header[PACKET_NONCE_START..].copy_from_slice(&nonce[NONCE_SIZE_DIFF..]); @@ -1705,7 +1705,7 @@ pub(crate) fn send_payload( .unwrap(), ); - if !send(&mtu_sized_buffer[..HEADER_SIZE + fragment_len]) { + if !send(&mut mtu_sized_buffer[..HEADER_SIZE + fragment_len]) { return Ok(()); } i = j; @@ -1729,7 +1729,7 @@ pub(crate) fn send_payload( .unwrap(), ); - if !send(&mtu_sized_buffer[..HEADER_SIZE + fragment_len]) { + if !send(&mut mtu_sized_buffer[..HEADER_SIZE + fragment_len]) { return Ok(()); } diff --git a/src/zssp.rs b/src/zssp.rs index 95a2398..92dbd63 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -338,7 +338,7 @@ impl Context { match packet_type { PACKET_TYPE_HANDSHAKE_RESPONSE => { log!(app, ReceivedRawX2); - received_x2_trans( + let should_warn_missing_ratchet = received_x2_trans( &app, ctx, &session, @@ -348,11 +348,15 @@ 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); - let result = received_c1_trans( + let just_established = received_c1_trans( &app, ctx, &session, @@ -362,7 +366,7 @@ impl Context { send_associated, )?; log!(app, KeyConfirmIsAuthSentAck(&session)); - if result { + if just_established { SessionEvent::Established } else { SessionEvent::Control @@ -466,11 +470,19 @@ impl Context { } log!(app, ReceivedRawX3); - let session = received_x3_trans(&app, ctx, zeta, kid_recv, assembled_packet, |packet, hk_send| { - send_with_fragmentation(send_unassociated_reply, send_unassociated_mtu, packet, hk_send); - })?; + let (session, should_warn_missing_ratchet) = + received_x3_trans(&app, ctx, zeta, kid_recv, assembled_packet, |packet, hk_send| { + send_with_fragmentation(send_unassociated_reply, send_unassociated_mtu, packet, hk_send); + })?; 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 { // This can occur naturally because either Bob's incoming_sessions cache got // full so Alice's incoming session was dropped, or the session this packet @@ -604,7 +616,7 @@ impl Context { pub fn send( &self, session: &Arc>, - send: impl FnMut(&[u8]) -> bool, + send: impl FnMut(&mut [u8]) -> bool, mtu_sized_buffer: &mut [u8], data: &[u8], ) -> Result<(), SendError> { From ac9d97f0c3b7610a11de52de281f11fba51ad094 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 9 Aug 2023 12:08:16 -0400 Subject: [PATCH 30/50] switched kids to ne bytes --- src/challenge.rs | 4 +- src/crypto/p384.rs | 4 +- src/zeta.rs | 227 +++++++++++++++++++++++---------------------- src/zssp.rs | 7 +- 4 files changed, 120 insertions(+), 122 deletions(-) diff --git a/src/challenge.rs b/src/challenge.rs index faabbce..09b14d4 100644 --- a/src/challenge.rs +++ b/src/challenge.rs @@ -16,7 +16,7 @@ pub struct ChallengeContext { /// Corresponds to Algorithm 11 found in Section 5. pub fn gen_null_response(rng: &mut impl RngCore) -> [u8; CHALLENGE_SIZE] { let mut response = [0u8; CHALLENGE_SIZE]; - response[POW_START..].copy_from_slice(&rng.next_u64().to_be_bytes()); + response[POW_START..].copy_from_slice(&rng.next_u64().to_ne_bytes()); response } /// Corresponds to Algorithm 13 found in Section 5. @@ -31,7 +31,7 @@ pub fn respond_to_challenge_in_place( let mut pow = rng.next_u64(); let mut work_buf = [0u8; SHA512_HASH_SIZE]; loop { - pre_response[POW_START..].copy_from_slice(&pow.to_be_bytes()); + pre_response[POW_START..].copy_from_slice(&pow.to_ne_bytes()); if verify_pow(hash, pre_response, &mut work_buf) { return; } diff --git a/src/crypto/p384.rs b/src/crypto/p384.rs index 647ac93..67f780a 100644 --- a/src/crypto/p384.rs +++ b/src/crypto/p384.rs @@ -35,9 +35,9 @@ pub trait P384KeyPair { /// 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`. + /// Perform ECDH key agreement, writing the raw (un-hashed!) ECDH secret to `ecdh_out`. /// - /// **CRITICAL**: This function must return `None` if key agreement between this private key and + /// **CRITICAL**: This function must return `false` 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 P-384 ECDH key agreement, or better yet use a peer reviewed /// library that has already implemented this correctly. diff --git a/src/zeta.rs b/src/zeta.rs index 3b077fb..f824eb0 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -23,58 +23,6 @@ use crate::zssp::{log, ContextInner, SessionQueue}; #[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())) -} -fn create_ratchet_state( - hmac: &mut App::Hmac, - noise: &SymmetricState, - pre_chain_len: u64, -) -> RatchetState { - let mut rk = Zeroizing::new([0u8; HASHLEN]); - let mut rf = Zeroizing::new([0u8; HASHLEN]); - noise.get_ask(hmac, LABEL_RATCHET_STATE, &mut rk, &mut rf); - RatchetState::new( - Zeroizing::new(rk[..RATCHET_SIZE].try_into().unwrap()), - Zeroizing::new(rf[..RATCHET_SIZE].try_into().unwrap()), - pre_chain_len + 1, - ) -} -fn get_counter(session: &Session, state: &MutableState) -> Option<(u64, bool)> { - if session.session_has_expired.load(Ordering::Relaxed) { - None - } else { - let c = session.send_counter.fetch_add(1, Ordering::Relaxed); - if c > THREAD_SAFE_COUNTER_HARD_EXPIRE { - session.session_has_expired.store(true, Ordering::SeqCst); - } - if c > state.key_creation_counter + EXPIRE_AFTER_USES { - session.session_has_expired.store(true, Ordering::SeqCst); - return None; - } - Some((c, c > state.key_creation_counter + App::SETTINGS.rekey_after_key_uses)) - } -} - /// Corresponds to the Zeta State Machine found in Section 4.1. pub struct Session { ctx: Weak>, @@ -131,34 +79,12 @@ pub(crate) struct DuplexKey { recv: Keys, nk: Option, } -impl Default for DuplexKey { - fn default() -> Self { - Self { send: Default::default(), recv: Default::default(), nk: None } - } -} -impl DuplexKey { - fn replace_nk(&mut self, nk_send: &[u8; HASHLEN], nk_recv: &[u8; HASHLEN]) { - self.nk = Some(App::AeadPool::new( - (&nk_send[..AES_256_KEY_SIZE]).try_into().unwrap(), - (&nk_recv[..AES_256_KEY_SIZE]).try_into().unwrap(), - )) - } -} #[derive(Default)] pub(crate) struct Keys { kek: Option>, kid: Option, } -impl Keys { - fn replace_kek(&mut self, kek: &[u8; HASHLEN]) { - // We want to give rust the best chance of implementing this in a way that does - // not leak the key on the stack. - self.kek - .get_or_insert(Zeroizing::new([0u8; AES_256_KEY_SIZE])) - .copy_from_slice(&kek[..AES_256_KEY_SIZE]); - } -} /// Corresponds to State A_1 of the Zeta State Machine found in Section 4.1. #[derive(Clone)] @@ -192,6 +118,38 @@ pub(crate) enum ZetaAutomata { }, } +impl Default for DuplexKey { + fn default() -> Self { + Self { send: Default::default(), recv: Default::default(), nk: None } + } +} +impl DuplexKey { + fn replace_nk(&mut self, nk_send: &[u8; HASHLEN], nk_recv: &[u8; HASHLEN]) { + self.nk = Some(App::AeadPool::new( + (&nk_send[..AES_256_KEY_SIZE]).try_into().unwrap(), + (&nk_recv[..AES_256_KEY_SIZE]).try_into().unwrap(), + )) + } +} +impl Keys { + fn replace_kek(&mut self, kek: &[u8; HASHLEN]) { + // We want to give rust the best chance of implementing this in a way that does + // not leak the key on the stack. + self.kek + .get_or_insert(Zeroizing::new([0u8; AES_256_KEY_SIZE])) + .copy_from_slice(&kek[..AES_256_KEY_SIZE]); + } +} + +impl MutableState { + fn key_ref(&self, is_next: bool) -> &DuplexKey { + &self.keys[(self.key_index ^ is_next) as usize] + } + fn key_mut(&mut self, is_next: bool) -> &mut DuplexKey { + &mut self.keys[(self.key_index ^ is_next) as usize] + } +} + impl SymmetricState { fn write_e_no_init( &mut self, @@ -241,6 +199,62 @@ 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())) +} +fn set_header(packet: &mut [u8], kid_send: u32, nonce: &[u8; AES_GCM_NONCE_SIZE]) { + packet[..KID_SIZE].copy_from_slice(&kid_send.to_ne_bytes()); + packet[PACKET_NONCE_START..HEADER_SIZE].copy_from_slice(&nonce[NONCE_SIZE_DIFF..]); +} +fn create_ratchet_state( + hmac: &mut App::Hmac, + noise: &SymmetricState, + pre_chain_len: u64, +) -> RatchetState { + let mut rk = Zeroizing::new([0u8; HASHLEN]); + let mut rf = Zeroizing::new([0u8; HASHLEN]); + noise.get_ask(hmac, LABEL_RATCHET_STATE, &mut rk, &mut rf); + RatchetState::new( + Zeroizing::new(rk[..RATCHET_SIZE].try_into().unwrap()), + Zeroizing::new(rf[..RATCHET_SIZE].try_into().unwrap()), + pre_chain_len + 1, + ) +} +fn get_counter(session: &Session, state: &MutableState) -> Option<(u64, bool)> { + if session.session_has_expired.load(Ordering::Relaxed) { + None + } else { + let c = session.send_counter.fetch_add(1, Ordering::Relaxed); + if c > THREAD_SAFE_COUNTER_HARD_EXPIRE { + session.session_has_expired.store(true, Ordering::SeqCst); + } + if c > state.key_creation_counter + EXPIRE_AFTER_USES { + session.session_has_expired.store(true, Ordering::SeqCst); + return None; + } + Some((c, c > state.key_creation_counter + App::SETTINGS.rekey_after_key_uses)) + } +} + /// Generate a random local key id that is currently unused. fn gen_kid(session_map: &HashMap, rng: &mut impl RngCore) -> NonZeroU32 { loop { @@ -251,19 +265,20 @@ fn gen_kid(session_map: &HashMap, rng: &mut impl RngCore) -> N } } } - -impl MutableState { - fn key_ref(&self, is_next: bool) -> &DuplexKey { - &self.keys[(self.key_index ^ is_next) as usize] - } - fn key_mut(&mut self, is_next: bool) -> &mut DuplexKey { - &mut self.keys[(self.key_index ^ is_next) as usize] - } -} - -fn set_header(packet: &mut [u8], kid_send: u32, nonce: &[u8; AES_GCM_NONCE_SIZE]) { - packet[..KID_SIZE].copy_from_slice(&kid_send.to_be_bytes()); - packet[PACKET_NONCE_START..HEADER_SIZE].copy_from_slice(&nonce[NONCE_SIZE_DIFF..]); +fn remap( + ctx: &Arc>, + session: &Arc>, + state: &MutableState, +) -> NonZeroU32 { + let mut session_map = ctx.session_map.write().unwrap(); + let weak = if let Some(Some(weak)) = state.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(), ctx.rng.lock().unwrap().deref_mut()); + session_map.insert(new_kid_recv, weak); + new_kid_recv } fn create_a1_state( @@ -283,7 +298,7 @@ fn create_a1_state( let mut x1 = ArrayVec::::new(); x1.extend([0u8; HEADER_SIZE]); // Noise process prologue. - let kid = kid_recv.get().to_be_bytes(); + let kid = kid_recv.get().to_ne_bytes(); x1.extend(kid); noise.mix_hash(hash, &kid); noise.mix_hash(hash, &s_remote.to_bytes()); @@ -450,7 +465,7 @@ pub(crate) fn received_x1_trans( // Noise process prologue. let j = i + KID_SIZE; noise.mix_hash(hash, &x1[i..j]); - let kid_send = NonZeroU32::new(u32::from_be_bytes(x1[i..j].try_into().unwrap())) + let kid_send = NonZeroU32::new(u32::from_ne_bytes(x1[i..j].try_into().unwrap())) .ok_or(byzantine_fault!(InvalidPacket, true))?; noise.mix_hash(hash, &ctx.s_secret.public_key_bytes()); i = j; @@ -537,12 +552,12 @@ pub(crate) fn received_x1_trans( ); let i = x2.len(); - x2.extend(kid_recv.get().to_be_bytes()); + x2.extend(kid_recv.get().to_ne_bytes()); let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), &mut x2[i..]); x2.extend(tag); 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]; @@ -654,7 +669,7 @@ pub(crate) fn received_x2_trans( if !noise.decrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), &mut payload, tag) { return None; } - NonZeroU32::new(u32::from_be_bytes(payload)).map(|kid2| (kid2, noise)) + NonZeroU32::new(u32::from_ne_bytes(payload)).map(|kid2| (kid2, noise)) }; // Check first key. let mut ratchet_i = 1; @@ -1248,7 +1263,7 @@ fn timeout_trans( noise.mix_key(hmac, session.noise_kk_ss.as_ref()); // Process message pattern 1 payload. let i = k1.len(); - k1.extend(new_kid_recv.get().to_be_bytes()); + k1.extend(new_kid_recv.get().to_ne_bytes()); let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_REKEY_INIT, 0), &mut k1[i..]); k1.extend(tag); @@ -1337,21 +1352,6 @@ pub(crate) fn process_timers( } } } -fn remap( - ctx: &Arc>, - session: &Arc>, - state: &MutableState, -) -> NonZeroU32 { - let mut session_map = ctx.session_map.write().unwrap(); - let weak = if let Some(Some(weak)) = state.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(), ctx.rng.lock().unwrap().deref_mut()); - session_map.insert(new_kid_recv, weak); - new_kid_recv -} /// Corresponds to Transition Algorithm 7 found in Section 4.3. pub(crate) fn received_k1_trans( app: &App, @@ -1432,7 +1432,7 @@ pub(crate) fn received_k1_trans( if !noise.decrypt_and_hash_in_place(hash, 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())) + let kid_send = NonZeroU32::new(u32::from_ne_bytes(k1[i..j].try_into().unwrap())) .ok_or(byzantine_fault!(FailedAuth, true))?; let mut k2 = ArrayVec::::new(); @@ -1450,7 +1450,7 @@ pub(crate) fn received_k1_trans( // Process message pattern 2 payload. let i = k2.len(); let new_kid_recv = remap(ctx, session, &state); - k2.extend(new_kid_recv.get().to_be_bytes()); + k2.extend(new_kid_recv.get().to_ne_bytes()); let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_REKEY_COMPLETE, 0), &mut k2[i..]); k2.extend(tag); @@ -1582,7 +1582,7 @@ pub(crate) fn received_k2_trans( if !noise.decrypt_and_hash_in_place(hash, 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())) + let kid_send = NonZeroU32::new(u32::from_ne_bytes(k2[i..j].try_into().unwrap())) .ok_or(byzantine_fault!(InvalidPacket, true))?; let new_ratchet_state = create_ratchet_state(hmac, &noise, state.ratchet_state1.chain_len); @@ -1667,7 +1667,7 @@ pub(crate) fn send_payload( let nonce = to_nonce(PACKET_TYPE_DATA, c); let key = state.key_ref(false); - let kid_send = key.send.kid.ok_or(SessionNotEstablished)?.get().to_be_bytes(); + let kid_send = key.send.kid.ok_or(SessionNotEstablished)?.get().to_ne_bytes(); let mut cipher = key.nk.as_ref().ok_or(SessionNotEstablished)?.start_enc(&nonce); debug_assert!(matches!( @@ -1763,7 +1763,8 @@ pub(crate) fn receive_payload_in_place( } else if Some(kid) == state.keys[1].recv.kid { state.keys[1].nk.as_ref() } else { - return Err(byzantine_fault!(OutOfSequence, true)); + // Should be unreachable unless we are leaking kids somewhere. + return Err(byzantine_fault!(UnknownLocalKeyId, true)); }; let mut cipher = specified_key diff --git a/src/zssp.rs b/src/zssp.rs index 92dbd63..0acc741 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -219,11 +219,9 @@ impl Context { let mut fragment_buffer = Assembled::new(); let kid_recv = incoming_fragment[0..KID_SIZE].try_into().unwrap(); - if let Some(kid_recv) = NonZeroU32::new(u32::from_be_bytes(kid_recv)) { - let session_map = self.0.session_map.read().unwrap(); + if let Some(kid_recv) = NonZeroU32::new(u32::from_ne_bytes(kid_recv)) { let session = ctx.session_map.read().unwrap().get(&kid_recv).map(|r| r.upgrade()); if let Some(Some(session)) = session { - drop(session_map); let state = session.state.read().unwrap(); state.hk_recv.decrypt_in_place( (&mut incoming_fragment[HEADER_AUTH_START..HEADER_AUTH_END]) @@ -417,7 +415,6 @@ impl Context { }; Ok(ReceiveOk::Session(session, ret)) } else { - drop(session_map); // Check for and handle PACKET_TYPE_ALICE_NOISE_XK_PATTERN_3 let zeta = self.0.unassociated_handshake_states.get(kid_recv); if let Some(zeta) = zeta { @@ -591,7 +588,7 @@ impl Context { return Err(byzantine_fault!(InvalidPacket, true)); } if let Some(kid_recv) = - NonZeroU32::new(u32::from_be_bytes(assembled_packet[..KID_SIZE].try_into().unwrap())) + NonZeroU32::new(u32::from_ne_bytes(assembled_packet[..KID_SIZE].try_into().unwrap())) { if let Some(Some(session)) = ctx.session_map.read().unwrap().get(&kid_recv).map(|r| r.upgrade()) { respond_to_challenge(ctx, &session, &assembled_packet[KID_SIZE..].try_into().unwrap()); From 9d9091f6556e78404fe0644da3953f2258c0eaa6 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 14 Aug 2023 11:05:33 -0400 Subject: [PATCH 31/50] refactored crypto --- examples/basic_test.rs | 47 ++--- src/application.rs | 45 ++--- src/handshake_cache.rs | 10 +- src/log_event.rs | 44 ++--- src/result.rs | 14 +- src/symmetric_state.rs | 34 ++-- src/zeta.rs | 421 +++++++++++++++++++++-------------------- src/zssp.rs | 84 ++++---- 8 files changed, 352 insertions(+), 347 deletions(-) diff --git a/examples/basic_test.rs b/examples/basic_test.rs index 3bbf1f0..f90a764 100644 --- a/examples/basic_test.rs +++ b/examples/basic_test.rs @@ -10,8 +10,8 @@ use rand_core::OsRng; use rand_core::RngCore; use zssp::application::{ - AcceptAction, ApplicationLayer, IncomingSessionAction, RatchetState, RatchetStates, RatchetUpdate, Settings, - RATCHET_SIZE, + AcceptAction, CryptoLayer, IncomingSessionAction, RatchetState, RatchetStates, RatchetUpdate, Settings, + RATCHET_SIZE, ApplicationLayer, }; use zssp::crypto::P384KeyPair; use zssp::crypto_impl::*; @@ -37,7 +37,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, @@ -59,24 +59,27 @@ impl ApplicationLayer for &TestApplication { type KeyPair = P384CrateKeyPair; type Kem = RustKyber1024PrivateKey; - type StorageError = std::convert::Infallible; type SessionData = u128; type IncomingPacketBuffer = Vec; +} +#[allow(unused)] +impl ApplicationLayer for &TestApplication { + type Crypto = TestApplication; - fn incoming_session(&self) -> IncomingSessionAction { + fn incoming_session(&mut self) -> IncomingSessionAction { IncomingSessionAction::Allow } - fn hello_requires_recognized_ratchet(&self) -> bool { + 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, @@ -85,28 +88,28 @@ impl ApplicationLayer for &TestApplication { } fn restore_by_fingerprint( - &self, + &mut self, ratchet_fingerprint: &[u8; RATCHET_SIZE], - ) -> Result, Self::StorageError> { + ) -> Result, ()> { let ratchets = self.ratchets.lock().unwrap(); Ok(ratchets.rf_map.get(ratchet_fingerprint).cloned()) } fn restore_by_identity( - &self, - remote_static_key: &Self::PublicKey, - session_data: &Self::SessionData, - ) -> Result, Self::StorageError> { + &mut self, + remote_static_key: &P384CratePublicKey, + session_data: &u128, + ) -> Result, ()> { let ratchets = self.ratchets.lock().unwrap(); Ok(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> { + ) -> Result<(), ()> { let mut ratchets = self.ratchets.lock().unwrap(); ratchets.peer_map.insert(*session_data, update_data.to_states()); @@ -123,11 +126,11 @@ impl ApplicationLayer for &TestApplication { Ok(()) } - fn time(&self) -> i64 { + fn time(&mut self) -> i64 { self.time.elapsed().as_millis() as i64 } - fn event_log(&self, event: zssp::LogEvent) { + fn event_log(&mut self, event: zssp::LogEvent) { println!(">[{}] {:?}", self.name, event); } } @@ -144,7 +147,7 @@ fn alice_main( bob_pubkey: P384CratePublicKey, ) { let startup_time = std::time::Instant::now(); - let context = zssp::Context::<&TestApplication>::new(alice_keypair, OsRng); + let context = zssp::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; @@ -250,7 +253,7 @@ fn bob_main( bob_keypair: P384CrateKeyPair, ) { let startup_time = std::time::Instant::now(); - let context = zssp::Context::<&TestApplication>::new(bob_keypair, OsRng); + let context = zssp::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; diff --git a/src/application.rs b/src/application.rs index d05aeb8..05bb201 100644 --- a/src/application.rs +++ b/src/application.rs @@ -90,7 +90,7 @@ impl Default for Settings { /// and negotiation timeout behavior. Both sides of a ZSSP session **must** have these constants /// set to the same values. Changing these constants is generally discouraged unless you know /// what you are doing. -pub trait ApplicationLayer: Sized { +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. @@ -129,9 +129,6 @@ pub trait ApplicationLayer: Sized { /// for ZSSP to achieve FIPS compliance. type Kem: Kyber1024PrivateKey; - /// A user-defined error returned when the `ApplicationLayer` fails to access persistent storage - /// for a peer's ratchet states. - type StorageError: std::error::Error; /// Type for arbitrary opaque object for use by the application that is attached to /// each session. @@ -144,14 +141,18 @@ pub trait ApplicationLayer: Sized { /// hold these for a short period of time when assembling fragmented packets on the receive /// path. type IncomingPacketBuffer: AsRef<[u8]> + AsMut<[u8]>; +} + +pub trait ApplicationLayer: Sized { + type Crypto: CryptoLayer; /// 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 /// should rekey. - fn time(&self) -> i64; + fn time(&mut self) -> i64; - fn incoming_session(&self) -> IncomingSessionAction; + fn incoming_session(&mut self) -> IncomingSessionAction; /// 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 /// then on they should be using non-empty ratchet states. @@ -161,7 +162,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. @@ -177,14 +178,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: &::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 @@ -193,9 +194,9 @@ 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>; + ) -> Result, ()>; /// 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. @@ -211,10 +212,10 @@ 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, - remote_static_key: &Self::PublicKey, - session_data: &Self::SessionData, - ) -> Result, Self::StorageError>; + &mut self, + remote_static_key: &::PublicKey, + session_data: &::SessionData, + ) -> Result, ()>; /// 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. @@ -232,17 +233,17 @@ 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, - remote_static_key: &Self::PublicKey, - session_data: &Self::SessionData, + &mut self, + remote_static_key: &::PublicKey, + session_data: &::SessionData, update_data: RatchetUpdate<'_>, - ) -> Result<(), Self::StorageError>; + ) -> Result<(), ()>; /// 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 /// nothing else. Do not base protocol-level decisions upon the events passed to this function. #[cfg(feature = "logging")] - fn event_log(&self, event: crate::LogEvent<'_, Self>); + fn event_log(&mut self, event: crate::LogEvent<'_, Self::Crypto>); } #[derive(Debug, PartialEq, Eq, Clone)] @@ -256,10 +257,10 @@ pub enum IncomingSessionAction { /// 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/handshake_cache.rs b/src/handshake_cache.rs index 3939cc7..1425a9b 100644 --- a/src/handshake_cache.rs +++ b/src/handshake_cache.rs @@ -3,23 +3,23 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, RwLock}; use crate::zeta::StateB2; -use crate::{application::ApplicationLayer, proto::MAX_UNASSOCIATED_HANDSHAKE_STATES}; +use crate::{application::CryptoLayer, proto::MAX_UNASSOCIATED_HANDSHAKE_STATES}; -pub(crate) struct UnassociatedHandshakeCache { +pub(crate) struct UnassociatedHandshakeCache { has_pending: AtomicBool, // Allowed to be falsely positive cache: RwLock>, } /// SoA format -struct CacheInner { +struct CacheInner { local_ids: [Option; MAX_UNASSOCIATED_HANDSHAKE_STATES], timeouts: [i64; MAX_UNASSOCIATED_HANDSHAKE_STATES], - handshakes: [Option>>; MAX_UNASSOCIATED_HANDSHAKE_STATES], + handshakes: [Option>>; MAX_UNASSOCIATED_HANDSHAKE_STATES], } /// Linear-search cache for capping the memory consumption of handshake data. /// Designed specifically to have short and simple code that clearly bounds above /// memory consumption. -impl UnassociatedHandshakeCache { +impl UnassociatedHandshakeCache { pub(crate) fn new() -> Self { Self { has_pending: AtomicBool::new(false), diff --git a/src/log_event.rs b/src/log_event.rs index 283ae3a..3671abd 100644 --- a/src/log_event.rs +++ b/src/log_event.rs @@ -1,22 +1,22 @@ use std::sync::Arc; -use crate::application::ApplicationLayer; +use crate::application::CryptoLayer; use crate::zeta::Session; /// ZSSP events that might be interesting to log or aggregate into metrics. -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, @@ -24,24 +24,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 8c19d85..a972597 100644 --- a/src/result.rs +++ b/src/result.rs @@ -1,18 +1,18 @@ use std::sync::Arc; -use crate::application::ApplicationLayer; +use crate::application::CryptoLayer; use crate::zeta::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, IdentityTooLarge, - RatchetIoError(IoError), + RatchetStorageError, } /// An error that can occur when attempting to send data over a session. @@ -64,7 +64,7 @@ pub enum FaultType { /// An error that occurred during the receipt of a given packet. #[derive(Debug)] -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. @@ -107,7 +107,7 @@ pub enum ReceiveError { Rejected, /// One of the ratchet saving or lookup functions returned an error, so the packet had to be /// dropped. - StorageError(StorageError), + RatchetStorageError, IoError(std::io::Error), } @@ -128,13 +128,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 71cf4d0..5981e3c 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>, + _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 @@ -40,7 +40,7 @@ impl SymmetricState { /// Corresponds to Noise `HKDF`. fn kbkdf( &self, - hmac: &mut App::Hmac, + hmac: &mut Crypto::Hmac, input_key_material: &[u8], label: &[u8; 4], num_outputs: u16, @@ -86,7 +86,7 @@ impl SymmetricState { } } /// Corresponds to Noise `MixKey`. - pub fn mix_key(&mut self, hmac: &mut App::Hmac, input_key_material: &[u8]) { + pub fn mix_key(&mut self, hmac: &mut Crypto::Hmac, input_key_material: &[u8]) { let mut next_ck = Zeroizing::new([0u8; HASHLEN]); let mut temp_k = Zeroizing::new([0u8; HASHLEN]); @@ -104,7 +104,7 @@ impl SymmetricState { self.k.clone_from_slice(&temp_k[..AES_256_KEY_SIZE]); } /// Corresponds to Noise `MixKey`. - pub fn mix_key_no_init(&mut self, hmac: &mut App::Hmac, input_key_material: &[u8]) { + pub fn mix_key_no_init(&mut self, hmac: &mut Crypto::Hmac, input_key_material: &[u8]) { let mut next_ck = Zeroizing::new([0u8; HASHLEN]); self.kbkdf(hmac, input_key_material, LABEL_KBKDF_CHAIN, 2, &mut next_ck, None, None); @@ -112,13 +112,13 @@ impl SymmetricState { *self.ck = *next_ck; } /// Corresponds to Noise `MixHash`. - pub fn mix_hash(&mut self, hash: &mut App::Hash, data: &[u8]) { + pub fn mix_hash(&mut self, hash: &mut Crypto::Hash, data: &[u8]) { hash.update(&self.h); hash.update(data); hash.finish_and_reset(&mut self.h); } /// Corresponds to Noise `MixKeyAndHash`. - pub fn mix_key_and_hash(&mut self, hash: &mut App::Hash, hmac: &mut App::Hmac, input_key_material: &[u8]) { + pub fn mix_key_and_hash(&mut self, hash: &mut Crypto::Hash, hmac: &mut Crypto::Hmac, input_key_material: &[u8]) { let mut next_ck = Zeroizing::new([0u8; HASHLEN]); let mut temp_h = [0u8; HASHLEN]; let mut temp_k = Zeroizing::new([0u8; HASHLEN]); @@ -138,7 +138,7 @@ impl SymmetricState { self.k.clone_from_slice(&temp_k[..AES_256_KEY_SIZE]); } /// Corresponds to Noise `MixKeyAndHash`. - pub fn mix_key_and_hash_no_init(&mut self, hash: &mut App::Hash, hmac: &mut App::Hmac, input_key_material: &[u8]) { + pub fn mix_key_and_hash_no_init(&mut self, hash: &mut Crypto::Hash, hmac: &mut Crypto::Hmac, input_key_material: &[u8]) { let mut next_ck = Zeroizing::new([0u8; HASHLEN]); let mut temp_h = [0u8; HASHLEN]; @@ -159,11 +159,11 @@ impl SymmetricState { #[must_use] pub fn encrypt_and_hash_in_place( &mut self, - hash: &mut App::Hash, + hash: &mut Crypto::Hash, iv: [u8; AES_GCM_NONCE_SIZE], data: &mut [u8], ) -> [u8; AES_GCM_TAG_SIZE] { - let tag = App::Aead::encrypt_in_place(&self.k, &iv, &self.h, data); + let tag = Crypto::Aead::encrypt_in_place(&self.k, &iv, &self.h, data); hash.update(&self.h); hash.update(data); hash.update(&tag); @@ -174,7 +174,7 @@ impl SymmetricState { #[must_use] pub fn decrypt_and_hash_in_place( &mut self, - hash: &mut App::Hash, + hash: &mut Crypto::Hash, iv: [u8; AES_GCM_NONCE_SIZE], data: &mut [u8], tag: [u8; AES_GCM_TAG_SIZE], @@ -182,19 +182,19 @@ impl SymmetricState { hash.update(&self.h); hash.update(data); hash.update(&tag); - let is_auth = App::Aead::decrypt_in_place(&self.k, &iv, &self.h, data, tag.as_ref().try_into().unwrap()); + let is_auth = Crypto::Aead::decrypt_in_place(&self.k, &iv, &self.h, data, tag.as_ref().try_into().unwrap()); hash.finish_and_reset(&mut self.h); is_auth } /// Corresponds to Noise `Split`. - pub fn split(self, hmac: &mut App::Hmac, key1: &mut [u8; HASHLEN], key2: &mut [u8; HASHLEN]) { + pub fn split(self, hmac: &mut Crypto::Hmac, key1: &mut [u8; HASHLEN], key2: &mut [u8; HASHLEN]) { self.kbkdf(hmac, &[], LABEL_KBKDF_CHAIN, 2, key1, Some(key2), None); } /// Get an additional symmetric key (ASK) that is a collision resistant hash of the transcript, /// is forward secrect and is cryptographically independent from all other produced keys. /// Based on Noise's unstable ASK mechanism, using KBKDF instead of HKDF. /// https://github.com/noiseprotocol/noise_wiki/wiki/Additional-Symmetric-Keys. - pub fn get_ask(&self, hmac: &mut App::Hmac, label: &[u8; 4], key1: &mut [u8; HASHLEN], key2: &mut [u8; HASHLEN]) { + pub fn get_ask(&self, hmac: &mut Crypto::Hmac, label: &[u8; 4], key1: &mut [u8; HASHLEN], key2: &mut [u8; HASHLEN]) { self.kbkdf(hmac, &self.h, label, 2, key1, Some(key2), None); } /// Used for internally debugging a key exchange. diff --git a/src/zeta.rs b/src/zeta.rs index f824eb0..24c3dbf 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -24,60 +24,60 @@ use crate::zssp::{log, ContextInner, SessionQueue}; use crate::LogEvent::*; /// Corresponds to the Zeta State Machine found in Section 4.1. -pub struct Session { - ctx: Weak>, +pub struct Session { + 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, queue_idx: BinaryHeapIndex, - pub(crate) s_remote: App::PublicKey, + pub(crate) s_remote: Crypto::PublicKey, send_counter: AtomicU64, session_has_expired: AtomicBool, pub window: Window, - pub(crate) defrag: [Mutex>; SESSION_MAX_FRAGMENTS_OOO], + pub(crate) defrag: [Mutex>; SESSION_MAX_FRAGMENTS_OOO], /// `session_queue -> state_machine_lock -> state -> session_map` state_machine_lock: Mutex<()>, /// `session_queue -> state_machine_lock -> state -> session_map` - pub(crate) state: RwLock>, + pub(crate) state: RwLock>, /// Pre-computed rekeying value. noise_kk_ss: Zeroizing<[u8; P384_ECDH_SHARED_SECRET_SIZE]>, } -pub(crate) struct MutableState { +pub(crate) struct MutableState { ratchet_state1: RatchetState, ratchet_state2: Option, - pub(crate) hk_send: App::PrpEnc, - pub(crate) hk_recv: App::PrpDec, + pub(crate) hk_send: Crypto::PrpEnc, + pub(crate) hk_recv: Crypto::PrpDec, key_creation_counter: u64, key_index: bool, - keys: [DuplexKey; 2], + keys: [DuplexKey; 2], resend_timer: AtomicI64, timeout_timer: i64, - pub(crate) beta: ZetaAutomata, + pub(crate) beta: ZetaAutomata, } /// 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]>, pub hk_recv: Zeroizing<[u8; AES_256_KEY_SIZE]>, - e_secret: App::KeyPair, - noise: SymmetricState, - pub defrag: Mutex>, + e_secret: Crypto::KeyPair, + noise: SymmetricState, + pub defrag: Mutex>, } -pub(crate) struct DuplexKey { +pub(crate) struct DuplexKey { send: Keys, recv: Keys, - nk: Option, + nk: Option, } #[derive(Default)] @@ -88,10 +88,10 @@ pub(crate) struct Keys { /// 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: ArrayVec, x1: ArrayVec, } @@ -102,15 +102,15 @@ pub(crate) struct StateA3 { } /// Corresponds to the ZKE Automata found in Section 4.1 - Definition 2. -pub(crate) enum ZetaAutomata { +pub(crate) enum ZetaAutomata { Null, - A1(Box>), + A1(Box>), A3(Box), S1, S2, R1 { - noise: SymmetricState, - e_secret: App::KeyPair, + noise: SymmetricState, + e_secret: Crypto::KeyPair, k1: ArrayVec, }, R2 { @@ -118,14 +118,14 @@ pub(crate) enum ZetaAutomata { }, } -impl Default for DuplexKey { +impl Default for DuplexKey { fn default() -> Self { Self { send: Default::default(), recv: Default::default(), nk: None } } } -impl DuplexKey { +impl DuplexKey { fn replace_nk(&mut self, nk_send: &[u8; HASHLEN], nk_recv: &[u8; HASHLEN]) { - self.nk = Some(App::AeadPool::new( + self.nk = Some(Crypto::AeadPool::new( (&nk_send[..AES_256_KEY_SIZE]).try_into().unwrap(), (&nk_recv[..AES_256_KEY_SIZE]).try_into().unwrap(), )) @@ -141,24 +141,24 @@ impl Keys { } } -impl MutableState { - fn key_ref(&self, is_next: bool) -> &DuplexKey { +impl MutableState { + fn key_ref(&self, is_next: bool) -> &DuplexKey { &self.keys[(self.key_index ^ is_next) as usize] } - fn key_mut(&mut self, is_next: bool) -> &mut DuplexKey { + fn key_mut(&mut self, is_next: bool) -> &mut DuplexKey { &mut self.keys[(self.key_index ^ is_next) as usize] } } -impl SymmetricState { +impl SymmetricState { fn write_e_no_init( &mut self, - hash: &mut App::Hash, - hmac: &mut App::Hmac, - rng: &Mutex, + hash: &mut Crypto::Hash, + hmac: &mut Crypto::Hmac, + rng: &Mutex, packet: &mut ArrayVec, - ) -> App::KeyPair { - let e_secret = App::KeyPair::generate(rng.lock().unwrap().deref_mut()); + ) -> Crypto::KeyPair { + let e_secret = Crypto::KeyPair::generate(rng.lock().unwrap().deref_mut()); let pub_key = e_secret.public_key_bytes(); packet.extend(pub_key); self.mix_hash(hash, &pub_key); @@ -167,19 +167,19 @@ impl SymmetricState { } fn read_e_no_init( &mut self, - hash: &mut App::Hash, - hmac: &mut App::Hmac, + hash: &mut Crypto::Hash, + hmac: &mut Crypto::Hmac, i: &mut usize, packet: &[u8], - ) -> Option { + ) -> Option { let j = *i + P384_PUBLIC_KEY_SIZE; let pub_key = &packet[*i..j]; self.mix_hash(hash, pub_key); self.mix_key_no_init(hmac, 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, hmac: &mut App::Hmac, secret: &App::KeyPair, remote: &App::PublicKey) -> Option<()> { + fn mix_dh(&mut self, hmac: &mut Crypto::Hmac, secret: &Crypto::KeyPair, remote: &Crypto::PublicKey) -> Option<()> { let mut ecdh_secret = Zeroizing::new([0u8; P384_ECDH_SHARED_SECRET_SIZE]); if secret.agree(&remote, &mut ecdh_secret) { self.mix_key(hmac, ecdh_secret.as_ref()); @@ -188,7 +188,7 @@ impl SymmetricState { None } } - fn mix_dh_no_init(&mut self, hmac: &mut App::Hmac, secret: &App::KeyPair, remote: &App::PublicKey) -> Option<()> { + fn mix_dh_no_init(&mut self, hmac: &mut Crypto::Hmac, secret: &Crypto::KeyPair, remote: &Crypto::PublicKey) -> Option<()> { let mut ecdh_secret = Zeroizing::new([0u8; P384_ECDH_SHARED_SECRET_SIZE]); if secret.agree(&remote, &mut ecdh_secret) { self.mix_key_no_init(hmac, ecdh_secret.as_ref()); @@ -225,9 +225,9 @@ fn set_header(packet: &mut [u8], kid_send: u32, nonce: &[u8; AES_GCM_NONCE_SIZE] packet[..KID_SIZE].copy_from_slice(&kid_send.to_ne_bytes()); packet[PACKET_NONCE_START..HEADER_SIZE].copy_from_slice(&nonce[NONCE_SIZE_DIFF..]); } -fn create_ratchet_state( - hmac: &mut App::Hmac, - noise: &SymmetricState, +fn create_ratchet_state( + hmac: &mut Crypto::Hmac, + noise: &SymmetricState, pre_chain_len: u64, ) -> RatchetState { let mut rk = Zeroizing::new([0u8; HASHLEN]); @@ -239,7 +239,7 @@ fn create_ratchet_state( pre_chain_len + 1, ) } -fn get_counter(session: &Session, state: &MutableState) -> Option<(u64, bool)> { +fn get_counter(session: &Session, state: &MutableState) -> Option<(u64, bool)> { if session.session_has_expired.load(Ordering::Relaxed) { None } else { @@ -251,7 +251,7 @@ fn get_counter(session: &Session, state: &MutableSta session.session_has_expired.store(true, Ordering::SeqCst); return None; } - Some((c, c > state.key_creation_counter + App::SETTINGS.rekey_after_key_uses)) + Some((c, c > state.key_creation_counter + Crypto::SETTINGS.rekey_after_key_uses)) } } @@ -265,10 +265,10 @@ fn gen_kid(session_map: &HashMap, rng: &mut impl RngCore) -> N } } } -fn remap( - ctx: &Arc>, - session: &Arc>, - state: &MutableState, +fn remap( + ctx: &Arc>, + session: &Arc>, + state: &MutableState, ) -> NonZeroU32 { let mut session_map = ctx.session_map.write().unwrap(); let weak = if let Some(Some(weak)) = state.key_ref(true).recv.kid.as_ref().map(|kid| session_map.remove(kid)) { @@ -281,20 +281,20 @@ fn remap( new_kid_recv } -fn create_a1_state( - hash: &mut App::Hash, - hmac: &mut App::Hmac, - rng: &Mutex, - s_remote: &App::PublicKey, +fn create_a1_state( + hash: &mut Crypto::Hash, + hmac: &mut Crypto::Hmac, + rng: &Mutex, + s_remote: &Crypto::PublicKey, kid_recv: NonZeroU32, ratchet_state1: &RatchetState, ratchet_state2: Option<&RatchetState>, identity: &[u8], -) -> 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 = ArrayVec::::new(); x1.extend([0u8; HEADER_SIZE]); // Noise process prologue. @@ -308,7 +308,7 @@ fn create_a1_state( noise.mix_dh(hmac, &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) = Crypto::Kem::generate(rng.lock().unwrap().deref_mut()); x1.extend(e1_public); let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, 0), &mut x1[i..]); x1.extend(tag); @@ -339,25 +339,25 @@ fn create_a1_state( })) } /// Corresponds to Transition Algorithm 1 found in Section 4.3. -pub(crate) fn trans_to_a1( - app: App, - ctx: &Arc>, - s_remote: App::PublicKey, - session_data: App::SessionData, +pub(crate) fn trans_to_a1>( + mut app: App, + ctx: &Arc>, + s_remote: Crypto::PublicKey, + session_data: Crypto::SessionData, identity: &[u8], - send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), -) -> Result>, OpenError> { + send: impl FnOnce(&mut [u8], Option<&Crypto::PrpEnc>), +) -> Result>, OpenError> { let RatchetStates { state1, state2 } = app .restore_by_identity(&s_remote, &session_data) - .map_err(|e| OpenError::RatchetIoError(e))? + .map_err(|_| OpenError::RatchetStorageError)? .unwrap_or_default(); let mut session_queue = ctx.session_queue.lock().unwrap(); let mut session_map = ctx.session_map.write().unwrap(); let kid_recv = gen_kid(session_map.deref(), ctx.rng.lock().unwrap().deref_mut()); - let hash = &mut App::Hash::new(); - let hmac = &mut App::Hmac::new(); + let hash = &mut Crypto::Hash::new(); + let hmac = &mut Crypto::Hmac::new(); let a1 = create_a1_state( hash, hmac, @@ -383,7 +383,7 @@ pub(crate) fn trans_to_a1( let current_time = app.time(); let queue_idx = session_queue.reserve_index(); - let resend_timer = current_time + App::SETTINGS.resend_time as i64; + let resend_timer = current_time + Crypto::SETTINGS.resend_time as i64; let session = Arc::new(Session { ctx: Arc::downgrade(ctx), session_data, @@ -397,13 +397,13 @@ pub(crate) fn trans_to_a1( state: RwLock::new(MutableState { ratchet_state1: state1.clone(), ratchet_state2: state2.clone(), - hk_send: App::PrpEnc::new((&hk_send[..AES_256_KEY_SIZE]).try_into().unwrap()), - hk_recv: App::PrpDec::new((&hk_recv[..AES_256_KEY_SIZE]).try_into().unwrap()), + hk_send: Crypto::PrpEnc::new((&hk_send[..AES_256_KEY_SIZE]).try_into().unwrap()), + hk_recv: Crypto::PrpDec::new((&hk_recv[..AES_256_KEY_SIZE]).try_into().unwrap()), key_creation_counter: 0, key_index: true, keys: [DuplexKey::default(), DuplexKey::default()], resend_timer: AtomicI64::new(resend_timer), - timeout_timer: current_time + App::SETTINGS.initial_offer_timeout as i64, + timeout_timer: current_time + Crypto::SETTINGS.initial_offer_timeout as i64, beta: ZetaAutomata::A1(a1), }), noise_kk_ss: noise_kk_ss.clone(), @@ -422,9 +422,9 @@ pub(crate) fn trans_to_a1( Ok(session) } /// Corresponds to Algorithm 13 found in Section 5. -pub(crate) fn respond_to_challenge( - ctx: &Arc>, - session: &Session, +pub(crate) fn respond_to_challenge( + ctx: &Arc>, + session: &Session, challenge: &[u8; CHALLENGE_SIZE], ) { let mut state = session.state.write().unwrap(); @@ -432,21 +432,21 @@ pub(crate) fn respond_to_challenge( let response_start = a1.x1.len() - CHALLENGE_SIZE; respond_to_challenge_in_place( ctx.rng.lock().unwrap().deref_mut(), - &mut App::Hash::new(), + &mut Crypto::Hash::new(), challenge, (&mut a1.x1[response_start..]).try_into().unwrap(), ); } } /// Corresponds to Transition Algorithm 2 found in Section 4.3. -pub(crate) fn received_x1_trans( - app: &App, - ctx: &ContextInner, - hash: &mut App::Hash, +pub(crate) fn received_x1_trans>( + app: &mut App, + ctx: &ContextInner, + hash: &mut Crypto::Hash, n: &[u8; AES_GCM_NONCE_SIZE], x1: &mut [u8], - send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), -) -> Result<(), ReceiveError> { + send: impl FnOnce(&mut [u8], Option<&Crypto::PrpEnc>), +) -> Result<(), ReceiveError> { use FaultType::*; // <- s // ... @@ -459,8 +459,8 @@ pub(crate) fn received_x1_trans( if &n[AES_GCM_NONCE_SIZE - 8..] != &x1[x1.len() - 8..] { return Err(byzantine_fault!(FailedAuth, true)); } - let hmac = &mut App::Hmac::new(); - let mut noise = SymmetricState::::initialize(PROTOCOL_NAME_NOISE_XK); + let hmac = &mut Crypto::Hmac::new(); + let mut noise = SymmetricState::::initialize(PROTOCOL_NAME_NOISE_XK); let mut i = 0; // Noise process prologue. let j = i + KID_SIZE; @@ -503,7 +503,7 @@ pub(crate) fn received_x1_trans( ratchet_state = Some(rs); break; } - Err(e) => return Err(ReceiveError::StorageError(e)), + Err(_) => return Err(ReceiveError::RatchetStorageError), } i += RATCHET_SIZE; } @@ -532,7 +532,7 @@ pub(crate) fn received_x1_trans( { let i = x2.len(); let mut ekem1_secret = Zeroizing::new([0u8; KYBER_PLAINTEXT_SIZE]); - let ekem1 = App::Kem::encapsulate( + let ekem1 = Crypto::Kem::encapsulate( ctx.rng.lock().unwrap().deref_mut(), (&x1[e1_start..e1_end]).try_into().unwrap(), &mut ekem1_secret, @@ -582,20 +582,20 @@ pub(crate) fn received_x1_trans( send( &mut x2, - Some(&App::PrpEnc::new(&hk_send[..AES_256_KEY_SIZE].try_into().unwrap())), + Some(&Crypto::PrpEnc::new(&hk_send[..AES_256_KEY_SIZE].try_into().unwrap())), ); Ok(()) } /// Corresponds to Transition Algorithm 3 found in Section 4.3. -pub(crate) fn received_x2_trans( - app: &App, - ctx: &Arc>, - session: &Arc>, +pub(crate) fn received_x2_trans>( + app: &mut App, + ctx: &Arc>, + session: &Arc>, kid: NonZeroU32, n: &[u8; AES_GCM_NONCE_SIZE], x2: &mut [u8], - send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), -) -> Result> { + send: impl FnOnce(&mut [u8], Option<&Crypto::PrpEnc>), +) -> Result { use FaultType::*; // <- e, ee, ekem1, psk // -> s, se @@ -605,8 +605,8 @@ pub(crate) fn received_x2_trans( let kex_lock = session.state_machine_lock.lock().unwrap(); let state = session.state.read().unwrap(); - let hash = &mut App::Hash::new(); - let hmac = &mut App::Hmac::new(); + let hash = &mut Crypto::Hash::new(); + let hmac = &mut Crypto::Hmac::new(); if Some(kid) != state.key_ref(true).recv.kid { return Err(byzantine_fault!(UnknownLocalKeyId, true)); @@ -660,7 +660,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 mut test_ratchet_key = |ratchet_key| -> Option<(NonZeroU32, SymmetricState)> { + let mut test_ratchet_key = |ratchet_key| -> Option<(NonZeroU32, SymmetricState)> { let mut noise = noise.clone(); let mut payload = payload.clone(); // Process message pattern 2 psk token. @@ -729,8 +729,8 @@ pub(crate) fn received_x2_trans( deleted_state2: None, }, ); - if let Err(e) = result { - return Err(ReceiveError::StorageError(e)); + if let Err(()) = result { + return Err(ReceiveError::RatchetStorageError); } let mut kek_recv = Zeroizing::new([0u8; HASHLEN]); @@ -754,9 +754,9 @@ pub(crate) fn received_x2_trans( state.ratchet_state1 = new_ratchet_state.clone(); let current_time = app.time(); state.key_creation_counter = session.send_counter.load(Ordering::Relaxed); - let resend_timer = current_time + App::SETTINGS.resend_time as i64; + let resend_timer = current_time + Crypto::SETTINGS.resend_time as i64; state.resend_timer = AtomicI64::new(resend_timer); - state.timeout_timer = current_time + App::SETTINGS.initial_offer_timeout as i64; + state.timeout_timer = current_time + Crypto::SETTINGS.initial_offer_timeout as i64; let a1 = if let ZetaAutomata::A1(a1) = &state.beta { a1 } else { @@ -779,24 +779,25 @@ pub(crate) fn received_x2_trans( Err(ReceiveError::ByzantineFault { .. }) => { let kex_lock = session.state_machine_lock.lock().unwrap(); let state = session.state.read().unwrap(); - timeout_trans(app, ctx, session, kex_lock, state, app.time(), send); + let current_time = app.time(); + timeout_trans(app, ctx, session, kex_lock, state, current_time, send); } Ok(ref mut packet) => send(packet, Some(&session.state.read().unwrap().hk_send)), _ => {} } result.map(|_| should_warn_missing_ratchet) } -fn send_control( - session: &Arc>, - state: &MutableState, +fn send_control( + session: &Arc>, + state: &MutableState, packet_type: u8, mut payload: ArrayVec, - send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), + send: impl FnOnce(&mut [u8], Option<&Crypto::PrpEnc>), ) -> bool { if let Some((c, _)) = get_counter(session, &state) { if let (Some(kek), Some(kid)) = (state.key_ref(false).send.kek.as_ref(), state.key_ref(false).send.kid) { let nonce = to_nonce(packet_type, c); - let tag = App::Aead::encrypt_in_place(kek, &nonce, &[], &mut payload[HEADER_SIZE..]); + let tag = Crypto::Aead::encrypt_in_place(kek, &nonce, &[], &mut payload[HEADER_SIZE..]); payload.extend(tag); set_header(&mut payload, kid.get(), &nonce); send(&mut payload, Some(&state.hk_send)); @@ -809,14 +810,14 @@ fn send_control( } } /// Corresponds to Transition Algorithm 4 found in Section 4.3. -pub(crate) fn received_x3_trans( - app: &App, - ctx: &Arc>, - zeta: Arc>, +pub(crate) fn received_x3_trans>( + app: &mut App, + ctx: &Arc>, + zeta: Arc>, kid: NonZeroU32, x3: &mut [u8], - send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), -) -> Result<(Arc>, bool), ReceiveError> { + send: impl FnOnce(&mut [u8], Option<&Crypto::PrpEnc>), +) -> Result<(Arc>, bool), ReceiveError> { use FaultType::*; // -> s, se if x3.len() < HANDSHAKE_COMPLETION_MIN_SIZE { @@ -825,8 +826,8 @@ pub(crate) fn received_x3_trans( if kid != zeta.kid_recv { return Err(byzantine_fault!(UnknownLocalKeyId, true)); } - let hash = &mut App::Hash::new(); - let hmac = &mut App::Hmac::new(); + let hash = &mut Crypto::Hash::new(); + let hmac = &mut Crypto::Hmac::new(); let mut noise = zeta.noise.clone(); let mut i = 0; @@ -838,7 +839,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))?; + Crypto::PublicKey::from_bytes((&x3[i..j]).try_into().unwrap()).ok_or(byzantine_fault!(FailedAuth, true))?; i = k; // Process message pattern 3 se token. noise @@ -870,7 +871,7 @@ pub(crate) fn received_x3_trans( let mut d = ArrayVec::::new(); d.extend([0u8; HEADER_SIZE]); let nonce = to_nonce(PACKET_TYPE_SESSION_REJECTED, c); - d.extend(App::Aead::encrypt_in_place( + d.extend(Crypto::Aead::encrypt_in_place( (&kek_send[..AES_256_KEY_SIZE]).try_into().unwrap(), &nonce, &[], @@ -891,7 +892,7 @@ pub(crate) fn received_x3_trans( should_warn_missing_ratchet = true; } else { if !responder_silently_rejects { - send(&mut create_reject(), Some(&App::PrpEnc::new(&zeta.hk_send))) + send(&mut create_reject(), Some(&Crypto::PrpEnc::new(&zeta.hk_send))) } return Err(byzantine_fault!(FailedAuth, true)); } @@ -919,8 +920,8 @@ pub(crate) fn received_x3_trans( deleted_state2: state2.as_ref(), }, ); - if let Err(e) = result { - return Err(ReceiveError::StorageError(e)); + if let Err(()) = result { + return Err(ReceiveError::RatchetStorageError); } let session = { @@ -935,7 +936,7 @@ pub(crate) fn received_x3_trans( let mut session_queue = ctx.session_queue.lock().unwrap(); let queue_idx = session_queue.reserve_index(); let current_time = app.time(); - let resend_timer = current_time + App::SETTINGS.resend_time as i64; + let resend_timer = current_time + Crypto::SETTINGS.resend_time as i64; let session = Arc::new(Session { ctx: Arc::downgrade(ctx), session_data, @@ -947,13 +948,13 @@ pub(crate) fn received_x3_trans( state: RwLock::new(MutableState { ratchet_state1: new_ratchet_state.clone(), ratchet_state2: None, - hk_send: App::PrpEnc::new(&zeta.hk_send), - hk_recv: App::PrpDec::new(&zeta.hk_recv), + hk_send: Crypto::PrpEnc::new(&zeta.hk_send), + hk_recv: Crypto::PrpDec::new(&zeta.hk_recv), key_creation_counter: c + 1, key_index: false, keys: [DuplexKey::default(), DuplexKey::default()], resend_timer: AtomicI64::new(resend_timer), - timeout_timer: current_time + App::SETTINGS.rekey_timeout as i64, + timeout_timer: current_time + Crypto::SETTINGS.rekey_timeout as i64, beta: ZetaAutomata::S1, }), window: Window::new(), @@ -983,25 +984,25 @@ pub(crate) fn received_x3_trans( Ok((session, should_warn_missing_ratchet)) } - Err(e) => Err(ReceiveError::StorageError(e)), + Err(()) => Err(ReceiveError::RatchetStorageError), } } else { if !responder_silently_rejects { - send(&mut create_reject(), Some(&App::PrpEnc::new(&zeta.hk_send))) + send(&mut create_reject(), Some(&Crypto::PrpEnc::new(&zeta.hk_send))) } Err(ReceiveError::Rejected) } } /// Corresponds to Transition Algorithm 5 found in Section 4.3. -pub(crate) fn received_c1_trans( - app: &App, - ctx: &Arc>, - session: &Arc>, +pub(crate) fn received_c1_trans>( + app: &mut App, + ctx: &Arc>, + session: &Arc>, kid: NonZeroU32, n: &[u8; AES_GCM_NONCE_SIZE], c1: &[u8], - send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), -) -> Result> { + send: impl FnOnce(&mut [u8], Option<&Crypto::PrpEnc>), +) -> Result { use FaultType::*; if c1.len() != KEY_CONFIRMATION_SIZE { @@ -1024,7 +1025,7 @@ pub(crate) fn received_c1_trans( let specified_key = state.key_ref(is_other).recv.kek.as_ref(); let specified_key = specified_key.ok_or(byzantine_fault!(OutOfSequence, true))?; let tag = c1[..].try_into().unwrap(); - if !App::Aead::decrypt_in_place(specified_key, n, &[], &mut [], tag) { + if !Crypto::Aead::decrypt_in_place(specified_key, n, &[], &mut [], tag) { return Err(byzantine_fault!(FailedAuth, true)); } let (_, c) = from_nonce(n); @@ -1047,8 +1048,8 @@ pub(crate) fn received_c1_trans( deleted_state2: None, }, ); - if let Err(e) = result { - return Err(ReceiveError::StorageError(e)); + if let Err(()) = result { + return Err(ReceiveError::RatchetStorageError); } } drop(state); @@ -1057,9 +1058,9 @@ pub(crate) fn received_c1_trans( state.ratchet_state2 = None; state.key_index ^= true; state.timeout_timer = app.time() - + App::SETTINGS + + Crypto::SETTINGS .rekey_after_time - .saturating_sub(ctx.rng.lock().unwrap().next_u64() % App::SETTINGS.rekey_time_max_jitter) + .saturating_sub(ctx.rng.lock().unwrap().next_u64() % Crypto::SETTINGS.rekey_time_max_jitter) as i64; state.resend_timer = AtomicI64::new(i64::MAX); state.beta = ZetaAutomata::S2; @@ -1084,14 +1085,14 @@ 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( - app: &App, - ctx: &Arc>, - session: &Arc>, +pub(crate) fn received_c2_trans>( + app: &mut App, + ctx: &Arc>, + session: &Arc>, kid: NonZeroU32, n: &[u8; AES_GCM_NONCE_SIZE], c2: &[u8], -) -> Result<(), ReceiveError> { +) -> Result<(), ReceiveError> { use FaultType::*; if c2.len() != ACKNOWLEDGEMENT_SIZE { @@ -1111,7 +1112,7 @@ pub(crate) fn received_c2_trans( } let tag = c2[..].try_into().unwrap(); - if !App::Aead::decrypt_in_place(state.key_ref(false).recv.kek.as_ref().unwrap(), n, &[], &mut [], tag) { + if !Crypto::Aead::decrypt_in_place(state.key_ref(false).recv.kek.as_ref().unwrap(), n, &[], &mut [], tag) { return Err(byzantine_fault!(FailedAuth, true)); } let (_, c) = from_nonce(n); @@ -1122,9 +1123,9 @@ pub(crate) fn received_c2_trans( let timeout_timer = { let mut state = session.state.write().unwrap(); state.timeout_timer = app.time() - + App::SETTINGS + + Crypto::SETTINGS .rekey_after_time - .saturating_sub(ctx.rng.lock().unwrap().next_u64() % App::SETTINGS.rekey_time_max_jitter) + .saturating_sub(ctx.rng.lock().unwrap().next_u64() % Crypto::SETTINGS.rekey_time_max_jitter) as i64; state.resend_timer = AtomicI64::new(i64::MAX); state.beta = ZetaAutomata::S2; @@ -1139,12 +1140,12 @@ 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( - session: &Arc>, +pub(crate) fn received_d_trans( + session: &Arc>, kid: NonZeroU32, n: &[u8; AES_GCM_NONCE_SIZE], d: &[u8], -) -> Result<(), ReceiveError> { +) -> Result<(), ReceiveError> { use FaultType::*; if d.len() != SESSION_REJECTED_SIZE { @@ -1159,7 +1160,7 @@ pub(crate) fn received_d_trans( } let tag = d[..].try_into().unwrap(); - if !App::Aead::decrypt_in_place(state.key_ref(true).recv.kek.as_ref().unwrap(), n, &[], &mut [], tag) { + if !Crypto::Aead::decrypt_in_place(state.key_ref(true).recv.kek.as_ref().unwrap(), n, &[], &mut [], tag) { return Err(byzantine_fault!(FailedAuth, true)); } let (_, c) = from_nonce(n); @@ -1173,14 +1174,14 @@ pub(crate) fn received_d_trans( Ok(()) } // Corresponds to the timeout timer Transition Algorithm described in Section 4.1 - Definition 3. -fn timeout_trans( - app: &App, - ctx: &Arc>, - session: &Arc>, +fn timeout_trans>( + app: &mut App, + ctx: &Arc>, + session: &Arc>, kex_lock: MutexGuard<'_, ()>, - state: RwLockReadGuard<'_, MutableState>, + state: RwLockReadGuard<'_, MutableState>, current_time: i64, - send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), + send: impl FnOnce(&mut [u8], Option<&Crypto::PrpEnc>), ) -> Option { match &state.beta { ZetaAutomata::Null => None, @@ -1197,8 +1198,8 @@ fn timeout_trans( } let new_kid_recv = remap(ctx, session, &state); - let hash = &mut App::Hash::new(); - let hmac = &mut App::Hmac::new(); + let hash = &mut Crypto::Hash::new(); + let hmac = &mut Crypto::Hmac::new(); if let Some(a1) = create_a1_state( hash, hmac, @@ -1221,9 +1222,9 @@ fn timeout_trans( state.hk_send.reset((&hk_send[..AES_256_KEY_SIZE]).try_into().unwrap()); *state.key_mut(true) = DuplexKey::default(); state.key_mut(true).recv.kid = Some(new_kid_recv); - let resend_timer = current_time + App::SETTINGS.resend_time as i64; + let resend_timer = current_time + Crypto::SETTINGS.resend_time as i64; state.resend_timer = AtomicI64::new(resend_timer); - state.timeout_timer = current_time + App::SETTINGS.initial_offer_timeout as i64; + state.timeout_timer = current_time + Crypto::SETTINGS.initial_offer_timeout as i64; state.beta = ZetaAutomata::A1(a1); resend_timer }; @@ -1244,8 +1245,8 @@ fn timeout_trans( // ... // -> psk, e, es, ss let mut noise = SymmetricState::initialize(PROTOCOL_NAME_NOISE_KK); - let hash = &mut App::Hash::new(); - let hmac = &mut App::Hmac::new(); + let hash = &mut Crypto::Hash::new(); + let hmac = &mut Crypto::Hmac::new(); let mut k1 = ArrayVec::::new(); k1.extend([0u8; HEADER_SIZE]); // Noise process prologue. @@ -1271,8 +1272,8 @@ fn timeout_trans( let resend_timer = { let mut state = session.state.write().unwrap(); state.key_mut(true).recv.kid = Some(new_kid_recv); - state.timeout_timer = current_time + App::SETTINGS.rekey_timeout as i64; - let resend_timer = current_time + App::SETTINGS.resend_time as i64; + state.timeout_timer = current_time + Crypto::SETTINGS.rekey_timeout as i64; + let resend_timer = current_time + Crypto::SETTINGS.resend_time as i64; state.resend_timer = AtomicI64::new(resend_timer); state.beta = ZetaAutomata::R1 { noise, e_secret, k1: k1.clone() }; resend_timer @@ -1298,12 +1299,12 @@ fn timeout_trans( } } /// Corresponds to the timer rules of the Zeta State Machine found in Section 4.1 - Definition 3. -pub(crate) fn process_timers( - app: &App, - ctx: &Arc>, - session: &Arc>, +pub(crate) fn process_timers>( + app: &mut App, + ctx: &Arc>, + session: &Arc>, current_time: i64, - send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), + send: impl FnOnce(&mut [u8], Option<&Crypto::PrpEnc>), ) -> Option { let kex_lock = session.state_machine_lock.lock().unwrap(); let state = session.state.read().unwrap(); @@ -1312,7 +1313,7 @@ pub(crate) fn process_timers( timeout_trans(app, ctx, session, kex_lock, state, current_time, send) } else { let ts = state.resend_timer.load(Ordering::Relaxed); - let resend_next = current_time + App::SETTINGS.resend_time as i64; + let resend_next = current_time + Crypto::SETTINGS.resend_time as i64; if ts <= current_time && state.resend_timer.fetch_max(resend_next, Ordering::Relaxed) == ts { // Corresponds to the resend timer rules found in Section 4.1 - Definition 3. @@ -1353,15 +1354,15 @@ pub(crate) fn process_timers( } } /// Corresponds to Transition Algorithm 7 found in Section 4.3. -pub(crate) fn received_k1_trans( - app: &App, - ctx: &Arc>, - session: &Arc>, +pub(crate) fn received_k1_trans>( + app: &mut App, + ctx: &Arc>, + session: &Arc>, kid: NonZeroU32, n: &[u8; AES_GCM_NONCE_SIZE], k1: &mut [u8], - send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), -) -> Result<(), ReceiveError> { + send: impl FnOnce(&mut [u8], Option<&Crypto::PrpEnc>), +) -> Result<(), ReceiveError> { use FaultType::*; // -> s // <- s @@ -1391,7 +1392,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 !Crypto::Aead::decrypt_in_place( state.key_ref(false).recv.kek.as_ref().unwrap(), n, &[], @@ -1407,9 +1408,9 @@ pub(crate) fn received_k1_trans( let result = (|| { let mut i = 0; - let mut noise = SymmetricState::::initialize(PROTOCOL_NAME_NOISE_KK); - let hash = &mut App::Hash::new(); - let hmac = &mut App::Hmac::new(); + let mut noise = SymmetricState::::initialize(PROTOCOL_NAME_NOISE_KK); + let hash = &mut Crypto::Hash::new(); + let hmac = &mut Crypto::Hmac::new(); // Noise process prologue. noise.mix_hash(hash, &session.s_remote.to_bytes()); noise.mix_hash(hash, &ctx.s_secret.public_key_bytes()); @@ -1466,8 +1467,8 @@ pub(crate) fn received_k1_trans( deleted_state2: None, }, ); - if let Err(e) = result { - return Err(ReceiveError::StorageError(e)); + if let Err(()) = result { + return Err(ReceiveError::RatchetStorageError); } let mut kek_recv = Zeroizing::new([0u8; HASHLEN]); @@ -1489,8 +1490,8 @@ pub(crate) fn received_k1_trans( state.ratchet_state1 = new_ratchet_state.clone(); let current_time = app.time(); state.key_creation_counter = session.send_counter.load(Ordering::Relaxed); - let resend_timer = current_time + App::SETTINGS.resend_time as i64; - state.timeout_timer = current_time + App::SETTINGS.rekey_timeout as i64; + let resend_timer = current_time + Crypto::SETTINGS.resend_time as i64; + state.timeout_timer = current_time + Crypto::SETTINGS.rekey_timeout as i64; state.resend_timer = AtomicI64::new(resend_timer); state.beta = ZetaAutomata::R2 { k2: k2.clone() }; resend_timer @@ -1515,15 +1516,15 @@ pub(crate) fn received_k1_trans( result } /// Corresponds to Transition Algorithm 8 found in Section 4.3. -pub(crate) fn received_k2_trans( - app: &App, - ctx: &Arc>, - session: &Arc>, +pub(crate) fn received_k2_trans>( + app: &mut App, + ctx: &Arc>, + session: &Arc>, kid: NonZeroU32, n: &[u8; AES_GCM_NONCE_SIZE], k2: &mut [u8], - send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), -) -> Result<(), ReceiveError> { + send: impl FnOnce(&mut [u8], Option<&Crypto::PrpEnc>), +) -> Result<(), ReceiveError> { use FaultType::*; // <- e, ee, se if k2.len() != REKEY_SIZE { @@ -1544,7 +1545,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 !Crypto::Aead::decrypt_in_place( state.key_ref(false).recv.kek.as_ref().unwrap(), n, &[], @@ -1561,8 +1562,8 @@ pub(crate) fn received_k2_trans( if let ZetaAutomata::R1 { noise, e_secret, .. } = &state.beta { let mut noise = noise.clone(); let mut i = 0; - let hash = &mut App::Hash::new(); - let hmac = &mut App::Hmac::new(); + let hash = &mut Crypto::Hash::new(); + let hmac = &mut Crypto::Hmac::new(); // Process message pattern 2 e token. let e_remote = noise .read_e_no_init(hash, hmac, &mut i, &k2) @@ -1597,8 +1598,8 @@ pub(crate) fn received_k2_trans( deleted_state2: state.ratchet_state2.as_ref(), }, ); - if let Err(e) = result { - return Err(ReceiveError::StorageError(e)); + if let Err(()) = result { + return Err(ReceiveError::RatchetStorageError); } let mut kek_recv = Zeroizing::new([0u8; HASHLEN]); let mut kek_send = Zeroizing::new([0u8; HASHLEN]); @@ -1618,8 +1619,8 @@ pub(crate) fn received_k2_trans( state.key_index ^= true; let current_time = app.time(); state.key_creation_counter = session.send_counter.load(Ordering::Relaxed); - let resend_timer = current_time + App::SETTINGS.resend_time as i64; - state.timeout_timer = current_time + App::SETTINGS.rekey_timeout as i64; + let resend_timer = current_time + Crypto::SETTINGS.resend_time as i64; + state.timeout_timer = current_time + Crypto::SETTINGS.rekey_timeout as i64; state.resend_timer = AtomicI64::new(resend_timer); state.beta = ZetaAutomata::S1; resend_timer @@ -1649,9 +1650,9 @@ pub(crate) fn received_k2_trans( result } /// Corresponds to Algorithm 9 found in Section 4.3. -pub(crate) fn send_payload( - ctx: &Arc>, - session: &Arc>, +pub(crate) fn send_payload( + ctx: &Arc>, + session: &Arc>, payload: &[u8], mut send: impl FnMut(&mut [u8]) -> bool, mtu_sized_buffer: &mut [u8], @@ -1747,14 +1748,14 @@ pub(crate) fn send_payload( Ok(()) } /// Corresponds to Algorithm 10 found in Section 4.3. -pub(crate) fn receive_payload_in_place( - session: &Arc>, - state: RwLockReadGuard<'_, MutableState>, +pub(crate) fn receive_payload_in_place( + session: &Arc>, + state: RwLockReadGuard<'_, MutableState>, kid: NonZeroU32, nonce: &[u8; AES_GCM_NONCE_SIZE], - fragments: &mut [App::IncomingPacketBuffer], + fragments: &mut [Crypto::IncomingPacketBuffer], mut output_buffer: impl Write, -) -> Result<(), ReceiveError> { +) -> Result<(), ReceiveError> { use FaultType::*; debug_assert!(!fragments.is_empty()); @@ -1804,12 +1805,12 @@ pub(crate) fn receive_payload_in_place( Ok(()) } -impl Drop for Session { +impl Drop for Session { fn drop(&mut self) { self.expire(); } } -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. @@ -1823,8 +1824,8 @@ impl Session { /// Allows us to expire sessions with the correct locking order, preventing deadlock. pub(crate) fn expire_inner( &self, - ctx: Option<&Arc>>, - session_queue: Option<&mut SessionQueue>, + ctx: Option<&Arc>>, + session_queue: Option<&mut SessionQueue>, ) { let _kex_lock = self.state_machine_lock.lock().unwrap(); let mut state = self.state.write().unwrap(); @@ -1867,7 +1868,7 @@ impl Session { ) } /// The static public key of the remote peer. - pub fn remote_static_key(&self) -> &App::PublicKey { + pub fn remote_static_key(&self) -> &Crypto::PublicKey { &self.s_remote } } diff --git a/src/zssp.rs b/src/zssp.rs index 0acc741..16a40e5 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -37,31 +37,31 @@ 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(pub Arc>); -impl Clone for Context { +pub struct Context(pub Arc>); +impl Clone for Context { fn clone(&self) -> Self { Self(self.0.clone()) } } -pub(crate) type SessionMap = RwLock>>>; -pub(crate) type SessionQueue = IndexedBinaryHeap>, Reverse>; -pub struct ContextInner { - pub rng: Mutex, - pub(crate) s_secret: App::KeyPair, +pub(crate) type SessionMap = RwLock>>>; +pub(crate) type SessionQueue = IndexedBinaryHeap>, Reverse>; +pub struct ContextInner { + pub rng: Mutex, + pub(crate) s_secret: Crypto::KeyPair, /// `session_queue -> state_machine_lock -> state -> session_map` - pub(crate) session_queue: Mutex>, + pub(crate) session_queue: Mutex>, /// `session_queue -> state_machine_lock -> state -> session_map` - pub(crate) session_map: SessionMap, - pub(crate) unassociated_defrag_cache: Mutex>, - pub(crate) unassociated_handshake_states: UnassociatedHandshakeCache, + pub(crate) session_map: SessionMap, + pub(crate) unassociated_defrag_cache: Mutex>, + pub(crate) unassociated_handshake_states: UnassociatedHandshakeCache, pub(crate) challenge: ChallengeContext, } -fn parse_fragment_header( +fn parse_fragment_header( incoming_fragment: &[u8], -) -> Result<(usize, usize, [u8; AES_GCM_NONCE_SIZE]), ReceiveError> { +) -> Result<(usize, usize, [u8; AES_GCM_NONCE_SIZE]), ReceiveError> { let fragment_no = incoming_fragment[FRAGMENT_NO_IDX] as usize; let fragment_count = incoming_fragment[FRAGMENT_COUNT_IDX] as usize; if fragment_no >= fragment_count || fragment_count > MAX_FRAGMENTS { @@ -110,9 +110,9 @@ fn send_with_fragmentation( true } -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: Mutex::new(rng), @@ -141,15 +141,15 @@ impl Context { /// peer, or None if we do not have one. /// * `local_identity_blob` - 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>( &self, app: App, send: impl FnMut(&mut [u8]) -> bool, mut mtu: usize, - static_remote_key: App::PublicKey, - session_data: App::SessionData, + static_remote_key: Crypto::PublicKey, + session_data: Crypto::SessionData, identity: &[u8], - ) -> Result>, OpenError> { + ) -> Result>, OpenError> { mtu = mtu.max(MIN_TRANSPORT_MTU); if identity.len() > IDENTITY_MAX_SIZE { return Err(OpenError::IdentityTooLarge); @@ -198,16 +198,16 @@ impl Context { /// * `current_time` - Current time in milliseconds. Does not have to be monotonic, nor synced /// with the remote peer. Used to check the state of local offers we may currently have or want /// to put in-flight. - pub fn receive<'a, SendFn: FnMut(&mut [u8]) -> bool>( + pub fn receive<'a, App: ApplicationLayer, SendFn: FnMut(&mut [u8]) -> bool>( &self, - app: App, + mut app: App, mut send_unassociated_reply: impl FnMut(&mut [u8]) -> bool, mut send_unassociated_mtu: usize, - mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, + mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, remote_address: &impl Hash, - mut incoming_fragment_buf: App::IncomingPacketBuffer, + mut incoming_fragment_buf: Crypto::IncomingPacketBuffer, output_buffer: impl Write, - ) -> Result, ReceiveError> { + ) -> Result, ReceiveError> { use crate::result::FaultType::*; let ctx = &self.0; send_unassociated_mtu = send_unassociated_mtu.max(MIN_TRANSPORT_MTU); @@ -327,7 +327,7 @@ impl Context { &mut incoming_fragment_buf.as_mut()[HEADER_SIZE..] }; - let send_associated = |packet: &mut [u8], hk_send: Option<&App::PrpEnc>| { + let send_associated = |packet: &mut [u8], hk_send: Option<&Crypto::PrpEnc>| { if let Some((send_fragment, mut mtu)) = send_to(&session) { mtu = mtu.max(MIN_TRANSPORT_MTU); send_with_fragmentation(send_fragment, mtu, packet, hk_send); @@ -337,7 +337,7 @@ impl Context { PACKET_TYPE_HANDSHAKE_RESPONSE => { log!(app, ReceivedRawX2); let should_warn_missing_ratchet = received_x2_trans( - &app, + &mut app, ctx, &session, kid_recv, @@ -355,7 +355,7 @@ impl Context { PACKET_TYPE_KEY_CONFIRM => { log!(app, ReceivedRawKeyConfirm); let just_established = received_c1_trans( - &app, + &mut app, ctx, &session, kid_recv, @@ -372,14 +372,14 @@ impl Context { } PACKET_TYPE_ACK => { log!(app, ReceivedRawAck); - received_c2_trans(&app, ctx, &session, kid_recv, &nonce, assembled_packet)?; + received_c2_trans(&mut app, ctx, &session, kid_recv, &nonce, assembled_packet)?; log!(app, AckIsAuth(&session)); SessionEvent::Control } PACKET_TYPE_REKEY_INIT => { log!(app, ReceivedRawK1); received_k1_trans( - &app, + &mut app, ctx, &session, kid_recv, @@ -393,7 +393,7 @@ impl Context { PACKET_TYPE_REKEY_COMPLETE => { log!(app, ReceivedRawK2); received_k2_trans( - &app, + &mut app, ctx, &session, kid_recv, @@ -418,7 +418,7 @@ impl Context { // Check for and handle PACKET_TYPE_ALICE_NOISE_XK_PATTERN_3 let zeta = self.0.unassociated_handshake_states.get(kid_recv); if let Some(zeta) = zeta { - App::PrpDec::new(&zeta.hk_recv).decrypt_in_place( + Crypto::PrpDec::new(&zeta.hk_recv).decrypt_in_place( (&mut incoming_fragment[HEADER_AUTH_START..HEADER_AUTH_END]) .try_into() .unwrap(), @@ -468,7 +468,7 @@ impl Context { log!(app, ReceivedRawX3); let (session, should_warn_missing_ratchet) = - received_x3_trans(&app, ctx, zeta, kid_recv, assembled_packet, |packet, hk_send| { + received_x3_trans(&mut app, ctx, zeta, kid_recv, assembled_packet, |packet, hk_send| { send_with_fragmentation(send_unassociated_reply, send_unassociated_mtu, packet, hk_send); })?; log!(app, X3IsAuthSentKeyConfirm(&session)); @@ -508,7 +508,7 @@ impl Context { incoming_fragment_buf, fragment_no, fragment_count, - App::SETTINGS.resend_time as i64, + Crypto::SETTINGS.resend_time as i64, app.time(), &mut fragment_buffer, ); @@ -536,7 +536,7 @@ impl Context { } // Process recv challenge layer. let challenge_start = assembled_packet.len() - CHALLENGE_SIZE; - let hash = &mut App::Hash::new(); + let hash = &mut Crypto::Hash::new(); match app.incoming_session() { IncomingSessionAction::Allow => {} IncomingSessionAction::Challenge => { @@ -569,7 +569,7 @@ impl Context { // Process recv zeta layer. received_x1_trans( - &app, + &mut app, ctx, hash, &nonce, @@ -612,7 +612,7 @@ impl Context { /// * `current_time` - Current time in milliseconds pub fn send( &self, - session: &Arc>, + session: &Arc>, send: impl FnMut(&mut [u8]) -> bool, mtu_sized_buffer: &mut [u8], data: &[u8], @@ -631,15 +631,15 @@ impl Context { /// 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. - pub fn service bool>( + pub fn service, SendFn: FnMut(&mut [u8]) -> bool>( &self, - app: App, - mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, + mut app: App, + mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, ) -> i64 { let ctx = &self.0; let mut session_queue = ctx.session_queue.lock().unwrap(); let current_time = app.time(); - let mut next_service_time = current_time + App::SETTINGS.fragment_assembly_timeout as i64; + let mut next_service_time = current_time + Crypto::SETTINGS.fragment_assembly_timeout as i64; // This update system takes heavy advantage of the fact that sessions only need to be updated // either roughly every second or roughly every hour. That big gap allows for minor optimizations. // If the gap changes (unlikely) this code may need to be rewritten. @@ -655,7 +655,7 @@ impl Context { continue; } }; - let result = process_timers(&app, ctx, &session, current_time, |packet, hk_send| { + let result = process_timers(&mut app, ctx, &session, current_time, |packet, hk_send| { if let Some((send_fragment, mut mtu)) = send_to(&session) { mtu = mtu.max(MIN_TRANSPORT_MTU); send_with_fragmentation(send_fragment, mtu, packet, hk_send); @@ -674,7 +674,7 @@ impl Context { .unassociated_defrag_cache .lock() .unwrap() - .check_for_expiry(App::SETTINGS.fragment_assembly_timeout as i64, current_time); + .check_for_expiry(Crypto::SETTINGS.fragment_assembly_timeout as i64, current_time); self.0.unassociated_handshake_states.service(current_time); next_service_time - current_time From a22898d3486d7e639846dd09eb15edf27107c875 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 14 Aug 2023 11:20:17 -0400 Subject: [PATCH 32/50] refortmatted --- examples/basic_test.rs | 15 +++---- src/application.rs | 12 +++--- src/symmetric_state.rs | 15 ++++++- src/zeta.rs | 91 ++++++++++++++++-------------------------- src/zssp.rs | 11 ++--- 5 files changed, 65 insertions(+), 79 deletions(-) diff --git a/examples/basic_test.rs b/examples/basic_test.rs index f90a764..9d9bdc8 100644 --- a/examples/basic_test.rs +++ b/examples/basic_test.rs @@ -10,8 +10,8 @@ use rand_core::OsRng; use rand_core::RngCore; use zssp::application::{ - AcceptAction, CryptoLayer, IncomingSessionAction, RatchetState, RatchetStates, RatchetUpdate, Settings, - RATCHET_SIZE, ApplicationLayer, + AcceptAction, ApplicationLayer, CryptoLayer, IncomingSessionAction, RatchetState, RatchetStates, RatchetUpdate, + Settings, RATCHET_SIZE, }; use zssp::crypto::P384KeyPair; use zssp::crypto_impl::*; @@ -79,7 +79,11 @@ impl ApplicationLayer for &TestApplication { true } - fn check_accept_session(&mut self, remote_static_key: &P384CratePublicKey, 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, @@ -87,10 +91,7 @@ impl ApplicationLayer for &TestApplication { } } - fn restore_by_fingerprint( - &mut self, - ratchet_fingerprint: &[u8; RATCHET_SIZE], - ) -> Result, ()> { + fn restore_by_fingerprint(&mut self, ratchet_fingerprint: &[u8; RATCHET_SIZE]) -> Result, ()> { let ratchets = self.ratchets.lock().unwrap(); Ok(ratchets.rf_map.get(ratchet_fingerprint).cloned()) } diff --git a/src/application.rs b/src/application.rs index 05bb201..fe94c9f 100644 --- a/src/application.rs +++ b/src/application.rs @@ -129,7 +129,6 @@ pub trait CryptoLayer: Sized { /// for ZSSP to achieve FIPS compliance. type Kem: Kyber1024PrivateKey; - /// Type for arbitrary opaque object for use by the application that is attached to /// each session. type SessionData; @@ -185,7 +184,11 @@ pub trait ApplicationLayer: Sized { /// 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: &::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 @@ -193,10 +196,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( - &mut self, - ratchet_fingerprint: &[u8; RATCHET_SIZE], - ) -> Result, ()>; + fn restore_by_fingerprint(&mut self, ratchet_fingerprint: &[u8; RATCHET_SIZE]) -> Result, ()>; /// 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/symmetric_state.rs b/src/symmetric_state.rs index 5981e3c..c9ea5a8 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -138,7 +138,12 @@ impl SymmetricState { self.k.clone_from_slice(&temp_k[..AES_256_KEY_SIZE]); } /// Corresponds to Noise `MixKeyAndHash`. - pub fn mix_key_and_hash_no_init(&mut self, hash: &mut Crypto::Hash, hmac: &mut Crypto::Hmac, input_key_material: &[u8]) { + pub fn mix_key_and_hash_no_init( + &mut self, + hash: &mut Crypto::Hash, + hmac: &mut Crypto::Hmac, + input_key_material: &[u8], + ) { let mut next_ck = Zeroizing::new([0u8; HASHLEN]); let mut temp_h = [0u8; HASHLEN]; @@ -194,7 +199,13 @@ impl SymmetricState { /// is forward secrect and is cryptographically independent from all other produced keys. /// Based on Noise's unstable ASK mechanism, using KBKDF instead of HKDF. /// https://github.com/noiseprotocol/noise_wiki/wiki/Additional-Symmetric-Keys. - pub fn get_ask(&self, hmac: &mut Crypto::Hmac, label: &[u8; 4], key1: &mut [u8; HASHLEN], key2: &mut [u8; HASHLEN]) { + pub fn get_ask( + &self, + hmac: &mut Crypto::Hmac, + label: &[u8; 4], + key1: &mut [u8; HASHLEN], + key2: &mut [u8; HASHLEN], + ) { self.kbkdf(hmac, &self.h, label, 2, key1, Some(key2), None); } /// Used for internally debugging a key exchange. diff --git a/src/zeta.rs b/src/zeta.rs index 24c3dbf..068f964 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -188,7 +188,12 @@ impl SymmetricState { None } } - fn mix_dh_no_init(&mut self, hmac: &mut Crypto::Hmac, secret: &Crypto::KeyPair, remote: &Crypto::PublicKey) -> Option<()> { + fn mix_dh_no_init( + &mut self, + hmac: &mut Crypto::Hmac, + secret: &Crypto::KeyPair, + remote: &Crypto::PublicKey, + ) -> Option<()> { let mut ecdh_secret = Zeroizing::new([0u8; P384_ECDH_SHARED_SECRET_SIZE]); if secret.agree(&remote, &mut ecdh_secret) { self.mix_key_no_init(hmac, ecdh_secret.as_ref()); @@ -251,7 +256,8 @@ fn get_counter(session: &Session, state: &MutableSt session.session_has_expired.store(true, Ordering::SeqCst); return None; } - Some((c, c > state.key_creation_counter + Crypto::SETTINGS.rekey_after_key_uses)) + let rekey_at = state.key_creation_counter + Crypto::SETTINGS.rekey_after_key_uses; + Some((c, c > rekey_at)) } } @@ -330,13 +336,8 @@ fn create_a1_state( set_header(&mut x1, 0, &to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, c)); - Some(Box::new(StateA1 { - noise, - e_secret, - e1_secret, - identity: identity.try_into().unwrap(), - x1, - })) + let identity = identity.try_into().unwrap(); + Some(Box::new(StateA1 { noise, e_secret, e1_secret, identity, x1 })) } /// Corresponds to Transition Algorithm 1 found in Section 4.3. pub(crate) fn trans_to_a1>( @@ -430,12 +431,9 @@ pub(crate) fn respond_to_challenge( let mut state = session.state.write().unwrap(); if let ZetaAutomata::A1(a1) = &mut state.beta { let response_start = a1.x1.len() - CHALLENGE_SIZE; - respond_to_challenge_in_place( - ctx.rng.lock().unwrap().deref_mut(), - &mut Crypto::Hash::new(), - challenge, - (&mut a1.x1[response_start..]).try_into().unwrap(), - ); + let mut rng = ctx.rng.lock().unwrap(); + let response = (&mut a1.x1[response_start..]).try_into().unwrap(); + respond_to_challenge_in_place(rng.deref_mut(), &mut Crypto::Hash::new(), challenge, response); } } /// Corresponds to Transition Algorithm 2 found in Section 4.3. @@ -718,7 +716,7 @@ pub(crate) fn received_x2_trans::new(); d.extend([0u8; HEADER_SIZE]); let nonce = to_nonce(PACKET_TYPE_SESSION_REJECTED, c); - d.extend(Crypto::Aead::encrypt_in_place( - (&kek_send[..AES_256_KEY_SIZE]).try_into().unwrap(), - &nonce, - &[], - &mut [], - )); + let kek_send = (&kek_send[..AES_256_KEY_SIZE]).try_into().unwrap(); + d.extend(Crypto::Aead::encrypt_in_place(kek_send, &nonce, &[], &mut [])); set_header(&mut d, zeta.kid_send.get(), &nonce); d }; @@ -909,7 +901,7 @@ pub(crate) fn received_x3_trans { pub(crate) challenge: ChallengeContext, } -fn parse_fragment_header( - incoming_fragment: &[u8], -) -> Result<(usize, usize, [u8; AES_GCM_NONCE_SIZE]), ReceiveError> { +fn parse_fragment_header(incoming_fragment: &[u8]) -> Result<(usize, usize, [u8; AES_GCM_NONCE_SIZE]), ReceiveError> { let fragment_no = incoming_fragment[FRAGMENT_NO_IDX] as usize; let fragment_count = incoming_fragment[FRAGMENT_COUNT_IDX] as usize; if fragment_no >= fragment_count || fragment_count > MAX_FRAGMENTS { @@ -223,11 +221,8 @@ impl Context { let session = ctx.session_map.read().unwrap().get(&kid_recv).map(|r| r.upgrade()); if let Some(Some(session)) = session { let state = session.state.read().unwrap(); - state.hk_recv.decrypt_in_place( - (&mut incoming_fragment[HEADER_AUTH_START..HEADER_AUTH_END]) - .try_into() - .unwrap(), - ); + let header_auth = &mut incoming_fragment[HEADER_AUTH_START..HEADER_AUTH_END]; + state.hk_recv.decrypt_in_place(header_auth.try_into().unwrap()); let (fragment_no, fragment_count, nonce) = parse_fragment_header(incoming_fragment)?; let (packet_type, incoming_counter) = from_nonce(&nonce); From 9de883f39303b3d0d331c56bc4a0905e3837061e Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 14 Aug 2023 11:27:28 -0400 Subject: [PATCH 33/50] reformatted --- src/result.rs | 4 +- src/zeta.rs | 171 +++++++++++++++++++++++++------------------------- src/zssp.rs | 40 ++++++------ 3 files changed, 107 insertions(+), 108 deletions(-) diff --git a/src/result.rs b/src/result.rs index a972597..126b903 100644 --- a/src/result.rs +++ b/src/result.rs @@ -112,7 +112,7 @@ pub enum ReceiveError { IoError(std::io::Error), } -macro_rules! byzantine_fault { +macro_rules! fault { ($name:expr, $unnatural:ident) => { ReceiveError::ByzantineFault { #[cfg(feature = "debug")] @@ -124,7 +124,7 @@ macro_rules! byzantine_fault { } }; } -pub(crate) use byzantine_fault; +pub(crate) use fault; /// Result generated by the context packet receive function, with possible payloads. #[derive(Clone)] diff --git a/src/zeta.rs b/src/zeta.rs index 068f964..2eb7f5a 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -17,7 +17,7 @@ use crate::fragged::Fragged; use crate::indexed_heap::BinaryHeapIndex; use crate::proto::*; use crate::ratchet_state::{RatchetState, RatchetStates}; -use crate::result::{byzantine_fault, FaultType, OpenError, ReceiveError, SendError}; +use crate::result::{fault, FaultType, OpenError, ReceiveError, SendError}; use crate::symmetric_state::SymmetricState; use crate::zssp::{log, ContextInner, SessionQueue}; #[cfg(feature = "logging")] @@ -125,19 +125,17 @@ impl Default for DuplexKey { } impl DuplexKey { fn replace_nk(&mut self, nk_send: &[u8; HASHLEN], nk_recv: &[u8; HASHLEN]) { - self.nk = Some(Crypto::AeadPool::new( - (&nk_send[..AES_256_KEY_SIZE]).try_into().unwrap(), - (&nk_recv[..AES_256_KEY_SIZE]).try_into().unwrap(), - )) + let nk_send = (&nk_send[..AES_256_KEY_SIZE]).try_into().unwrap(); + let nk_recv = (&nk_recv[..AES_256_KEY_SIZE]).try_into().unwrap(); + self.nk = Some(Crypto::AeadPool::new(nk_send, nk_recv)) } } impl Keys { fn replace_kek(&mut self, kek: &[u8; HASHLEN]) { // We want to give rust the best chance of implementing this in a way that does // not leak the key on the stack. - self.kek - .get_or_insert(Zeroizing::new([0u8; AES_256_KEY_SIZE])) - .copy_from_slice(&kek[..AES_256_KEY_SIZE]); + let old_kek = self.kek.get_or_insert(Zeroizing::new([0u8; AES_256_KEY_SIZE])); + old_kek.copy_from_slice(&kek[..AES_256_KEY_SIZE]); } } @@ -151,6 +149,7 @@ impl MutableState { } impl SymmetricState { + #[must_use] fn write_e_no_init( &mut self, hash: &mut Crypto::Hash, @@ -165,6 +164,7 @@ impl SymmetricState { self.mix_key_no_init(hmac, &pub_key); e_secret } + #[must_use] fn read_e_no_init( &mut self, hash: &mut Crypto::Hash, @@ -179,6 +179,7 @@ impl SymmetricState { *i = j; Crypto::PublicKey::from_bytes((pub_key).try_into().unwrap()) } + #[must_use] fn mix_dh(&mut self, hmac: &mut Crypto::Hmac, secret: &Crypto::KeyPair, remote: &Crypto::PublicKey) -> Option<()> { let mut ecdh_secret = Zeroizing::new([0u8; P384_ECDH_SHARED_SECRET_SIZE]); if secret.agree(&remote, &mut ecdh_secret) { @@ -188,6 +189,7 @@ impl SymmetricState { None } } + #[must_use] fn mix_dh_no_init( &mut self, hmac: &mut Crypto::Hmac, @@ -451,11 +453,11 @@ pub(crate) fn received_x1_trans e, es, e1 // <- e, ee, ekem1, psk if !(HANDSHAKE_HELLO_MIN_SIZE..=HANDSHAKE_HELLO_MAX_SIZE).contains(&x1.len()) { - return Err(byzantine_fault!(InvalidPacket, true)); + return Err(fault!(InvalidPacket, true)); } if &n[AES_GCM_NONCE_SIZE - 8..] != &x1[x1.len() - 8..] { - return Err(byzantine_fault!(FailedAuth, true)); + return Err(fault!(FailedAuth, true)); } let hmac = &mut Crypto::Hmac::new(); let mut noise = SymmetricState::::initialize(PROTOCOL_NAME_NOISE_XK); @@ -463,24 +465,24 @@ pub(crate) fn received_x1_trans s, se if HANDSHAKE_RESPONSE_SIZE != x2.len() { - return Err(byzantine_fault!(InvalidPacket, true)); + return Err(fault!(InvalidPacket, true)); } let kex_lock = session.state_machine_lock.lock().unwrap(); @@ -607,42 +609,42 @@ pub(crate) fn received_x2_trans= COUNTER_WINDOW_MAX_SKIP_AHEAD || &n[AES_GCM_NONCE_SIZE - 3..] != &x2[x2.len() - 3..] { - return Err(byzantine_fault!(FailedAuth, true)); + return Err(fault!(FailedAuth, true)); } let mut should_warn_missing_ratchet = false; let mut result = (|| { let a1 = if let ZetaAutomata::A1(a1) = &state.beta { a1 } else { - return Err(byzantine_fault!(FailedAuth, true)); + return Err(fault!(FailedAuth, true)); }; let mut noise = a1.noise.clone(); let mut i = 0; // Process message pattern 2 e token. let e_remote = noise .read_e_no_init(hash, hmac, &mut i, &x2) - .ok_or(byzantine_fault!(FailedAuth, true))?; + .ok_or(fault!(FailedAuth, true))?; // Process message pattern 2 ee token. noise .mix_dh(hmac, &a1.e_secret, &e_remote) - .ok_or(byzantine_fault!(FailedAuth, true))?; + .ok_or(fault!(FailedAuth, true))?; // Process message pattern 2 ekem1 token. let j = i + KYBER_CIPHERTEXT_SIZE; let k = j + AES_GCM_TAG_SIZE; let tag = x2[j..k].try_into().unwrap(); if !noise.decrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), &mut x2[i..j], tag) { - return Err(byzantine_fault!(FailedAuth, true)); + return Err(fault!(FailedAuth, true)); } let mut ekem1_secret = Zeroizing::new([0u8; KYBER_PLAINTEXT_SIZE]); if !a1 .e1_secret .decapsulate((&x2[i..j]).try_into().unwrap(), &mut ekem1_secret) { - return Err(byzantine_fault!(FailedAuth, true)); + return Err(fault!(FailedAuth, true)); } noise.mix_key_no_init(hmac, ekem1_secret.as_ref()); drop(ekem1_secret); @@ -690,7 +692,7 @@ pub(crate) fn received_x2_trans::new(); x3.extend([0u8; HEADER_SIZE]); @@ -702,7 +704,7 @@ pub(crate) fn received_x2_trans s, se if x3.len() < HANDSHAKE_COMPLETION_MIN_SIZE { - return Err(byzantine_fault!(InvalidPacket, true)); + return Err(fault!(InvalidPacket, true)); } if kid != zeta.kid_recv { - return Err(byzantine_fault!(UnknownLocalKeyId, true)); + return Err(fault!(UnknownLocalKeyId, true)); } let hash = &mut Crypto::Hash::new(); let hmac = &mut Crypto::Hmac::new(); @@ -832,21 +834,20 @@ pub(crate) fn received_x3_trans return Err(byzantine_fault!(OutOfSequence, false)), + Occupied(_) => return Err(fault!(OutOfSequence, false)), Vacant(entry) => entry, }; let mut session_queue = ctx.session_queue.lock().unwrap(); @@ -996,7 +997,7 @@ pub(crate) fn received_c1_trans::new(); c2.extend([0u8; HEADER_SIZE]); if !send_control(session, &state, PACKET_TYPE_ACK, c2, send) { - return Err(byzantine_fault!(OutOfSequence, true)); + return Err(fault!(OutOfSequence, true)); } Ok(just_establised) @@ -1081,7 +1082,7 @@ pub(crate) fn received_c2_trans( use FaultType::*; if d.len() != SESSION_REJECTED_SIZE { - return Err(byzantine_fault!(InvalidPacket, true)); + return Err(fault!(InvalidPacket, true)); } let kex_lock = session.state_machine_lock.lock().unwrap(); let state = session.state.read().unwrap(); if Some(kid) != state.key_ref(true).recv.kid || !matches!(&state.beta, ZetaAutomata::A3 { .. }) { - return Err(byzantine_fault!(OutOfSequence, true)); + return Err(fault!(OutOfSequence, true)); } let tag = d[..].try_into().unwrap(); if !Crypto::Aead::decrypt_in_place(state.key_ref(true).recv.kek.as_ref().unwrap(), n, &[], &mut [], tag) { - return Err(byzantine_fault!(FailedAuth, true)); + return Err(fault!(FailedAuth, true)); } let (_, c) = from_nonce(n); if !session.window.update(c) { - return Err(byzantine_fault!(ExpiredCounter, true)); + return Err(fault!(ExpiredCounter, true)); } drop(state); @@ -1352,7 +1353,7 @@ pub(crate) fn received_k1_trans psk, e, es, ss // <- e, ee, se if k1.len() != REKEY_SIZE { - return Err(byzantine_fault!(InvalidPacket, true)); + return Err(fault!(InvalidPacket, true)); } let kex_lock = session.state_machine_lock.lock().unwrap(); @@ -1360,7 +1361,7 @@ pub(crate) fn received_k1_trans true, @@ -1369,7 +1370,7 @@ pub(crate) fn received_k1_trans::new(); k2.extend([0u8; HEADER_SIZE]); @@ -1425,11 +1426,11 @@ pub(crate) fn received_k1_trans::new(); c1.extend([0u8; HEADER_SIZE]); if !send_control(&session, &state, PACKET_TYPE_KEY_CONFIRM, c1, send) { - return Err(byzantine_fault!(OutOfSequence, true)); + return Err(fault!(OutOfSequence, true)); } Ok(()) @@ -1744,12 +1745,10 @@ pub(crate) fn receive_payload_in_place( state.keys[1].nk.as_ref() } else { // Should be unreachable unless we are leaking kids somewhere. - return Err(byzantine_fault!(UnknownLocalKeyId, true)); + return Err(fault!(UnknownLocalKeyId, true)); }; - let mut cipher = specified_key - .ok_or(byzantine_fault!(OutOfSequence, true))? - .start_dec(nonce); + let mut cipher = specified_key.ok_or(fault!(OutOfSequence, true))?.start_dec(nonce); let (_, c) = from_nonce(nonce); // NOTE: This only works because we check the size of every received fragment in the receive @@ -1765,13 +1764,13 @@ pub(crate) fn receive_payload_in_place( cipher.decrypt_in_place(&mut fragment[..tag_idx]); if !cipher.finish((&fragment[tag_idx..]).try_into().unwrap()) { - return Err(byzantine_fault!(FailedAuth, true)); + return Err(fault!(FailedAuth, true)); } if !session.window.update(c) { // This error is marked as not happening naturally, but it could occur if something about // the transport protocol is duplicating packets. - return Err(byzantine_fault!(ExpiredCounter, true)); + return Err(fault!(ExpiredCounter, true)); } for fragment in fragments { diff --git a/src/zssp.rs b/src/zssp.rs index 03cfd22..7ba3b41 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -18,7 +18,7 @@ use crate::fragged::Assembled; use crate::handshake_cache::UnassociatedHandshakeCache; use crate::indexed_heap::IndexedBinaryHeap; use crate::proto::*; -use crate::result::{byzantine_fault, FaultType, OpenError, ReceiveError, ReceiveOk, SendError, SessionEvent}; +use crate::result::{fault, FaultType, OpenError, ReceiveError, ReceiveOk, SendError, SessionEvent}; #[cfg(feature = "logging")] use crate::LogEvent::*; @@ -63,7 +63,7 @@ fn parse_fragment_header(incoming_fragment: &[u8]) -> Result<(usize, usize, [u8; let fragment_no = incoming_fragment[FRAGMENT_NO_IDX] as usize; let fragment_count = incoming_fragment[FRAGMENT_COUNT_IDX] as usize; if fragment_no >= fragment_count || fragment_count > MAX_FRAGMENTS { - return Err(byzantine_fault!(FaultType::InvalidPacket, true)); + return Err(fault!(FaultType::InvalidPacket, true)); } let mut nonce = [0u8; AES_GCM_NONCE_SIZE]; nonce[2..].copy_from_slice(&incoming_fragment[PACKET_NONCE_START..HEADER_SIZE]); @@ -211,7 +211,7 @@ impl Context { send_unassociated_mtu = send_unassociated_mtu.max(MIN_TRANSPORT_MTU); let incoming_fragment: &mut [u8] = incoming_fragment_buf.as_mut(); if incoming_fragment.len() < MIN_PACKET_SIZE { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + return Err(fault!(FaultType::InvalidPacket, false)); } let mut fragment_buffer = Assembled::new(); @@ -239,10 +239,10 @@ impl Context { if !matches!(&state.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)); + return Err(fault!(OutOfSequence, false)); } if incoming_counter >= COUNTER_WINDOW_MAX_SKIP_AHEAD { - return Err(byzantine_fault!(ExpiredCounter, true)); + return Err(fault!(ExpiredCounter, true)); } } else if PACKET_TYPE_USES_COUNTER_RANGE.contains(&packet_type) { // For DOS resistant reply-protection we need to check that the given counter is @@ -258,14 +258,14 @@ impl Context { // received the first session key and is reject all of Alice's resends. // This can also occur if a session was manually expired, but not // dropped, and the remote party is still sending us data. - return Err(byzantine_fault!(ExpiredCounter, false)); + return Err(fault!(ExpiredCounter, false)); } } else if packet_type == PACKET_TYPE_HANDSHAKE_COMPLETION { // This can be triggered if Bob successfully received a session key and // needs to reject all of Alice's resends of PACKET_TYPE_NOISE_XK_PATTERN_3. - return Err(byzantine_fault!(InvalidPacket, false)); + return Err(fault!(InvalidPacket, false)); } else { - return Err(byzantine_fault!(InvalidPacket, true)); + return Err(fault!(InvalidPacket, true)); } } @@ -312,7 +312,7 @@ impl Context { for fragment in fragment_buffer.as_ref() { buffer .try_extend_from_slice(&fragment.as_ref()[HEADER_SIZE..]) - .map_err(|_| byzantine_fault!(InvalidPacket, true))?; + .map_err(|_| fault!(InvalidPacket, true))?; } // We have not yet authenticated the sender so we do not report // receiving a packet from them. @@ -405,7 +405,7 @@ impl Context { log!(app, DIsAuthClosedSession(&session)); SessionEvent::Rejected } - _ => return Err(byzantine_fault!(InvalidPacket, true)), // This is unreachable. + _ => return Err(fault!(InvalidPacket, true)), // This is unreachable. } }; Ok(ReceiveOk::Session(session, ret)) @@ -429,7 +429,7 @@ impl Context { { //vrfy if packet_type != PACKET_TYPE_HANDSHAKE_COMPLETION || incoming_counter != 0 { - return Err(byzantine_fault!(InvalidPacket, true)); + return Err(fault!(InvalidPacket, true)); } } @@ -448,7 +448,7 @@ impl Context { for fragment in fragment_buffer.as_ref() { buffer .try_extend_from_slice(&fragment.as_ref()[HEADER_SIZE..]) - .map_err(|_| byzantine_fault!(InvalidPacket, true))?; + .map_err(|_| fault!(InvalidPacket, true))?; } buffer.as_mut() } @@ -479,7 +479,7 @@ impl Context { // This can occur naturally because either Bob's incoming_sessions cache got // full so Alice's incoming session was dropped, or the session this packet // was for was dropped by the application. - return Err(byzantine_fault!(UnknownLocalKeyId, false)); + return Err(fault!(UnknownLocalKeyId, false)); } } } else { @@ -490,7 +490,7 @@ impl Context { { //vrfy if packet_type != PACKET_TYPE_HANDSHAKE_HELLO && packet_type != PACKET_TYPE_CHALLENGE { - return Err(byzantine_fault!(InvalidPacket, true)); + return Err(fault!(InvalidPacket, true)); } } @@ -513,7 +513,7 @@ impl Context { for fragment in fragment_buffer.as_ref() { buffer .try_extend_from_slice(&fragment.as_ref()[HEADER_SIZE..]) - .map_err(|_| byzantine_fault!(InvalidPacket, true))?; + .map_err(|_| fault!(InvalidPacket, true))?; } buffer.as_mut() } @@ -527,7 +527,7 @@ impl Context { if !(HANDSHAKE_HELLO_CHALLENGE_MIN_SIZE..=HANDSHAKE_HELLO_CHALLENGE_MAX_SIZE) .contains(&assembled_packet.len()) { - return Err(byzantine_fault!(InvalidPacket, true)); + return Err(fault!(InvalidPacket, true)); } // Process recv challenge layer. let challenge_start = assembled_packet.len() - CHALLENGE_SIZE; @@ -554,7 +554,7 @@ impl Context { send_unassociated_reply(&mut challenge_packet); // If we issue a challenge the first hello packet will always fail. - return Err(byzantine_fault!(FailedAuth, false)); + return Err(fault!(FailedAuth, false)); } else { log!(app, X1SucceededChallenge); } @@ -580,7 +580,7 @@ impl Context { log!(app, ReceivedRawChallenge); // Process recv challenge layer. if assembled_packet.len() != KID_SIZE + CHALLENGE_SIZE { - return Err(byzantine_fault!(InvalidPacket, true)); + return Err(fault!(InvalidPacket, true)); } if let Some(kid_recv) = NonZeroU32::new(u32::from_ne_bytes(assembled_packet[..KID_SIZE].try_into().unwrap())) @@ -591,9 +591,9 @@ impl Context { return Ok(ReceiveOk::Unassociated); } } - Err(byzantine_fault!(UnknownLocalKeyId, true)) + Err(fault!(UnknownLocalKeyId, true)) } else { - Err(byzantine_fault!(InvalidPacket, true)) + Err(fault!(InvalidPacket, true)) } } } From 77d2d0f4f1cb8a91bee7d2575480faff84b0dc66 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 14 Aug 2023 12:02:57 -0400 Subject: [PATCH 34/50] reformatted imports --- src/zeta.rs | 5 +++-- src/zssp.rs | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/zeta.rs b/src/zeta.rs index 2eb7f5a..59abe4c 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -1,5 +1,3 @@ -use arrayvec::ArrayVec; -use rand_core::RngCore; use std::cmp::Reverse; use std::collections::HashMap; use std::io::Write; @@ -7,6 +5,9 @@ use std::num::NonZeroU32; use std::ops::{Deref, DerefMut}; use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering}; use std::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockReadGuard, Weak}; + +use arrayvec::ArrayVec; +use rand_core::RngCore; use zeroize::Zeroizing; use crate::antireplay::Window; diff --git a/src/zssp.rs b/src/zssp.rs index 7ba3b41..7190c3a 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -11,7 +11,6 @@ use rand_core::RngCore; use crate::challenge::ChallengeContext; use crate::crypto::*; use crate::zeta::*; - use crate::application::*; use crate::frag_cache::UnassociatedFragCache; use crate::fragged::Assembled; From 91c00f318b1d2a2286ff4832ebfdb77b131c5cc0 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 14 Aug 2023 12:25:33 -0400 Subject: [PATCH 35/50] added benchmark --- examples/basic_test.rs | 2 +- examples/benchmark.rs | 314 +++++++++++++++++++++++++++++++++++++++++ src/application.rs | 3 +- src/zeta.rs | 2 +- src/zssp.rs | 7 +- 5 files changed, 322 insertions(+), 6 deletions(-) create mode 100644 examples/benchmark.rs diff --git a/examples/basic_test.rs b/examples/basic_test.rs index 9d9bdc8..11e5d0b 100644 --- a/examples/basic_test.rs +++ b/examples/basic_test.rs @@ -68,7 +68,7 @@ impl ApplicationLayer for &TestApplication { type Crypto = TestApplication; fn incoming_session(&mut self) -> IncomingSessionAction { - IncomingSessionAction::Allow + IncomingSessionAction::Challenge } fn hello_requires_recognized_ratchet(&mut self) -> bool { diff --git a/examples/benchmark.rs b/examples/benchmark.rs new file mode 100644 index 0000000..52ce563 --- /dev/null +++ b/examples/benchmark.rs @@ -0,0 +1,314 @@ +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{mpsc, Arc}; +use std::thread; +use std::time::{Duration, Instant}; + +use arrayvec::ArrayVec; +use rand_core::OsRng; +use rand_core::RngCore; + +use zssp::application::{ + AcceptAction, ApplicationLayer, CryptoLayer, IncomingSessionAction, RatchetState, RatchetStates, RatchetUpdate, + RATCHET_SIZE, +}; +use zssp::crypto::P384KeyPair; +use zssp::crypto_impl::*; +use zssp::result::ReceiveError; +use zssp::Session; + +const TEST_MTU: usize = 1500; + +struct TestApplication { + time: Instant, +} + +#[allow(unused)] +impl CryptoLayer for TestApplication { + type Rng = OsRng; + type PrpEnc = Aes256OpenSSLEnc; + type PrpDec = Aes256OpenSSLDec; + type Aead = AesGcmOpenSSL; + type AeadPool = AesGcmOpenSSLPool; + type Hash = Sha512Crate; + type Hmac = HmacSha512Crate; + type PublicKey = P384CratePublicKey; + type KeyPair = P384CrateKeyPair; + type Kem = RustKyber1024PrivateKey; + + type SessionData = (); + + type IncomingPacketBuffer = Vec; +} +#[allow(unused)] +impl ApplicationLayer for &TestApplication { + type Crypto = TestApplication; + + fn incoming_session(&mut self) -> IncomingSessionAction { + IncomingSessionAction::Allow + } + + fn hello_requires_recognized_ratchet(&mut self) -> bool { + false + } + + fn initiator_disallows_downgrade(&mut self, session: &Arc>) -> bool { + false + } + + fn check_accept_session( + &mut self, + remote_static_key: &P384CratePublicKey, + identity: &[u8], + ) -> AcceptAction { + AcceptAction { + session_data: Some(()), + responder_disallows_downgrade: true, + responder_silently_rejects: false, + } + } + + fn restore_by_fingerprint(&mut self, ratchet_fingerprint: &[u8; RATCHET_SIZE]) -> Result, ()> { + Ok(None) + } + + fn restore_by_identity( + &mut self, + remote_static_key: &P384CratePublicKey, + session_data: &(), + ) -> Result, ()> { + Ok(None) + } + + fn save_ratchet_state( + &mut self, + remote_static_key: &P384CratePublicKey, + session_data: &(), + update_data: RatchetUpdate<'_>, + ) -> Result<(), ()> { + Ok(()) + } + + fn time(&mut self) -> i64 { + self.time.elapsed().as_millis() as i64 + } +} + +#[allow(unused)] +fn alice_main( + run: &AtomicBool, + alice_app: &TestApplication, + alice_out: mpsc::SyncSender>, + alice_in: mpsc::Receiver>, + alice_keypair: P384CrateKeyPair, + bob_pubkey: P384CratePublicKey, +) { + let startup_time = std::time::Instant::now(); + let context = zssp::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; + + let alice_session = Some( + context + .open( + alice_app, + |b| alice_out.send(b.to_vec()).is_ok(), + TEST_MTU, + bob_pubkey.clone(), + (), + &[], + ) + .unwrap(), + ); + println!("[alice] opening session"); + while run.load(Ordering::Relaxed) { + let current_time = startup_time.elapsed().as_millis() as i64; + loop { + let pkt = alice_in.try_recv(); + if let Ok(pkt) = pkt { + use zssp::result::ReceiveOk::*; + use zssp::result::SessionEvent::*; + let mut output_data = Vec::new(); + match context.receive( + alice_app, + |b| alice_out.send(b.to_vec()).is_ok(), + TEST_MTU, + |_| Some((|b: &mut [u8]| alice_out.send(b.to_vec()).is_ok(), TEST_MTU)), + &0, + pkt, + &mut output_data, + ) { + Ok(Unassociated) => { + //println!("[alice] ok"); + } + Ok(Session(_, event)) => match event { + Established => { + up = true; + } + Data => { + assert!(!output_data.is_empty()); + //println!("[alice] received {}", data.len()); + } + Control => (), + _ => panic!(), + }, + Err(e) => { + println!("[alice] ERROR {:?}", e); + if let ReceiveError::ByzantineFault { unnatural, .. } = e { + assert!(!unnatural) + } + } + } + } else { + break; + } + } + + if up { + context + .send( + alice_session.as_ref().unwrap(), + |b| alice_out.send(b.to_vec()).is_ok(), + &mut [0u8; TEST_MTU], + &test_data[..1400 + ((OsRng.next_u64() as usize) % (test_data.len() - 1400))], + ) + .unwrap(); + } else { + thread::sleep(Duration::from_millis(10)); + } + + if current_time >= next_service { + next_service = current_time + + context.service(alice_app, |_| { + Some((|b: &mut [u8]| alice_out.send(b.to_vec()).is_ok(), TEST_MTU)) + }); + } + } +} + +#[allow(unused)] +fn bob_main( + run: &AtomicBool, + bob_app: &TestApplication, + bob_out: mpsc::SyncSender>, + bob_in: mpsc::Receiver>, + bob_keypair: P384CrateKeyPair, +) { + let startup_time = std::time::Instant::now(); + let context = zssp::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; + let mut output_data = ArrayVec::::new(); + + 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 { + use zssp::result::ReceiveOk::*; + use zssp::result::SessionEvent::*; + match context.receive( + bob_app, + |b| bob_out.send(b.to_vec()).is_ok(), + TEST_MTU, + |_| Some((|b: &mut [u8]| bob_out.send(b.to_vec()).is_ok(), TEST_MTU)), + &0, + pkt, + &mut output_data, + ) { + Ok(Unassociated) => {} + Ok(Session(s, event)) => match event { + NewSession | NewDowngradedSession => { + println!("[bob] new session, took {}s", current_time as f32 / 1000.0); + let _ = bob_session.replace(s); + } + Data => { + assert!(!output_data.is_empty()); + //println!("[bob] received {}", output_data.len()); + transferred += output_data.len() as u64 * 2; // *2 because we are also sending this many bytes back + context + .send( + &s, + |b| bob_out.send(b.to_vec()).is_ok(), + &mut [0u8; TEST_MTU], + &output_data, + ) + .unwrap(); + } + Control => (), + _ => panic!(), + }, + Err(e) => { + println!("[bob] ERROR {:?}", e); + if let ReceiveError::ByzantineFault { unnatural, .. } = e { + assert!(!unnatural) + } + } + } + } + + 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: &mut [u8]| bob_out.send(b.to_vec()).is_ok(), TEST_MTU)) + }); + } + } +} + +fn core(time: u64) { + let run = &AtomicBool::new(true); + + let alice_keypair = P384CrateKeyPair::generate(&mut OsRng); + let alice_app = TestApplication { time: Instant::now()}; + let bob_keypair = P384CrateKeyPair::generate(&mut OsRng); + let bob_pubkey = bob_keypair.public_key(); + let bob_app = TestApplication { time: Instant::now() }; + + 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(); + ts.spawn(move || { + alice_main( + run, + &alice_app, + alice_out, + alice_in, + alice_keypair, + bob_pubkey, + ) + }); + } + ts.spawn(move || bob_main(run, &bob_app, bob_out, bob_in, bob_keypair)); + + thread::sleep(Duration::from_secs(time)); + + run.store(false, Ordering::SeqCst); + println!("finished"); + }); +} + +fn main() { + core(60 * 60) +} + +#[test] +fn test_main() { + core(2) +} diff --git a/src/application.rs b/src/application.rs index fe94c9f..3246920 100644 --- a/src/application.rs +++ b/src/application.rs @@ -243,7 +243,8 @@ 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: crate::LogEvent<'_, Self::Crypto>); + #[allow(unused)] + fn event_log(&mut self, event: crate::LogEvent<'_, Self::Crypto>) {} } #[derive(Debug, PartialEq, Eq, Clone)] diff --git a/src/zeta.rs b/src/zeta.rs index 59abe4c..f6d3cb7 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -229,7 +229,7 @@ pub(crate) fn from_nonce(n: &[u8]) -> (u8, u64) { let c_start = n.len() - 8; (n[c_start - 1], u64::from_be_bytes(n[c_start..].try_into().unwrap())) } -fn set_header(packet: &mut [u8], kid_send: u32, nonce: &[u8; AES_GCM_NONCE_SIZE]) { +pub(crate) fn set_header(packet: &mut [u8], kid_send: u32, nonce: &[u8; AES_GCM_NONCE_SIZE]) { packet[..KID_SIZE].copy_from_slice(&kid_send.to_ne_bytes()); packet[PACKET_NONCE_START..HEADER_SIZE].copy_from_slice(&nonce[NONCE_SIZE_DIFF..]); } diff --git a/src/zssp.rs b/src/zssp.rs index 7190c3a..2996c0c 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -8,16 +8,16 @@ use std::sync::{Arc, Mutex, RwLock, Weak}; use arrayvec::ArrayVec; use rand_core::RngCore; +use crate::application::*; use crate::challenge::ChallengeContext; use crate::crypto::*; -use crate::zeta::*; -use crate::application::*; use crate::frag_cache::UnassociatedFragCache; use crate::fragged::Assembled; use crate::handshake_cache::UnassociatedHandshakeCache; use crate::indexed_heap::IndexedBinaryHeap; use crate::proto::*; use crate::result::{fault, FaultType, OpenError, ReceiveError, ReceiveOk, SendError, SessionEvent}; +use crate::zeta::*; #[cfg(feature = "logging")] use crate::LogEvent::*; @@ -549,7 +549,8 @@ impl Context { challenge_packet.extend(challenge); let nonce = to_nonce(PACKET_TYPE_CHALLENGE, ctx.rng.lock().unwrap().next_u64()); challenge_packet[FRAGMENT_COUNT_IDX] = 1; - challenge_packet[PACKET_NONCE_START..HEADER_SIZE].copy_from_slice(&nonce); + challenge_packet[PACKET_NONCE_START..HEADER_SIZE].copy_from_slice(&nonce[..PACKET_NONCE_SIZE]); + set_header(&mut challenge_packet, 0, &nonce); send_unassociated_reply(&mut challenge_packet); // If we issue a challenge the first hello packet will always fail. From ad8272bf4acb2b12458d9b88ed4dfaa36af13624 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 14 Aug 2023 13:07:03 -0400 Subject: [PATCH 36/50] fixed benchmark --- examples/benchmark.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/benchmark.rs b/examples/benchmark.rs index 52ce563..0defb8d 100644 --- a/examples/benchmark.rs +++ b/examples/benchmark.rs @@ -107,6 +107,7 @@ fn alice_main( 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 output_data = ArrayVec::::new(); let alice_session = Some( context @@ -128,7 +129,7 @@ fn alice_main( if let Ok(pkt) = pkt { use zssp::result::ReceiveOk::*; use zssp::result::SessionEvent::*; - let mut output_data = Vec::new(); + output_data.clear(); match context.receive( alice_app, |b| alice_out.send(b.to_vec()).is_ok(), @@ -210,6 +211,7 @@ fn bob_main( if let Ok(pkt) = pkt { use zssp::result::ReceiveOk::*; use zssp::result::SessionEvent::*; + output_data.clear(); match context.receive( bob_app, |b| bob_out.send(b.to_vec()).is_ok(), From 9f4cd4bfcddd7e0597459ae3d0861595349dfd9e Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 14 Aug 2023 13:28:21 -0400 Subject: [PATCH 37/50] removed extra lines --- examples/benchmark.rs | 13 ++----------- src/zeta.rs | 7 ++----- src/zssp.rs | 3 ++- 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/examples/benchmark.rs b/examples/benchmark.rs index 0defb8d..b8b9e4e 100644 --- a/examples/benchmark.rs +++ b/examples/benchmark.rs @@ -275,7 +275,7 @@ fn core(time: u64) { let run = &AtomicBool::new(true); let alice_keypair = P384CrateKeyPair::generate(&mut OsRng); - let alice_app = TestApplication { time: Instant::now()}; + let alice_app = TestApplication { time: Instant::now() }; let bob_keypair = P384CrateKeyPair::generate(&mut OsRng); let bob_pubkey = bob_keypair.public_key(); let bob_app = TestApplication { time: Instant::now() }; @@ -286,16 +286,7 @@ fn core(time: u64) { thread::scope(|ts| { { let alice_out = alice_out.clone(); - ts.spawn(move || { - alice_main( - run, - &alice_app, - alice_out, - alice_in, - alice_keypair, - bob_pubkey, - ) - }); + ts.spawn(move || alice_main(run, &alice_app, alice_out, alice_in, alice_keypair, bob_pubkey)); } ts.spawn(move || bob_main(run, &bob_app, bob_out, bob_in, bob_keypair)); diff --git a/src/zeta.rs b/src/zeta.rs index f6d3cb7..b2d9b52 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -1705,11 +1705,8 @@ pub(crate) fn send_payload( ); mtu_sized_buffer[HEADER_SIZE + payload_rem..HEADER_SIZE + fragment_len].copy_from_slice(&cipher.finish()); - state.hk_send.encrypt_in_place( - (&mut mtu_sized_buffer[HEADER_AUTH_START..HEADER_AUTH_END]) - .try_into() - .unwrap(), - ); + let header_auth = &mut mtu_sized_buffer[HEADER_AUTH_START..HEADER_AUTH_END]; + state.hk_send.encrypt_in_place(header_auth.try_into().unwrap()); if !send(&mut mtu_sized_buffer[..HEADER_SIZE + fragment_len]) { return Ok(()); diff --git a/src/zssp.rs b/src/zssp.rs index 2996c0c..27b24a6 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -549,7 +549,8 @@ impl Context { challenge_packet.extend(challenge); let nonce = to_nonce(PACKET_TYPE_CHALLENGE, ctx.rng.lock().unwrap().next_u64()); challenge_packet[FRAGMENT_COUNT_IDX] = 1; - challenge_packet[PACKET_NONCE_START..HEADER_SIZE].copy_from_slice(&nonce[..PACKET_NONCE_SIZE]); + challenge_packet[PACKET_NONCE_START..HEADER_SIZE] + .copy_from_slice(&nonce[..PACKET_NONCE_SIZE]); set_header(&mut challenge_packet, 0, &nonce); send_unassociated_reply(&mut challenge_packet); From ea46c2bced38b7ccbaff987676b67536a66d2ba4 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 14 Aug 2023 16:21:21 -0400 Subject: [PATCH 38/50] added profiler --- .gitignore | 1 + Cargo.toml | 3 + examples/benchmark.rs | 15 +- flamegraph.svg | 491 ++++++++++++++++++++++++++++++++++++++++++ src/zeta.rs | 26 +-- src/zssp.rs | 3 +- 6 files changed, 508 insertions(+), 31 deletions(-) create mode 100644 flamegraph.svg diff --git a/.gitignore b/.gitignore index ea8c4bf..790f4b6 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +./perf* diff --git a/Cargo.toml b/Cargo.toml index 48a75dc..80513a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,9 @@ name = "zssp" path = "src/lib.rs" doc = true +[profile.bench] +debug = true + [dependencies] rand_core = { version = "0.6.4" } zeroize = { version = "1.6.0" } diff --git a/examples/benchmark.rs b/examples/benchmark.rs index 0defb8d..b2ec3fb 100644 --- a/examples/benchmark.rs +++ b/examples/benchmark.rs @@ -275,7 +275,7 @@ fn core(time: u64) { let run = &AtomicBool::new(true); let alice_keypair = P384CrateKeyPair::generate(&mut OsRng); - let alice_app = TestApplication { time: Instant::now()}; + let alice_app = TestApplication { time: Instant::now() }; let bob_keypair = P384CrateKeyPair::generate(&mut OsRng); let bob_pubkey = bob_keypair.public_key(); let bob_app = TestApplication { time: Instant::now() }; @@ -286,16 +286,7 @@ fn core(time: u64) { thread::scope(|ts| { { let alice_out = alice_out.clone(); - ts.spawn(move || { - alice_main( - run, - &alice_app, - alice_out, - alice_in, - alice_keypair, - bob_pubkey, - ) - }); + ts.spawn(move || alice_main(run, &alice_app, alice_out, alice_in, alice_keypair, bob_pubkey)); } ts.spawn(move || bob_main(run, &bob_app, bob_out, bob_in, bob_keypair)); @@ -307,7 +298,7 @@ fn core(time: u64) { } fn main() { - core(60 * 60) + core(20) } #[test] diff --git a/flamegraph.svg b/flamegraph.svg new file mode 100644 index 0000000..8075ab4 --- /dev/null +++ b/flamegraph.svg @@ -0,0 +1,491 @@ +Flame Graph Reset ZoomSearch <zssp::crypto_impl::openssl::Aes256OpenSSLDec as zssp::crypto::aes::Aes256Dec>::decrypt_in_place (13 samples, 0.03%)zssp::crypto_impl::openssl::CipherCtx::update (5 samples, 0.01%)<zssp::crypto_impl::openssl::Aes256OpenSSLEnc as zssp::crypto::aes::Aes256Enc>::encrypt_in_place (35 samples, 0.09%)zssp::crypto_impl::openssl::CipherCtx::update (5 samples, 0.01%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::encrypt (71 samples, 0.18%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_enc (8 samples, 0.02%)CRYPTO_THREAD_read_lock (7 samples, 0.02%)CRYPTO_gcm128_decrypt (8 samples, 0.02%)CRYPTO_gcm128_decrypt_ctr32 (21 samples, 0.05%)CRYPTO_gcm128_encrypt (10 samples, 0.03%)CRYPTO_gcm128_encrypt_ctr32 (16 samples, 0.04%)CRYPTO_gcm128_finish (4 samples, 0.01%)CRYPTO_gcm128_init (9 samples, 0.02%)CRYPTO_gcm128_tag (5 samples, 0.01%)CRYPTO_zalloc (25 samples, 0.06%)EVP_CIPHER_CTX_ctrl (5 samples, 0.01%)EVP_CIPHER_CTX_reset (7 samples, 0.02%)EVP_CIPHER_CTX_set_padding (12 samples, 0.03%)EVP_CIPHER_fetch (6 samples, 0.02%)EVP_CIPHER_free (17 samples, 0.04%)EVP_DecryptUpdate (4 samples, 0.01%)EVP_EncryptFinal_ex (6 samples, 0.02%)OPENSSL_LH_retrieve (10 samples, 0.03%)OPENSSL_strnlen (5 samples, 0.01%)OSSL_PARAM_locate (121 samples, 0.31%)OSSL_PARAM_set_uint64 (4 samples, 0.01%)[libc.so.6] (60 samples, 0.15%)[libcrypto.so.3] (289 samples, 0.74%)cfree (13 samples, 0.03%)malloc (31 samples, 0.08%)pthread_rwlock_rdlock (5 samples, 0.01%)pthread_rwlock_unlock (7 samples, 0.02%)std::sync::mpmc::Sender<T>::send (9 samples, 0.02%)<std::sync::mpmc::select::Token as core::default::Default>::default (11 samples, 0.03%)std::sync::mpmc::array::Channel<T>::start_send (11 samples, 0.03%)std::sync::mpmc::array::Channel<T>::send (45 samples, 0.12%)std::sync::mpmc::array::Channel<T>::write (7 samples, 0.02%)<std::sync::mpmc::select::Token as core::default::Default>::default (5 samples, 0.01%)std::sync::mpmc::array::Channel<T>::try_recv (19 samples, 0.05%)std::sync::mpmc::utils::Backoff::new (11 samples, 0.03%)core::sync::atomic::AtomicBool::load (10 samples, 0.03%)core::sync::atomic::atomic_load (10 samples, 0.03%)std::sync::mpmc::waker::SyncWaker::notify (11 samples, 0.03%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (7 samples, 0.02%)std::time::Instant::elapsed (7 samples, 0.02%)syscall (10 samples, 0.03%)__entry_text_start (6 samples, 0.02%)[anon] (1,024 samples, 2.62%)[a..zssp::zeta::receive_payload_in_place (25 samples, 0.06%)std::io::impls::<impl std::io::Write for &mut W>::write (7 samples, 0.02%)<arrayvec::arrayvec::ArrayVec<u8,_> as std::io::Write>::write (7 samples, 0.02%)arrayvec::arrayvec::ArrayVec<T,_>::try_extend_from_slice (7 samples, 0.02%)core::intrinsics::copy_nonoverlapping (7 samples, 0.02%)<std::sync::mpmc::zero::ZeroToken as core::default::Default>::default (4 samples, 0.01%)EVP_DecryptUpdate (4 samples, 0.01%)[libc.so.6] (5 samples, 0.01%)std::sync::mpmc::array::Channel<T>::send (8 samples, 0.02%)std::sync::mpmc::array::Channel<T>::start_recv (9 samples, 0.02%)[benchmark] (54 samples, 0.14%)zssp::zssp::Context<Crypto>::receive (17 samples, 0.04%)CRYPTO_THREAD_read_lock (17 samples, 0.04%)CRYPTO_THREAD_run_once (4 samples, 0.01%)CRYPTO_THREAD_unlock (19 samples, 0.05%)CRYPTO_gcm128_finish (14 samples, 0.04%)CRYPTO_gcm128_setiv (7 samples, 0.02%)CRYPTO_get_ex_data (10 samples, 0.03%)OPENSSL_sk_value (4 samples, 0.01%)OSSL_PARAM_construct_size_t (5 samples, 0.01%)[libc.so.6] (17 samples, 0.04%)[libcrypto.so.3] (145 samples, 0.37%)pthread_getspecific (7 samples, 0.02%)pthread_rwlock_rdlock (22 samples, 0.06%)[libcrypto.so.3] (316 samples, 0.81%)pthread_rwlock_unlock (22 samples, 0.06%)<core::num::nonzero::NonZeroU32 as core::hash::Hash>::hash (14 samples, 0.04%)<zssp::crypto_impl::openssl::Aes256OpenSSLDec as zssp::crypto::aes::Aes256Dec>::decrypt_in_place (14 samples, 0.04%)<zssp::crypto_impl::openssl::Aes256OpenSSLEnc as zssp::crypto::aes::Aes256Enc>::encrypt_in_place (4 samples, 0.01%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::decrypt_in_place (80 samples, 0.20%)zssp::crypto_impl::openssl::CipherCtx::update (10 samples, 0.03%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::finish (5 samples, 0.01%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::encrypt (8 samples, 0.02%)zssp::crypto_impl::openssl::CipherCtx::update (8 samples, 0.02%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_dec (7 samples, 0.02%)CRYPTO_THREAD_read_lock (6 samples, 0.02%)CRYPTO_THREAD_unlock (11 samples, 0.03%)CRYPTO_gcm128_decrypt (10 samples, 0.03%)CRYPTO_gcm128_decrypt_ctr32 (22 samples, 0.06%)CRYPTO_gcm128_encrypt (15 samples, 0.04%)CRYPTO_gcm128_encrypt_ctr32 (28 samples, 0.07%)CRYPTO_strndup (5 samples, 0.01%)EVP_CIPHER_CTX_ctrl (10 samples, 0.03%)EVP_CIPHER_CTX_get_iv_length (4 samples, 0.01%)EVP_CIPHER_get_block_size (9 samples, 0.02%)EVP_DecryptUpdate (54 samples, 0.14%)EVP_EncryptUpdate (45 samples, 0.12%)OPENSSL_LH_retrieve (8 samples, 0.02%)OPENSSL_init_crypto (4 samples, 0.01%)OSSL_PARAM_locate (18 samples, 0.05%)[[vdso]] (8 samples, 0.02%)[benchmark] (13 samples, 0.03%)EVP_DecryptUpdate (13 samples, 0.03%)[libc.so.6] (88 samples, 0.23%)[libcrypto.so.3] (322 samples, 0.82%)__bss_start (11 samples, 0.03%)[libcrypto.so.3] (11 samples, 0.03%)__entry_text_start (39 samples, 0.10%)_copy_to_iter (4 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)_copy_to_iter (37 samples, 0.09%)copyout (29 samples, 0.07%)asm_sysvec_reschedule_ipi (10 samples, 0.03%)sysvec_reschedule_ipi (10 samples, 0.03%)irqentry_exit (10 samples, 0.03%)raw_irqentry_exit_cond_resched (10 samples, 0.03%)preempt_schedule_irq (10 samples, 0.03%)__schedule (10 samples, 0.03%)finish_task_switch.isra.0 (10 samples, 0.03%)__perf_event_task_sched_in (10 samples, 0.03%)perf_pmu_nop_void (5 samples, 0.01%)__memcpy (6 samples, 0.02%)chacha_block_generic (225 samples, 0.58%)chacha_permute (202 samples, 0.52%)__x64_sys_getrandom (367 samples, 0.94%)get_random_bytes_user (354 samples, 0.91%)crng_make_state (276 samples, 0.71%)crng_fast_key_erasure (253 samples, 0.65%)exit_to_user_mode_prepare (4 samples, 0.01%)exit_to_user_mode_prepare (13 samples, 0.03%)fpregs_assert_state_consistent (5 samples, 0.01%)do_syscall_64 (396 samples, 1.01%)syscall_exit_to_user_mode (21 samples, 0.05%)entry_SYSCALL_64_after_hwframe (403 samples, 1.03%)<rand_core::os::OsRng as rand_core::RngCore>::next_u64 (485 samples, 1.24%)rand_core::impls::next_u64_via_fill (485 samples, 1.24%)<rand_core::os::OsRng as rand_core::RngCore>::fill_bytes (485 samples, 1.24%)<rand_core::os::OsRng as rand_core::RngCore>::try_fill_bytes (485 samples, 1.24%)getrandom::getrandom (485 samples, 1.24%)getrandom::getrandom_uninit (485 samples, 1.24%)getrandom::imp::getrandom_inner (484 samples, 1.24%)getrandom::util_libc::sys_fill_exact (484 samples, 1.24%)getrandom::imp::getrandom_inner::_{{closure}} (482 samples, 1.23%)getrandom::imp::getrandom (482 samples, 1.23%)syscall (482 samples, 1.23%)syscall_return_via_sysret (11 samples, 0.03%)[libc.so.6] (18 samples, 0.05%)core::ptr::drop_in_place<alloc::sync::Arc<zssp::zeta::Session<benchmark::TestApplication>>> (7 samples, 0.02%)<alloc::sync::Arc<T> as core::ops::drop::Drop>::drop (7 samples, 0.02%)core::sync::atomic::AtomicUsize::fetch_sub (6 samples, 0.02%)core::sync::atomic::atomic_sub (6 samples, 0.02%)_ZN3std4sync4mpmc5array16Channel$LT$T$GT$10start_recv17h7800ca29c64cb868E.llvm.12455019271255371362 (7 samples, 0.02%)core::result::Result<T,E>::map_err (5 samples, 0.01%)core::mem::maybe_uninit::MaybeUninit<T>::assume_init (15 samples, 0.04%)std::sync::mpmc::array::Channel<T>::read (17 samples, 0.04%)core::slice::<impl [T]>::get_unchecked (8 samples, 0.02%)<usize as core::slice::index::SliceIndex<[T]>>::get_unchecked (8 samples, 0.02%)core::ptr::const_ptr::<impl *const T>::add (8 samples, 0.02%)core::ptr::const_ptr::<impl *const T>::offset (8 samples, 0.02%)core::sync::atomic::AtomicUsize::compare_exchange_weak (47 samples, 0.12%)core::sync::atomic::atomic_compare_exchange_weak (47 samples, 0.12%)core::sync::atomic::AtomicUsize::load (143 samples, 0.37%)core::sync::atomic::atomic_load (143 samples, 0.37%)core::sync::atomic::fence (16 samples, 0.04%)std::sync::mpsc::Receiver<T>::try_recv (299 samples, 0.77%)std::sync::mpmc::Receiver<T>::try_recv (299 samples, 0.77%)std::sync::mpmc::array::Channel<T>::try_recv (287 samples, 0.73%)std::sync::mpmc::array::Channel<T>::start_recv (248 samples, 0.63%)<std::time::Instant as core::ops::arith::Sub>::sub (6 samples, 0.02%)std::time::Instant::duration_since (6 samples, 0.02%)std::time::Instant::checked_duration_since (5 samples, 0.01%)std::sys::unix::time::inner::Instant::checked_sub_instant (5 samples, 0.01%)std::sys::unix::time::Timespec::sub_timespec (5 samples, 0.01%)std::time::Instant::elapsed (28 samples, 0.07%)std::time::Instant::now (22 samples, 0.06%)std::sys::unix::time::inner::Instant::now (22 samples, 0.06%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (22 samples, 0.06%)clock_gettime (21 samples, 0.05%)[[vdso]] (21 samples, 0.05%)[[vdso]] (17 samples, 0.04%)<T as core::convert::TryInto<U>>::try_into (386 samples, 0.99%)core::array::_<impl core::convert::TryFrom<&[T]> for [T: N]>::try_from (386 samples, 0.99%)core::result::Result<T,E>::map (386 samples, 0.99%)<std::sync::mpmc::zero::ZeroToken as core::default::Default>::default (4 samples, 0.01%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (61 samples, 0.16%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (61 samples, 0.16%)std::sys::unix::locks::futex_mutex::Mutex::unlock (58 samples, 0.15%)core::sync::atomic::AtomicU32::swap (56 samples, 0.14%)core::sync::atomic::atomic_swap (56 samples, 0.14%)std::sync::mutex::Mutex<T>::lock (67 samples, 0.17%)std::sys::unix::locks::futex_mutex::Mutex::lock (66 samples, 0.17%)core::sync::atomic::AtomicU32::compare_exchange (65 samples, 0.17%)core::sync::atomic::atomic_compare_exchange (65 samples, 0.17%)EVP_CIPHER_CTX_get_block_size (4 samples, 0.01%)<zssp::crypto_impl::openssl::Aes256OpenSSLDec as zssp::crypto::aes::Aes256Dec>::decrypt_in_place (362 samples, 0.93%)zssp::crypto_impl::openssl::CipherCtx::update (228 samples, 0.58%)EVP_DecryptUpdate (221 samples, 0.57%)[libcrypto.so.3] (203 samples, 0.52%)[libcrypto.so.3] (148 samples, 0.38%)[libcrypto.so.3] (137 samples, 0.35%)__rust_probestack (8 samples, 0.02%)alloc::sync::Weak<T>::upgrade::_{{closure}} (5 samples, 0.01%)core::sync::atomic::AtomicUsize::compare_exchange_weak (54 samples, 0.14%)core::sync::atomic::atomic_compare_exchange_weak (54 samples, 0.14%)alloc::sync::Weak<T>::upgrade (78 samples, 0.20%)core::sync::atomic::AtomicUsize::fetch_update (72 samples, 0.18%)core::sync::atomic::AtomicUsize::load (9 samples, 0.02%)core::sync::atomic::atomic_load (9 samples, 0.02%)core::option::Option<T>::map (82 samples, 0.21%)zssp::zssp::Context<Crypto>::receive::_{{closure}} (82 samples, 0.21%)zssp::zssp::Context<Crypto>::receive (4 samples, 0.01%)core::ptr::drop_in_place<alloc::sync::Arc<zssp::zeta::Session<benchmark::TestApplication>>> (59 samples, 0.15%)<alloc::sync::Arc<T> as core::ops::drop::Drop>::drop (59 samples, 0.15%)core::sync::atomic::AtomicUsize::fetch_sub (54 samples, 0.14%)core::sync::atomic::atomic_sub (54 samples, 0.14%)[libc.so.6] (33 samples, 0.08%)__entry_text_start (6 samples, 0.02%)futex_unqueue (8 samples, 0.02%)update_curr (13 samples, 0.03%)cpuacct_charge (4 samples, 0.01%)dequeue_task (24 samples, 0.06%)dequeue_task_fair (24 samples, 0.06%)dequeue_entity (24 samples, 0.06%)finish_task_switch.isra.0 (12 samples, 0.03%)raw_spin_rq_unlock (7 samples, 0.02%)prepare_task_switch (14 samples, 0.04%)__perf_event_task_sched_out (4 samples, 0.01%)psi_group_change (16 samples, 0.04%)psi_task_switch (31 samples, 0.08%)sched_clock_cpu (6 samples, 0.02%)__schedule (97 samples, 0.25%)futex_wait_queue (109 samples, 0.28%)schedule (102 samples, 0.26%)__get_user_nocheck_4 (11 samples, 0.03%)futex_q_lock (4 samples, 0.01%)futex_q_unlock (4 samples, 0.01%)futex_wait_setup (28 samples, 0.07%)__x64_sys_futex (156 samples, 0.40%)do_futex (154 samples, 0.39%)futex_wait (153 samples, 0.39%)__rseq_handle_notify_resume (10 samples, 0.03%)exit_to_user_mode_loop (17 samples, 0.04%)do_syscall_64 (183 samples, 0.47%)syscall_exit_to_user_mode (22 samples, 0.06%)exit_to_user_mode_prepare (22 samples, 0.06%)__lll_lock_wait_private (227 samples, 0.58%)entry_SYSCALL_64_after_hwframe (185 samples, 0.47%)[libc.so.6] (583 samples, 1.49%)__entry_text_start (7 samples, 0.02%)__x64_sys_futex (6 samples, 0.02%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)raw_irqentry_exit_cond_resched (5 samples, 0.01%)preempt_schedule_irq (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)_raw_spin_lock (8 samples, 0.02%)futex_hash (4 samples, 0.01%)_raw_spin_lock (18 samples, 0.05%)native_queued_spin_lock_slowpath (18 samples, 0.05%)get_futex_key (4 samples, 0.01%)futex_wake (53 samples, 0.14%)__x64_sys_futex (71 samples, 0.18%)do_futex (68 samples, 0.17%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)do_syscall_64 (86 samples, 0.22%)syscall_exit_to_user_mode (14 samples, 0.04%)exit_to_user_mode_prepare (13 samples, 0.03%)entry_SYSCALL_64_after_hwframe (94 samples, 0.24%)alloc::alloc::dealloc (730 samples, 1.87%)a..cfree (727 samples, 1.86%)c..__lll_lock_wake_private (106 samples, 0.27%)<alloc::alloc::Global as core::alloc::Allocator>::deallocate (732 samples, 1.87%)<..core::ptr::drop_in_place<arrayvec::arrayvec::ArrayVec<alloc::vec::Vec<u8>,48_usize>> (738 samples, 1.89%)c..<arrayvec::arrayvec::ArrayVec<T,_> as core::ops::drop::Drop>::drop (738 samples, 1.89%)<..arrayvec::arrayvec::ArrayVec<T,_>::clear (738 samples, 1.89%)a..arrayvec::arrayvec_impl::ArrayVecImpl::clear (738 samples, 1.89%)a..arrayvec::arrayvec_impl::ArrayVecImpl::truncate (738 samples, 1.89%)a..core::ptr::drop_in_place<[alloc::vec::Vec<u8>]> (736 samples, 1.88%)c..core::ptr::drop_in_place<alloc::vec::Vec<u8>> (736 samples, 1.88%)c..core::ptr::drop_in_place<alloc::raw_vec::RawVec<u8>> (736 samples, 1.88%)c..<alloc::raw_vec::RawVec<T,A> as core::ops::drop::Drop>::drop (736 samples, 1.88%)<..alloc::raw_vec::RawVec<T,A>::current_memory (4 samples, 0.01%)std::sync::poison::Flag::done (9 samples, 0.02%)std::thread::panicking (9 samples, 0.02%)std::panicking::panicking (9 samples, 0.02%)std::panicking::panic_count::count_is_zero (9 samples, 0.02%)core::sync::atomic::AtomicUsize::load (9 samples, 0.02%)core::sync::atomic::atomic_load (9 samples, 0.02%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (76 samples, 0.19%)std::sys::unix::locks::futex_mutex::Mutex::unlock (67 samples, 0.17%)core::sync::atomic::AtomicU32::swap (65 samples, 0.17%)core::sync::atomic::atomic_swap (65 samples, 0.17%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::fragged::Fragged<alloc::vec::Vec<u8>,48_usize>>> (77 samples, 0.20%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<std::collections::hash::map::HashMap<core::num::nonzero::NonZeroU32,alloc::sync::Weak<zssp::zeta::Session<benchmark::TestApplication>>>>> (77 samples, 0.20%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (77 samples, 0.20%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (77 samples, 0.20%)core::sync::atomic::AtomicU32::fetch_sub (70 samples, 0.18%)core::sync::atomic::atomic_sub (70 samples, 0.18%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<zssp::zeta::MutableState<benchmark::TestApplication>>> (51 samples, 0.13%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (51 samples, 0.13%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (51 samples, 0.13%)core::sync::atomic::AtomicU32::fetch_sub (48 samples, 0.12%)core::sync::atomic::atomic_sub (48 samples, 0.12%)<core::hash::sip::Sip13Rounds as core::hash::sip::Sip>::c_rounds (7 samples, 0.02%)core::num::<impl u64>::wrapping_add (6 samples, 0.02%)core::num::<impl u64>::rotate_left (14 samples, 0.04%)<std::collections::hash::map::DefaultHasher as core::hash::Hasher>::finish (51 samples, 0.13%)<core::hash::sip::SipHasher13 as core::hash::Hasher>::finish (51 samples, 0.13%)<core::hash::sip::Hasher<S> as core::hash::Hasher>::finish (51 samples, 0.13%)<core::hash::sip::Sip13Rounds as core::hash::sip::Sip>::d_rounds (34 samples, 0.09%)core::num::<impl u64>::wrapping_add (13 samples, 0.03%)hashbrown::map::make_hash (70 samples, 0.18%)core::hash::BuildHasher::hash_one (70 samples, 0.18%)core::hash::impls::<impl core::hash::Hash for &T>::hash (14 samples, 0.04%)<core::num::nonzero::NonZeroU32 as core::hash::Hash>::hash (14 samples, 0.04%)core::hash::impls::<impl core::hash::Hash for u32>::hash (14 samples, 0.04%)core::hash::Hasher::write_u32 (14 samples, 0.04%)<std::collections::hash::map::DefaultHasher as core::hash::Hasher>::write (14 samples, 0.04%)<core::hash::sip::SipHasher13 as core::hash::Hasher>::write (14 samples, 0.04%)<core::hash::sip::Hasher<S> as core::hash::Hasher>::write (14 samples, 0.04%)core::hash::sip::u8to64_le (11 samples, 0.03%)<hashbrown::raw::bitmask::BitMaskIter as core::iter::traits::iterator::Iterator>::next (4 samples, 0.01%)hashbrown::raw::RawTable<T,A>::find::_{{closure}} (8 samples, 0.02%)hashbrown::map::equivalent_key::_{{closure}} (8 samples, 0.02%)<core::num::nonzero::NonZeroU32 as core::cmp::PartialEq>::eq (8 samples, 0.02%)hashbrown::raw::h2 (9 samples, 0.02%)hashbrown::map::HashMap<K,V,S,A>::get_inner (109 samples, 0.28%)hashbrown::raw::RawTable<T,A>::get (39 samples, 0.10%)hashbrown::raw::RawTable<T,A>::find (39 samples, 0.10%)hashbrown::raw::RawTableInner<A>::find_inner (39 samples, 0.10%)hashbrown::raw::sse2::Group::load (13 samples, 0.03%)core::core_arch::x86::sse2::_mm_loadu_si128 (13 samples, 0.03%)core::intrinsics::copy_nonoverlapping (13 samples, 0.03%)std::collections::hash::map::HashMap<K,V,S>::get (113 samples, 0.29%)hashbrown::map::HashMap<K,V,S,A>::get (113 samples, 0.29%)zssp::zssp::Context<Crypto>::receive (4 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (6 samples, 0.02%)std::sync::mutex::MutexGuard<T>::new (14 samples, 0.04%)std::sync::poison::Flag::guard (8 samples, 0.02%)std::sync::mutex::Mutex<T>::lock (47 samples, 0.12%)std::sys::unix::locks::futex_mutex::Mutex::lock (33 samples, 0.08%)core::sync::atomic::AtomicU32::compare_exchange (32 samples, 0.08%)core::sync::atomic::atomic_compare_exchange (32 samples, 0.08%)core::sync::atomic::AtomicU32::compare_exchange_weak (117 samples, 0.30%)core::sync::atomic::atomic_compare_exchange_weak (117 samples, 0.30%)std::sync::rwlock::RwLock<T>::read (140 samples, 0.36%)std::sys::unix::locks::futex_rwlock::RwLock::read (140 samples, 0.36%)std::sys::unix::locks::futex_rwlock::is_read_lockable (6 samples, 0.02%)zssp::antireplay::Window<_,_>::check (7 samples, 0.02%)core::sync::atomic::AtomicU64::load (6 samples, 0.02%)core::sync::atomic::atomic_load (6 samples, 0.02%)arrayvec::arrayvec::ArrayVec<T,_>::push (16 samples, 0.04%)arrayvec::arrayvec_impl::ArrayVecImpl::push (16 samples, 0.04%)arrayvec::arrayvec_impl::ArrayVecImpl::try_push (16 samples, 0.04%)arrayvec::arrayvec_impl::ArrayVecImpl::push_unchecked (14 samples, 0.04%)core::ptr::write (12 samples, 0.03%)core::mem::maybe_uninit::MaybeUninit<T>::write (4 samples, 0.01%)zssp::fragged::Fragged<Fragment,_>::assemble (51 samples, 0.13%)<T as core::convert::TryInto<U>>::try_into (28 samples, 0.07%)core::array::_<impl core::convert::TryFrom<&[T]> for [T: N]>::try_from (28 samples, 0.07%)core::result::Result<T,E>::map (28 samples, 0.07%)zssp::zeta::from_nonce (33 samples, 0.08%)<core::slice::iter::IterMut<T> as core::iter::traits::iterator::Iterator>::next (6 samples, 0.02%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (76 samples, 0.19%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (76 samples, 0.19%)core::sync::atomic::AtomicU32::fetch_sub (75 samples, 0.19%)core::sync::atomic::atomic_sub (75 samples, 0.19%)CRYPTO_gcm128_decrypt (231 samples, 0.59%)[libcrypto.so.3] (86 samples, 0.22%)CRYPTO_gcm128_decrypt_ctr32 (440 samples, 1.13%)[libcrypto.so.3] (352 samples, 0.90%)[libcrypto.so.3] (27 samples, 0.07%)CRYPTO_gcm128_setiv (23 samples, 0.06%)[libcrypto.so.3] (20 samples, 0.05%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::decrypt_in_place (4,270 samples, 10.93%)<zssp::crypto_im..zssp::crypto_impl::openssl::CipherCtx::update (4,270 samples, 10.93%)zssp::crypto_imp..EVP_DecryptUpdate (4,266 samples, 10.92%)EVP_DecryptUpdate[libcrypto.so.3] (4,171 samples, 10.67%)[libcrypto.so.3][libcrypto.so.3] (4,167 samples, 10.66%)[libcrypto.so.3][libcrypto.so.3] (4,144 samples, 10.60%)[libcrypto.so.3][libcrypto.so.3] (3,418 samples, 8.75%)[libcrypto.s..[libcrypto.so.3] (3,257 samples, 8.33%)[libcrypto.s..asm_sysvec_reschedule_ipi (17 samples, 0.04%)sysvec_reschedule_ipi (16 samples, 0.04%)irqentry_exit (16 samples, 0.04%)irqentry_exit_to_user_mode (16 samples, 0.04%)exit_to_user_mode_prepare (16 samples, 0.04%)exit_to_user_mode_loop (16 samples, 0.04%)schedule (15 samples, 0.04%)__schedule (15 samples, 0.04%)finish_task_switch.isra.0 (15 samples, 0.04%)__perf_event_task_sched_in (15 samples, 0.04%)perf_ctx_enable (15 samples, 0.04%)CRYPTO_clear_free (92 samples, 0.24%)OPENSSL_cleanse (92 samples, 0.24%)EVP_CIPHER_free (27 samples, 0.07%)cfree (42 samples, 0.11%)[libc.so.6] (7 samples, 0.02%)EVP_CIPHER_CTX_free (184 samples, 0.47%)EVP_CIPHER_CTX_reset (180 samples, 0.46%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::AesGcmOpenSSLDec> (210 samples, 0.54%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::CipherCtx> (210 samples, 0.54%)<zssp::crypto_impl::openssl::CipherCtx as core::ops::drop::Drop>::drop (210 samples, 0.54%)cfree (25 samples, 0.06%)[libc.so.6] (15 samples, 0.04%)CRYPTO_gcm128_finish (29 samples, 0.07%)[libcrypto.so.3] (18 samples, 0.05%)[libcrypto.so.3] (33 samples, 0.08%)zssp::crypto_impl::openssl::CipherCtx::finalize (46 samples, 0.12%)EVP_DecryptFinal_ex (45 samples, 0.12%)[libcrypto.so.3] (38 samples, 0.10%)[libcrypto.so.3] (37 samples, 0.09%)OSSL_PARAM_get_octet_string (6 samples, 0.02%)[libc.so.6] (17 samples, 0.04%)OSSL_PARAM_locate (26 samples, 0.07%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::finish (333 samples, 0.85%)zssp::crypto_impl::openssl::CipherCtx::set_tag (77 samples, 0.20%)EVP_CIPHER_CTX_ctrl (77 samples, 0.20%)[libcrypto.so.3] (39 samples, 0.10%)OSSL_PARAM_locate_const (4 samples, 0.01%)[libc.so.6] (15 samples, 0.04%)OSSL_PARAM_locate (28 samples, 0.07%)EVP_CIPHER_CTX_set_padding (64 samples, 0.16%)[libcrypto.so.3] (42 samples, 0.11%)[libc.so.6] (25 samples, 0.06%)OSSL_PARAM_locate (48 samples, 0.12%)strcmp@plt (7 samples, 0.02%)EVP_CIPHER_CTX_get_iv_length (81 samples, 0.21%)[libcrypto.so.3] (63 samples, 0.16%)[libc.so.6] (20 samples, 0.05%)OSSL_PARAM_locate (41 samples, 0.10%)EVP_CIPHER_CTX_get_key_length (60 samples, 0.15%)[libcrypto.so.3] (53 samples, 0.14%)pthread_rwlock_rdlock (159 samples, 0.41%)CRYPTO_THREAD_read_lock (163 samples, 0.42%)pthread_rwlock_unlock (79 samples, 0.20%)CRYPTO_THREAD_unlock (86 samples, 0.22%)pthread_rwlock_unlock@plt (5 samples, 0.01%)EVP_CIPHER_up_ref (42 samples, 0.11%)OPENSSL_LH_retrieve (70 samples, 0.18%)[libcrypto.so.3] (57 samples, 0.15%)[libcrypto.so.3] (6 samples, 0.02%)pthread_rwlock_rdlock (43 samples, 0.11%)CRYPTO_THREAD_read_lock (47 samples, 0.12%)pthread_rwlock_unlock (66 samples, 0.17%)CRYPTO_THREAD_unlock (69 samples, 0.18%)CRYPTO_strndup (13 samples, 0.03%)OPENSSL_strcasecmp (38 samples, 0.10%)OPENSSL_LH_retrieve (162 samples, 0.41%)[libcrypto.so.3] (151 samples, 0.39%)[libcrypto.so.3] (36 samples, 0.09%)cfree (12 samples, 0.03%)[libc.so.6] (4 samples, 0.01%)[libcrypto.so.3] (310 samples, 0.79%)pthread_getspecific (7 samples, 0.02%)EVP_CIPHER_fetch (702 samples, 1.80%)E..[libcrypto.so.3] (699 samples, 1.79%)[..[libcrypto.so.3] (690 samples, 1.77%)EVP_CIPHER_free (18 samples, 0.05%)EVP_CIPHER_up_ref (27 samples, 0.07%)[libc.so.6] (10 samples, 0.03%)malloc (9 samples, 0.02%)CRYPTO_zalloc (22 samples, 0.06%)OPENSSL_init_crypto (4 samples, 0.01%)CRYPTO_gcm128_init (155 samples, 0.40%)[libcrypto.so.3] (126 samples, 0.32%)[libcrypto.so.3] (188 samples, 0.48%)[libcrypto.so.3] (32 samples, 0.08%)zssp::crypto_impl::openssl::CipherCtx::cipher_init (1,141 samples, 2.92%)zs..EVP_CipherInit_ex (1,141 samples, 2.92%)EV..[libcrypto.so.3] (1,141 samples, 2.92%)[l..[libcrypto.so.3] (224 samples, 0.57%)malloc (12 samples, 0.03%)CRYPTO_zalloc (17 samples, 0.04%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_dec (1,226 samples, 3.14%)<zs..zssp::crypto_impl::openssl::CipherCtx::new (20 samples, 0.05%)core::slice::index::<impl core::ops::index::Index<I> for [T]>::index (6 samples, 0.02%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::index (6 samples, 0.02%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::get_unchecked (5 samples, 0.01%)<core::ops::range::Range<usize> as core::slice::index::SliceIndex<[T]>>::get_unchecked (5 samples, 0.01%)core::ptr::const_ptr::<impl *const T>::add (4 samples, 0.01%)core::ptr::const_ptr::<impl *const T>::offset (4 samples, 0.01%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::index_mut (4 samples, 0.01%)core::slice::index::<impl core::ops::index::IndexMut<I> for [T]>::index_mut (5 samples, 0.01%)std::io::impls::<impl std::io::Write for &mut W>::write (232 samples, 0.59%)<arrayvec::arrayvec::ArrayVec<u8,_> as std::io::Write>::write (232 samples, 0.59%)arrayvec::arrayvec::ArrayVec<T,_>::try_extend_from_slice (230 samples, 0.59%)core::intrinsics::copy_nonoverlapping (228 samples, 0.58%)[libc.so.6] (227 samples, 0.58%)zssp::antireplay::Window<_,_>::update (27 samples, 0.07%)core::sync::atomic::AtomicU64::fetch_max (26 samples, 0.07%)core::sync::atomic::atomic_umax (26 samples, 0.07%)zssp::zeta::receive_payload_in_place (6,190 samples, 15.84%)zssp::zeta::receive_payl..zssp::zssp::Context<Crypto>::receive (8,570 samples, 21.93%)zssp::zssp::Context<Crypto>::receivezssp::zssp::parse_fragment_header (65 samples, 0.17%)core::slice::<impl [T]>::copy_from_slice (4 samples, 0.01%)core::intrinsics::copy_nonoverlapping (4 samples, 0.01%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (56 samples, 0.14%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (56 samples, 0.14%)std::sys::unix::locks::futex_mutex::Mutex::unlock (55 samples, 0.14%)core::sync::atomic::AtomicU32::swap (52 samples, 0.13%)core::sync::atomic::atomic_swap (52 samples, 0.13%)std::sync::mutex::Mutex<T>::lock (64 samples, 0.16%)std::sys::unix::locks::futex_mutex::Mutex::lock (61 samples, 0.16%)core::sync::atomic::AtomicU32::compare_exchange (55 samples, 0.14%)core::sync::atomic::atomic_compare_exchange (55 samples, 0.14%)<zssp::crypto_impl::openssl::Aes256OpenSSLEnc as zssp::crypto::aes::Aes256Enc>::encrypt_in_place (314 samples, 0.80%)zssp::crypto_impl::openssl::CipherCtx::update (188 samples, 0.48%)EVP_EncryptUpdate (183 samples, 0.47%)[libcrypto.so.3] (148 samples, 0.38%)[libcrypto.so.3] (106 samples, 0.27%)[libcrypto.so.3] (96 samples, 0.25%)CRYPTO_gcm128_encrypt (250 samples, 0.64%)[libcrypto.so.3] (143 samples, 0.37%)CRYPTO_gcm128_encrypt_ctr32 (415 samples, 1.06%)[libcrypto.so.3] (319 samples, 0.82%)[libcrypto.so.3] (28 samples, 0.07%)[libcrypto.so.3] (19 samples, 0.05%)CRYPTO_gcm128_setiv (25 samples, 0.06%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)perf_ctx_enable (18 samples, 0.05%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::encrypt (3,317 samples, 8.49%)<zssp::crypt..zssp::crypto_impl::openssl::CipherCtx::update (3,311 samples, 8.47%)zssp::crypto..EVP_EncryptUpdate (3,309 samples, 8.47%)EVP_EncryptU..[libcrypto.so.3] (3,257 samples, 8.33%)[libcrypto.s..[libcrypto.so.3] (3,255 samples, 8.33%)[libcrypto.s..[libcrypto.so.3] (3,235 samples, 8.28%)[libcrypto.s..[libcrypto.so.3] (2,506 samples, 6.41%)[libcryp..[libcrypto.so.3] (2,299 samples, 5.88%)[libcry..asm_sysvec_reschedule_ipi (20 samples, 0.05%)sysvec_reschedule_ipi (20 samples, 0.05%)irqentry_exit (20 samples, 0.05%)irqentry_exit_to_user_mode (20 samples, 0.05%)exit_to_user_mode_prepare (20 samples, 0.05%)exit_to_user_mode_loop (20 samples, 0.05%)schedule (19 samples, 0.05%)__schedule (19 samples, 0.05%)finish_task_switch.isra.0 (19 samples, 0.05%)__perf_event_task_sched_in (19 samples, 0.05%)CRYPTO_clear_free (107 samples, 0.27%)OPENSSL_cleanse (103 samples, 0.26%)EVP_CIPHER_free (30 samples, 0.08%)EVP_CIPHER_CTX_free (206 samples, 0.53%)EVP_CIPHER_CTX_reset (205 samples, 0.52%)cfree (52 samples, 0.13%)[libc.so.6] (11 samples, 0.03%)cfree (11 samples, 0.03%)[libc.so.6] (5 samples, 0.01%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc> (220 samples, 0.56%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::CipherCtx> (220 samples, 0.56%)<zssp::crypto_impl::openssl::CipherCtx as core::ops::drop::Drop>::drop (220 samples, 0.56%)[libcrypto.so.3] (15 samples, 0.04%)zssp::crypto_impl::openssl::CipherCtx::finalize (47 samples, 0.12%)EVP_EncryptFinal_ex (46 samples, 0.12%)[libcrypto.so.3] (33 samples, 0.08%)[libcrypto.so.3] (33 samples, 0.08%)[libcrypto.so.3] (29 samples, 0.07%)CRYPTO_gcm128_tag (29 samples, 0.07%)CRYPTO_gcm128_finish (22 samples, 0.06%)[libc.so.6] (32 samples, 0.08%)OSSL_PARAM_locate (53 samples, 0.14%)strcmp@plt (5 samples, 0.01%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::finish (358 samples, 0.92%)zssp::crypto_impl::openssl::CipherCtx::get_tag (91 samples, 0.23%)EVP_CIPHER_CTX_ctrl (91 samples, 0.23%)[libcrypto.so.3] (68 samples, 0.17%)OSSL_PARAM_set_octet_string (5 samples, 0.01%)[libc.so.6] (12 samples, 0.03%)EVP_CIPHER_CTX_set_padding (64 samples, 0.16%)[libcrypto.so.3] (38 samples, 0.10%)OSSL_PARAM_locate (32 samples, 0.08%)strcmp@plt (4 samples, 0.01%)[libc.so.6] (17 samples, 0.04%)OSSL_PARAM_locate (40 samples, 0.10%)strcmp@plt (5 samples, 0.01%)EVP_CIPHER_CTX_get_iv_length (79 samples, 0.20%)[libcrypto.so.3] (55 samples, 0.14%)[libc.so.6] (20 samples, 0.05%)OSSL_PARAM_locate (37 samples, 0.09%)EVP_CIPHER_CTX_get_key_length (63 samples, 0.16%)[libcrypto.so.3] (54 samples, 0.14%)pthread_rwlock_rdlock (130 samples, 0.33%)CRYPTO_THREAD_read_lock (136 samples, 0.35%)pthread_rwlock_unlock (73 samples, 0.19%)CRYPTO_THREAD_unlock (82 samples, 0.21%)pthread_rwlock_unlock@plt (5 samples, 0.01%)EVP_CIPHER_up_ref (24 samples, 0.06%)OPENSSL_LH_retrieve (75 samples, 0.19%)[libcrypto.so.3] (56 samples, 0.14%)[libcrypto.so.3] (5 samples, 0.01%)pthread_rwlock_rdlock (38 samples, 0.10%)CRYPTO_THREAD_read_lock (40 samples, 0.10%)pthread_rwlock_unlock (43 samples, 0.11%)CRYPTO_THREAD_unlock (45 samples, 0.12%)OPENSSL_strnlen (5 samples, 0.01%)CRYPTO_strndup (10 samples, 0.03%)malloc (5 samples, 0.01%)OPENSSL_strcasecmp (41 samples, 0.10%)OPENSSL_LH_retrieve (132 samples, 0.34%)[libcrypto.so.3] (118 samples, 0.30%)[libcrypto.so.3] (29 samples, 0.07%)cfree (19 samples, 0.05%)[libc.so.6] (7 samples, 0.02%)[libcrypto.so.3] (264 samples, 0.68%)EVP_CIPHER_fetch (603 samples, 1.54%)[libcrypto.so.3] (600 samples, 1.54%)[libcrypto.so.3] (594 samples, 1.52%)EVP_CIPHER_free (17 samples, 0.04%)EVP_CIPHER_up_ref (20 samples, 0.05%)OBJ_nid2sn (6 samples, 0.02%)malloc (8 samples, 0.02%)CRYPTO_zalloc (14 samples, 0.04%)OPENSSL_init_crypto (5 samples, 0.01%)CRYPTO_gcm128_init (161 samples, 0.41%)[libcrypto.so.3] (132 samples, 0.34%)[libcrypto.so.3] (201 samples, 0.51%)[libcrypto.so.3] (39 samples, 0.10%)zssp::crypto_impl::openssl::CipherCtx::cipher_init (1,042 samples, 2.67%)zs..EVP_CipherInit_ex (1,042 samples, 2.67%)EV..[libcrypto.so.3] (1,042 samples, 2.67%)[l..[libcrypto.so.3] (232 samples, 0.59%)[libc.so.6] (4 samples, 0.01%)malloc (9 samples, 0.02%)CRYPTO_zalloc (15 samples, 0.04%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_enc (1,125 samples, 2.88%)<z..zssp::crypto_impl::openssl::CipherCtx::new (16 samples, 0.04%)__rdl_alloc (9 samples, 0.02%)std::sys::unix::alloc::<impl core::alloc::global::GlobalAlloc for std::alloc::System>::alloc (9 samples, 0.02%)[libc.so.6] (54 samples, 0.14%)[libc.so.6] (925 samples, 2.37%)[l..__entry_text_start (4 samples, 0.01%)__get_user_nocheck_4 (5 samples, 0.01%)futex_q_lock (5 samples, 0.01%)futex_q_unlock (6 samples, 0.02%)futex_wait (24 samples, 0.06%)futex_wait_setup (19 samples, 0.05%)__x64_sys_futex (26 samples, 0.07%)do_futex (26 samples, 0.07%)do_syscall_64 (31 samples, 0.08%)syscall_exit_to_user_mode (4 samples, 0.01%)__lll_lock_wait_private (53 samples, 0.14%)entry_SYSCALL_64_after_hwframe (33 samples, 0.08%)__entry_text_start (4 samples, 0.01%)_raw_spin_lock (4 samples, 0.01%)futex_hash (4 samples, 0.01%)_raw_spin_lock (4 samples, 0.01%)native_queued_spin_lock_slowpath (4 samples, 0.01%)futex_wake_mark (13 samples, 0.03%)preempt_schedule_thunk (5 samples, 0.01%)preempt_schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_pmu_nop_void (5 samples, 0.01%)__smp_call_single_queue (12 samples, 0.03%)native_send_call_func_single_ipi (9 samples, 0.02%)x2apic_send_IPI (9 samples, 0.02%)native_write_msr (6 samples, 0.02%)llist_add_batch (17 samples, 0.04%)do_futex (136 samples, 0.35%)futex_wake (128 samples, 0.33%)wake_up_q (75 samples, 0.19%)try_to_wake_up (66 samples, 0.17%)ttwu_queue_wakelist (36 samples, 0.09%)__x64_sys_futex (137 samples, 0.35%)do_syscall_64 (141 samples, 0.36%)entry_SYSCALL_64_after_hwframe (144 samples, 0.37%)alloc::vec::Vec<T,A>::with_capacity_in (1,803 samples, 4.61%)alloc..alloc::raw_vec::RawVec<T,A>::with_capacity_in (1,803 samples, 4.61%)alloc..alloc::raw_vec::RawVec<T,A>::allocate_in (1,803 samples, 4.61%)alloc..<alloc::alloc::Global as core::alloc::Allocator>::allocate (1,801 samples, 4.61%)<allo..alloc::alloc::Global::alloc_impl (1,801 samples, 4.61%)alloc..alloc::alloc::alloc (1,801 samples, 4.61%)alloc..malloc (1,789 samples, 4.58%)malloc__lll_lock_wake_private (156 samples, 0.40%)alloc::slice::<impl [T]>::to_vec (2,000 samples, 5.12%)alloc:..alloc::slice::<impl [T]>::to_vec_in (2,000 samples, 5.12%)alloc:..alloc::slice::hack::to_vec (2,000 samples, 5.12%)alloc:..<T as alloc::slice::hack::ConvertVec>::to_vec (2,000 samples, 5.12%)<T as ..core::ptr::const_ptr::<impl *const T>::copy_to_nonoverlapping (197 samples, 0.50%)core::intrinsics::copy_nonoverlapping (197 samples, 0.50%)[libc.so.6] (194 samples, 0.50%)core::result::Result<T,E>::is_ok (4 samples, 0.01%)<std::sync::mpmc::select::Token as core::default::Default>::default (4 samples, 0.01%)std::sync::mpmc::array::Channel<T>::send (4 samples, 0.01%)core::sync::atomic::AtomicUsize::compare_exchange_weak (66 samples, 0.17%)core::sync::atomic::atomic_compare_exchange_weak (66 samples, 0.17%)std::sync::mpmc::array::Channel<T>::start_send (102 samples, 0.26%)core::sync::atomic::AtomicUsize::load (11 samples, 0.03%)core::sync::atomic::atomic_load (11 samples, 0.03%)core::sync::atomic::AtomicBool::load (6 samples, 0.02%)core::sync::atomic::atomic_load (5 samples, 0.01%)futex_wake_mark (8 samples, 0.02%)__smp_call_single_queue (7 samples, 0.02%)native_send_call_func_single_ipi (6 samples, 0.02%)x2apic_send_IPI (6 samples, 0.02%)native_write_msr (4 samples, 0.01%)llist_add_batch (7 samples, 0.02%)futex_wake (71 samples, 0.18%)wake_up_q (44 samples, 0.11%)try_to_wake_up (43 samples, 0.11%)ttwu_queue_wakelist (24 samples, 0.06%)__x64_sys_futex (75 samples, 0.19%)do_futex (75 samples, 0.19%)entry_SYSCALL_64_after_hwframe (76 samples, 0.19%)do_syscall_64 (76 samples, 0.19%)<core::slice::iter::Iter<T> as core::iter::traits::iterator::Iterator>::position (88 samples, 0.23%)std::sync::mpmc::waker::Waker::try_select::_{{closure}} (88 samples, 0.23%)std::sync::mpmc::context::Context::unpark (84 samples, 0.21%)std::thread::Thread::unpark (84 samples, 0.21%)std::sys_common::thread_parking::futex::Parker::unpark (84 samples, 0.21%)std::sys::unix::futex::futex_wake (81 samples, 0.21%)syscall (80 samples, 0.20%)std::sync::mpmc::waker::Waker::try_select (89 samples, 0.23%)std::sync::mpmc::array::Channel<T>::write (112 samples, 0.29%)std::sync::mpmc::waker::SyncWaker::notify (105 samples, 0.27%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_pmu_nop_void (5 samples, 0.01%)futex_wait_queue (6 samples, 0.02%)schedule (6 samples, 0.02%)__schedule (6 samples, 0.02%)std::sync::mpmc::context::Context::wait_until (7 samples, 0.02%)std::thread::park (7 samples, 0.02%)std::sys_common::thread_parking::futex::Parker::park (7 samples, 0.02%)std::sys::unix::futex::futex_wait (7 samples, 0.02%)syscall (7 samples, 0.02%)entry_SYSCALL_64_after_hwframe (7 samples, 0.02%)do_syscall_64 (7 samples, 0.02%)__x64_sys_futex (7 samples, 0.02%)do_futex (7 samples, 0.02%)futex_wait (7 samples, 0.02%)std::sync::mpmc::context::Context::with (8 samples, 0.02%)std::thread::local::LocalKey<T>::try_with (8 samples, 0.02%)std::sync::mpmc::context::Context::with::_{{closure}} (8 samples, 0.02%)std::sync::mpmc::context::Context::with::_{{closure}} (8 samples, 0.02%)std::sync::mpmc::array::Channel<T>::send::_{{closure}} (8 samples, 0.02%)core::hint::spin_loop (14 samples, 0.04%)core::core_arch::x86::sse2::_mm_pause (14 samples, 0.04%)std::sync::mpmc::array::Channel<T>::send (251 samples, 0.64%)std::sync::mpmc::utils::Backoff::spin_light (15 samples, 0.04%)benchmark::bob_main::_{{closure}} (3,468 samples, 8.87%)benchmark::bo..std::sync::mpsc::SyncSender<T>::send (1,464 samples, 3.75%)std:..std::sync::mpmc::Sender<T>::send (1,462 samples, 3.74%)std:..core::iter::range::<impl core::iter::traits::iterator::Iterator for core::ops::range::Range<A>>::next (6 samples, 0.02%)<core::ops::range::Range<T> as core::iter::range::RangeIteratorImpl>::spec_next (6 samples, 0.02%)core::mem::drop (25 samples, 0.06%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<zssp::zeta::MutableState<benchmark::TestApplication>>> (25 samples, 0.06%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (25 samples, 0.06%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (25 samples, 0.06%)core::sync::atomic::AtomicU32::fetch_sub (24 samples, 0.06%)core::sync::atomic::atomic_sub (24 samples, 0.06%)core::slice::<impl [T]>::copy_from_slice (14 samples, 0.04%)core::intrinsics::copy_nonoverlapping (14 samples, 0.04%)core::sync::atomic::AtomicU32::compare_exchange_weak (20 samples, 0.05%)core::sync::atomic::atomic_compare_exchange_weak (20 samples, 0.05%)std::sync::rwlock::RwLock<T>::read (22 samples, 0.06%)std::sys::unix::locks::futex_rwlock::RwLock::read (22 samples, 0.06%)benchmark::alice_main (18,175 samples, 46.50%)benchmark::alice_mainzssp::zssp::Context<Crypto>::send (8,691 samples, 22.24%)zssp::zssp::Context<Crypto>::sendzssp::zeta::send_payload (8,691 samples, 22.24%)zssp::zeta::send_payloadzssp::zeta::get_counter (14 samples, 0.04%)core::sync::atomic::AtomicU64::fetch_add (11 samples, 0.03%)core::sync::atomic::atomic_add (11 samples, 0.03%)[libc.so.6] (15 samples, 0.04%)arrayvec::arrayvec::ArrayVec<T,_>::clear (4 samples, 0.01%)arrayvec::arrayvec_impl::ArrayVecImpl::clear (4 samples, 0.01%)arrayvec::arrayvec_impl::ArrayVecImpl::truncate (4 samples, 0.01%)core::ptr::drop_in_place<alloc::sync::Arc<zssp::zeta::Session<benchmark::TestApplication>>> (10 samples, 0.03%)<alloc::sync::Arc<T> as core::ops::drop::Drop>::drop (10 samples, 0.03%)core::sync::atomic::AtomicUsize::fetch_sub (10 samples, 0.03%)core::sync::atomic::atomic_sub (10 samples, 0.03%)core::time::Duration::as_millis (5 samples, 0.01%)_ZN3std4sync4mpmc5array16Channel$LT$T$GT$10start_recv17h7800ca29c64cb868E.llvm.12455019271255371362 (4 samples, 0.01%)std::sync::mpmc::array::Channel<T>::read (6 samples, 0.02%)core::sync::atomic::AtomicUsize::compare_exchange_weak (65 samples, 0.17%)core::sync::atomic::atomic_compare_exchange_weak (65 samples, 0.17%)core::sync::atomic::AtomicUsize::load (84 samples, 0.21%)core::sync::atomic::atomic_load (84 samples, 0.21%)std::sync::mpmc::array::Channel<T>::start_recv (190 samples, 0.49%)core::sync::atomic::fence (4 samples, 0.01%)core::sync::atomic::AtomicU32::swap (5 samples, 0.01%)core::sync::atomic::atomic_swap (5 samples, 0.01%)[[vdso]] (4 samples, 0.01%)core::option::Option<T>::and_then (5 samples, 0.01%)std::sys::unix::futex::futex_wait::_{{closure}} (5 samples, 0.01%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (5 samples, 0.01%)clock_gettime (5 samples, 0.01%)futex_setup_timer (5 samples, 0.01%)hrtimer_init_sleeper (4 samples, 0.01%)__hrtimer_init (4 samples, 0.01%)futex_unqueue (4 samples, 0.01%)hrtimer_sleeper_start_expires (6 samples, 0.02%)hrtimer_start_range_ns (6 samples, 0.02%)update_curr (5 samples, 0.01%)dequeue_entity (14 samples, 0.04%)update_load_avg (5 samples, 0.01%)dequeue_task (18 samples, 0.05%)dequeue_task_fair (18 samples, 0.05%)finish_task_switch.isra.0 (14 samples, 0.04%)raw_spin_rq_unlock (6 samples, 0.02%)pick_next_task (5 samples, 0.01%)prepare_task_switch (6 samples, 0.02%)__perf_event_task_sched_out (4 samples, 0.01%)psi_group_change (12 samples, 0.03%)record_times (5 samples, 0.01%)psi_task_switch (22 samples, 0.06%)__schedule (72 samples, 0.18%)futex_wait_queue (90 samples, 0.23%)schedule (81 samples, 0.21%)__get_user_nocheck_4 (6 samples, 0.02%)futex_q_lock (7 samples, 0.02%)futex_wait_setup (19 samples, 0.05%)hrtimer_cancel (5 samples, 0.01%)hrtimer_try_to_cancel (5 samples, 0.01%)do_futex (131 samples, 0.34%)futex_wait (130 samples, 0.33%)__x64_sys_futex (143 samples, 0.37%)get_timespec64 (5 samples, 0.01%)exit_to_user_mode_loop (11 samples, 0.03%)__rseq_handle_notify_resume (4 samples, 0.01%)std::sync::mpmc::context::Context::wait_until (178 samples, 0.46%)std::thread::park_timeout (177 samples, 0.45%)std::sys_common::thread_parking::futex::Parker::park_timeout (175 samples, 0.45%)std::sys::unix::futex::futex_wait (170 samples, 0.43%)syscall (164 samples, 0.42%)entry_SYSCALL_64_after_hwframe (159 samples, 0.41%)do_syscall_64 (159 samples, 0.41%)syscall_exit_to_user_mode (15 samples, 0.04%)exit_to_user_mode_prepare (15 samples, 0.04%)core::sync::atomic::AtomicBool::store (8 samples, 0.02%)core::sync::atomic::atomic_store (8 samples, 0.02%)std::sync::mpmc::waker::Waker::register (5 samples, 0.01%)std::sync::mpmc::waker::Waker::register_with_packet (5 samples, 0.01%)alloc::vec::Vec<T,A>::push (5 samples, 0.01%)core::ptr::write (5 samples, 0.01%)std::sync::mpmc::waker::SyncWaker::register (24 samples, 0.06%)std::sync::mutex::Mutex<T>::lock (8 samples, 0.02%)std::sys::unix::locks::futex_mutex::Mutex::lock (8 samples, 0.02%)core::sync::atomic::AtomicU32::compare_exchange (8 samples, 0.02%)core::sync::atomic::atomic_compare_exchange (8 samples, 0.02%)std::sync::mpmc::context::Context::with (207 samples, 0.53%)std::thread::local::LocalKey<T>::try_with (207 samples, 0.53%)std::sync::mpmc::context::Context::with::_{{closure}} (207 samples, 0.53%)std::sync::mpmc::context::Context::with::_{{closure}} (207 samples, 0.53%)std::sync::mpmc::array::Channel<T>::recv::_{{closure}} (207 samples, 0.53%)std::sync::mpmc::Receiver<T>::recv_deadline (431 samples, 1.10%)std::sync::mpmc::array::Channel<T>::recv (431 samples, 1.10%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (5 samples, 0.01%)clock_gettime (5 samples, 0.01%)[[vdso]] (5 samples, 0.01%)std::sync::mpmc::Receiver<T>::recv_timeout (4 samples, 0.01%)[[vdso]] (131 samples, 0.34%)[[vdso]] (88 samples, 0.23%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (146 samples, 0.37%)clock_gettime (140 samples, 0.36%)__vdso_clock_gettime (6 samples, 0.02%)std::sync::mpsc::Receiver<T>::recv_timeout (605 samples, 1.55%)std::sync::mpmc::Receiver<T>::recv_timeout (602 samples, 1.54%)std::time::SystemTime::checked_add (12 samples, 0.03%)std::sys::unix::time::SystemTime::checked_add_duration (12 samples, 0.03%)std::sys::unix::time::Timespec::checked_add_duration (12 samples, 0.03%)core::option::Option<T>::and_then (4 samples, 0.01%)core::cmp::impls::<impl core::cmp::PartialOrd<&B> for &A>::ge (6 samples, 0.02%)core::cmp::PartialOrd::ge (6 samples, 0.02%)<std::sys::unix::time::Timespec as core::cmp::PartialOrd>::partial_cmp (5 samples, 0.01%)std::time::Instant::duration_since (22 samples, 0.06%)std::time::Instant::checked_duration_since (21 samples, 0.05%)std::sys::unix::time::inner::Instant::checked_sub_instant (21 samples, 0.05%)std::sys::unix::time::Timespec::sub_timespec (21 samples, 0.05%)<std::time::Instant as core::ops::arith::Sub>::sub (27 samples, 0.07%)std::time::Instant::elapsed (5 samples, 0.01%)[[vdso]] (148 samples, 0.38%)[[vdso]] (107 samples, 0.27%)std::time::Instant::elapsed (192 samples, 0.49%)std::time::Instant::now (159 samples, 0.41%)std::sys::unix::time::inner::Instant::now (159 samples, 0.41%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (155 samples, 0.40%)clock_gettime (154 samples, 0.39%)__vdso_clock_gettime (4 samples, 0.01%)<T as core::convert::TryInto<U>>::try_into (306 samples, 0.78%)core::array::_<impl core::convert::TryFrom<&[T]> for [T: N]>::try_from (306 samples, 0.78%)core::result::Result<T,E>::map (306 samples, 0.78%)<core::result::Result<T,E> as core::ops::try_trait::Try>::branch (13 samples, 0.03%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (61 samples, 0.16%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (61 samples, 0.16%)std::sys::unix::locks::futex_mutex::Mutex::unlock (60 samples, 0.15%)core::sync::atomic::AtomicU32::swap (56 samples, 0.14%)core::sync::atomic::atomic_swap (56 samples, 0.14%)std::sync::mutex::MutexGuard<T>::new (6 samples, 0.02%)std::sync::poison::Flag::guard (6 samples, 0.02%)std::thread::panicking (6 samples, 0.02%)std::panicking::panicking (6 samples, 0.02%)std::panicking::panic_count::count_is_zero (6 samples, 0.02%)std::sync::mutex::Mutex<T>::lock (77 samples, 0.20%)std::sys::unix::locks::futex_mutex::Mutex::lock (71 samples, 0.18%)core::sync::atomic::AtomicU32::compare_exchange (69 samples, 0.18%)core::sync::atomic::atomic_compare_exchange (69 samples, 0.18%)EVP_CIPHER_CTX_get_block_size (5 samples, 0.01%)<zssp::crypto_impl::openssl::Aes256OpenSSLDec as zssp::crypto::aes::Aes256Dec>::decrypt_in_place (344 samples, 0.88%)zssp::crypto_impl::openssl::CipherCtx::update (200 samples, 0.51%)EVP_DecryptUpdate (196 samples, 0.50%)[libcrypto.so.3] (166 samples, 0.42%)[libcrypto.so.3] (119 samples, 0.30%)[libcrypto.so.3] (113 samples, 0.29%)__rust_probestack (9 samples, 0.02%)core::cmp::Ord::max (5 samples, 0.01%)zssp::zssp::Context<Crypto>::receive (5 samples, 0.01%)core::num::nonzero::NonZeroU32::new (6 samples, 0.02%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_pmu_nop_void (5 samples, 0.01%)alloc::sync::Weak<T>::upgrade::_{{closure}} (7 samples, 0.02%)core::sync::atomic::AtomicUsize::compare_exchange_weak (43 samples, 0.11%)core::sync::atomic::atomic_compare_exchange_weak (43 samples, 0.11%)alloc::sync::Weak<T>::upgrade (63 samples, 0.16%)core::sync::atomic::AtomicUsize::fetch_update (61 samples, 0.16%)core::sync::atomic::AtomicUsize::load (6 samples, 0.02%)core::sync::atomic::atomic_load (6 samples, 0.02%)core::option::Option<T>::map (71 samples, 0.18%)zssp::zssp::Context<Crypto>::receive::_{{closure}} (71 samples, 0.18%)zssp::zssp::Context<Crypto>::receive (8 samples, 0.02%)core::ptr::drop_in_place<alloc::sync::Arc<zssp::zeta::Session<benchmark::TestApplication>>> (55 samples, 0.14%)<alloc::sync::Arc<T> as core::ops::drop::Drop>::drop (55 samples, 0.14%)core::sync::atomic::AtomicUsize::fetch_sub (53 samples, 0.14%)core::sync::atomic::atomic_sub (53 samples, 0.14%)__rdl_dealloc (4 samples, 0.01%)std::sys::unix::alloc::<impl core::alloc::global::GlobalAlloc for std::alloc::System>::dealloc (4 samples, 0.01%)[libc.so.6] (73 samples, 0.19%)__entry_text_start (13 samples, 0.03%)futex_unqueue (8 samples, 0.02%)update_curr (16 samples, 0.04%)cpuacct_charge (8 samples, 0.02%)dequeue_entity (23 samples, 0.06%)update_load_avg (4 samples, 0.01%)dequeue_task_fair (28 samples, 0.07%)dequeue_task (29 samples, 0.07%)__perf_event_task_sched_in (8 samples, 0.02%)perf_ctx_enable (8 samples, 0.02%)x86_pmu_enable (4 samples, 0.01%)intel_pmu_enable_all (4 samples, 0.01%)native_write_msr (4 samples, 0.01%)finish_task_switch.isra.0 (19 samples, 0.05%)raw_spin_rq_unlock (5 samples, 0.01%)pick_next_task_fair (5 samples, 0.01%)pick_next_task (10 samples, 0.03%)put_prev_task_fair (4 samples, 0.01%)prepare_task_switch (16 samples, 0.04%)__perf_event_task_sched_out (5 samples, 0.01%)_raw_spin_lock (4 samples, 0.01%)psi_group_change (22 samples, 0.06%)psi_task_switch (35 samples, 0.09%)futex_wait_queue (143 samples, 0.37%)schedule (136 samples, 0.35%)__schedule (130 samples, 0.33%)__get_user_nocheck_4 (8 samples, 0.02%)_raw_spin_lock (6 samples, 0.02%)futex_q_lock (8 samples, 0.02%)futex_wait (192 samples, 0.49%)futex_wait_setup (30 samples, 0.08%)__x64_sys_futex (201 samples, 0.51%)do_futex (200 samples, 0.51%)__rseq_handle_notify_resume (7 samples, 0.02%)rseq_update_cpu_node_id (5 samples, 0.01%)exit_to_user_mode_loop (12 samples, 0.03%)exit_to_user_mode_prepare (19 samples, 0.05%)do_syscall_64 (227 samples, 0.58%)syscall_exit_to_user_mode (22 samples, 0.06%)entry_SYSCALL_64_after_hwframe (233 samples, 0.60%)__lll_lock_wait_private (283 samples, 0.72%)[libc.so.6] (759 samples, 1.94%)[..__entry_text_start (8 samples, 0.02%)futex_hash (7 samples, 0.02%)_raw_spin_lock (12 samples, 0.03%)native_queued_spin_lock_slowpath (12 samples, 0.03%)__x64_sys_futex (65 samples, 0.17%)do_futex (61 samples, 0.16%)futex_wake (51 samples, 0.13%)entry_SYSCALL_64_after_hwframe (78 samples, 0.20%)do_syscall_64 (77 samples, 0.20%)syscall_exit_to_user_mode (6 samples, 0.02%)exit_to_user_mode_prepare (6 samples, 0.02%)alloc::alloc::dealloc (903 samples, 2.31%)a..cfree (896 samples, 2.29%)c..__lll_lock_wake_private (96 samples, 0.25%)<alloc::alloc::Global as core::alloc::Allocator>::deallocate (906 samples, 2.32%)<..core::ptr::drop_in_place<arrayvec::arrayvec::ArrayVec<alloc::vec::Vec<u8>,48_usize>> (914 samples, 2.34%)c..<arrayvec::arrayvec::ArrayVec<T,_> as core::ops::drop::Drop>::drop (914 samples, 2.34%)<..arrayvec::arrayvec::ArrayVec<T,_>::clear (914 samples, 2.34%)a..arrayvec::arrayvec_impl::ArrayVecImpl::clear (914 samples, 2.34%)a..arrayvec::arrayvec_impl::ArrayVecImpl::truncate (914 samples, 2.34%)a..core::ptr::drop_in_place<[alloc::vec::Vec<u8>]> (913 samples, 2.34%)c..core::ptr::drop_in_place<alloc::vec::Vec<u8>> (911 samples, 2.33%)c..core::ptr::drop_in_place<alloc::raw_vec::RawVec<u8>> (911 samples, 2.33%)c..<alloc::raw_vec::RawVec<T,A> as core::ops::drop::Drop>::drop (911 samples, 2.33%)<..alloc::raw_vec::RawVec<T,A>::current_memory (5 samples, 0.01%)std::sync::poison::Flag::done (9 samples, 0.02%)std::thread::panicking (9 samples, 0.02%)std::panicking::panicking (9 samples, 0.02%)std::panicking::panic_count::count_is_zero (9 samples, 0.02%)core::sync::atomic::AtomicUsize::load (5 samples, 0.01%)core::sync::atomic::atomic_load (5 samples, 0.01%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::fragged::Fragged<alloc::vec::Vec<u8>,48_usize>>> (71 samples, 0.18%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (71 samples, 0.18%)std::sys::unix::locks::futex_mutex::Mutex::unlock (62 samples, 0.16%)core::sync::atomic::AtomicU32::swap (61 samples, 0.16%)core::sync::atomic::atomic_swap (61 samples, 0.16%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<std::collections::hash::map::HashMap<core::num::nonzero::NonZeroU32,alloc::sync::Weak<zssp::zeta::Session<benchmark::TestApplication>>>>> (58 samples, 0.15%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (58 samples, 0.15%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (58 samples, 0.15%)core::sync::atomic::AtomicU32::fetch_sub (53 samples, 0.14%)core::sync::atomic::atomic_sub (53 samples, 0.14%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<zssp::zeta::MutableState<benchmark::TestApplication>>> (48 samples, 0.12%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (48 samples, 0.12%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (48 samples, 0.12%)core::sync::atomic::AtomicU32::fetch_sub (44 samples, 0.11%)core::sync::atomic::atomic_sub (44 samples, 0.11%)core::num::<impl u64>::rotate_left (6 samples, 0.02%)<core::hash::sip::Sip13Rounds as core::hash::sip::Sip>::c_rounds (11 samples, 0.03%)core::num::<impl u64>::rotate_left (11 samples, 0.03%)<std::collections::hash::map::DefaultHasher as core::hash::Hasher>::finish (51 samples, 0.13%)<core::hash::sip::SipHasher13 as core::hash::Hasher>::finish (51 samples, 0.13%)<core::hash::sip::Hasher<S> as core::hash::Hasher>::finish (51 samples, 0.13%)<core::hash::sip::Sip13Rounds as core::hash::sip::Sip>::d_rounds (30 samples, 0.08%)core::num::<impl u64>::wrapping_add (12 samples, 0.03%)<std::collections::hash::map::RandomState as core::hash::BuildHasher>::build_hasher (5 samples, 0.01%)hashbrown::map::make_hash (76 samples, 0.19%)core::hash::BuildHasher::hash_one (76 samples, 0.19%)core::hash::impls::<impl core::hash::Hash for &T>::hash (17 samples, 0.04%)<core::num::nonzero::NonZeroU32 as core::hash::Hash>::hash (17 samples, 0.04%)core::hash::impls::<impl core::hash::Hash for u32>::hash (17 samples, 0.04%)core::hash::Hasher::write_u32 (17 samples, 0.04%)<std::collections::hash::map::DefaultHasher as core::hash::Hasher>::write (17 samples, 0.04%)<core::hash::sip::SipHasher13 as core::hash::Hasher>::write (17 samples, 0.04%)<core::hash::sip::Hasher<S> as core::hash::Hasher>::write (17 samples, 0.04%)core::hash::sip::u8to64_le (7 samples, 0.02%)<hashbrown::raw::bitmask::BitMaskIter as core::iter::traits::iterator::Iterator>::next (9 samples, 0.02%)hashbrown::raw::bitmask::BitMask::lowest_set_bit (5 samples, 0.01%)hashbrown::map::equivalent_key::_{{closure}} (7 samples, 0.02%)<core::num::nonzero::NonZeroU32 as core::cmp::PartialEq>::eq (7 samples, 0.02%)hashbrown::raw::RawTable<T,A>::find::_{{closure}} (11 samples, 0.03%)hashbrown::raw::Bucket<T>::as_ref (4 samples, 0.01%)hashbrown::raw::Bucket<T>::as_ptr (4 samples, 0.01%)core::ptr::mut_ptr::<impl *mut T>::sub (4 samples, 0.01%)core::ptr::mut_ptr::<impl *mut T>::offset (4 samples, 0.01%)hashbrown::raw::h2 (10 samples, 0.03%)hashbrown::map::HashMap<K,V,S,A>::get_inner (119 samples, 0.30%)hashbrown::raw::RawTable<T,A>::get (43 samples, 0.11%)hashbrown::raw::RawTable<T,A>::find (43 samples, 0.11%)hashbrown::raw::RawTableInner<A>::find_inner (43 samples, 0.11%)hashbrown::raw::sse2::Group::match_byte (4 samples, 0.01%)core::core_arch::x86::sse2::_mm_movemask_epi8 (4 samples, 0.01%)std::collections::hash::map::HashMap<K,V,S>::get (122 samples, 0.31%)hashbrown::map::HashMap<K,V,S,A>::get (122 samples, 0.31%)std::sync::mutex::MutexGuard<T>::new (8 samples, 0.02%)std::sync::poison::Flag::guard (5 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (47 samples, 0.12%)std::sys::unix::locks::futex_mutex::Mutex::lock (37 samples, 0.09%)core::sync::atomic::AtomicU32::compare_exchange (37 samples, 0.09%)core::sync::atomic::atomic_compare_exchange (37 samples, 0.09%)core::sync::atomic::AtomicU32::compare_exchange_weak (108 samples, 0.28%)core::sync::atomic::atomic_compare_exchange_weak (108 samples, 0.28%)std::sync::rwlock::RwLock<T>::read (129 samples, 0.33%)std::sys::unix::locks::futex_rwlock::RwLock::read (129 samples, 0.33%)std::sys::unix::locks::futex_rwlock::is_read_lockable (7 samples, 0.02%)zssp::antireplay::Window<_,_>::check (11 samples, 0.03%)core::sync::atomic::AtomicU64::load (6 samples, 0.02%)core::sync::atomic::atomic_load (6 samples, 0.02%)arrayvec::arrayvec::ArrayVec<T,_>::push (19 samples, 0.05%)arrayvec::arrayvec_impl::ArrayVecImpl::push (19 samples, 0.05%)arrayvec::arrayvec_impl::ArrayVecImpl::try_push (19 samples, 0.05%)arrayvec::arrayvec_impl::ArrayVecImpl::push_unchecked (13 samples, 0.03%)core::ptr::write (12 samples, 0.03%)core::mem::maybe_uninit::MaybeUninit<T>::assume_init_read (4 samples, 0.01%)core::ptr::const_ptr::<impl *const T>::read (4 samples, 0.01%)core::ptr::read (4 samples, 0.01%)core::mem::maybe_uninit::MaybeUninit<T>::assume_init (4 samples, 0.01%)core::mem::maybe_uninit::MaybeUninit<T>::write (4 samples, 0.01%)zssp::fragged::Fragged<Fragment,_>::assemble (5 samples, 0.01%)zssp::fragged::Fragged<Fragment,_>::assemble (62 samples, 0.16%)<T as core::convert::TryInto<U>>::try_into (26 samples, 0.07%)core::array::_<impl core::convert::TryFrom<&[T]> for [T: N]>::try_from (26 samples, 0.07%)core::result::Result<T,E>::map (26 samples, 0.07%)zssp::zeta::from_nonce (31 samples, 0.08%)core::num::<impl u64>::from_be_bytes (4 samples, 0.01%)core::num::<impl u64>::from_be (4 samples, 0.01%)core::num::<impl u64>::swap_bytes (4 samples, 0.01%)<core::slice::iter::IterMut<T> as core::iter::traits::iterator::Iterator>::next (9 samples, 0.02%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (55 samples, 0.14%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (55 samples, 0.14%)core::sync::atomic::AtomicU32::fetch_sub (53 samples, 0.14%)core::sync::atomic::atomic_sub (53 samples, 0.14%)[libcrypto.so.3] (103 samples, 0.26%)CRYPTO_gcm128_decrypt (234 samples, 0.60%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (4 samples, 0.01%)[libcrypto.so.3] (17 samples, 0.04%)CRYPTO_gcm128_decrypt_ctr32 (452 samples, 1.16%)[libcrypto.so.3] (355 samples, 0.91%)CRYPTO_gcm128_setiv (15 samples, 0.04%)[libcrypto.so.3] (14 samples, 0.04%)asm_sysvec_apic_timer_interrupt (5 samples, 0.01%)sysvec_apic_timer_interrupt (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)__perf_event_task_sched_in (9 samples, 0.02%)perf_ctx_enable (9 samples, 0.02%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::decrypt_in_place (4,297 samples, 10.99%)<zssp::crypto_im..zssp::crypto_impl::openssl::CipherCtx::update (4,297 samples, 10.99%)zssp::crypto_imp..EVP_DecryptUpdate (4,294 samples, 10.99%)EVP_DecryptUpdate[libcrypto.so.3] (4,195 samples, 10.73%)[libcrypto.so.3][libcrypto.so.3] (4,187 samples, 10.71%)[libcrypto.so.3][libcrypto.so.3] (4,169 samples, 10.67%)[libcrypto.so.3][libcrypto.so.3] (3,429 samples, 8.77%)[libcrypto.s..[libcrypto.so.3] (3,280 samples, 8.39%)[libcrypto.s..asm_sysvec_reschedule_ipi (10 samples, 0.03%)sysvec_reschedule_ipi (10 samples, 0.03%)irqentry_exit (10 samples, 0.03%)irqentry_exit_to_user_mode (10 samples, 0.03%)exit_to_user_mode_prepare (10 samples, 0.03%)exit_to_user_mode_loop (10 samples, 0.03%)schedule (10 samples, 0.03%)__schedule (10 samples, 0.03%)finish_task_switch.isra.0 (10 samples, 0.03%)CRYPTO_clear_free (106 samples, 0.27%)OPENSSL_cleanse (103 samples, 0.26%)EVP_CIPHER_free (44 samples, 0.11%)EVP_CIPHER_CTX_free (199 samples, 0.51%)EVP_CIPHER_CTX_reset (198 samples, 0.51%)cfree (37 samples, 0.09%)[libc.so.6] (5 samples, 0.01%)cfree (9 samples, 0.02%)[libc.so.6] (4 samples, 0.01%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::AesGcmOpenSSLDec> (212 samples, 0.54%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::CipherCtx> (212 samples, 0.54%)<zssp::crypto_impl::openssl::CipherCtx as core::ops::drop::Drop>::drop (212 samples, 0.54%)[libcrypto.so.3] (26 samples, 0.07%)[libcrypto.so.3] (26 samples, 0.07%)[libcrypto.so.3] (26 samples, 0.07%)CRYPTO_gcm128_finish (24 samples, 0.06%)[libcrypto.so.3] (18 samples, 0.05%)zssp::crypto_impl::openssl::CipherCtx::finalize (36 samples, 0.09%)EVP_DecryptFinal_ex (36 samples, 0.09%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)OSSL_PARAM_get_octet_string (7 samples, 0.02%)[libcrypto.so.3] (5 samples, 0.01%)[libc.so.6] (12 samples, 0.03%)OSSL_PARAM_locate (30 samples, 0.08%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::finish (336 samples, 0.86%)zssp::crypto_impl::openssl::CipherCtx::set_tag (88 samples, 0.23%)EVP_CIPHER_CTX_ctrl (88 samples, 0.23%)[libcrypto.so.3] (49 samples, 0.13%)[libc.so.6] (27 samples, 0.07%)OSSL_PARAM_locate (38 samples, 0.10%)EVP_CIPHER_CTX_set_padding (90 samples, 0.23%)[libcrypto.so.3] (53 samples, 0.14%)[libc.so.6] (24 samples, 0.06%)OSSL_PARAM_locate (48 samples, 0.12%)EVP_CIPHER_CTX_get_iv_length (82 samples, 0.21%)[libcrypto.so.3] (60 samples, 0.15%)[libc.so.6] (18 samples, 0.05%)OSSL_PARAM_locate (34 samples, 0.09%)EVP_CIPHER_CTX_get_key_length (51 samples, 0.13%)[libcrypto.so.3] (42 samples, 0.11%)CRYPTO_THREAD_read_lock (155 samples, 0.40%)pthread_rwlock_rdlock (151 samples, 0.39%)pthread_rwlock_unlock (88 samples, 0.23%)CRYPTO_THREAD_unlock (91 samples, 0.23%)EVP_CIPHER_up_ref (29 samples, 0.07%)OPENSSL_LH_retrieve (59 samples, 0.15%)[libcrypto.so.3] (51 samples, 0.13%)pthread_rwlock_rdlock (40 samples, 0.10%)CRYPTO_THREAD_read_lock (43 samples, 0.11%)pthread_rwlock_unlock (49 samples, 0.13%)CRYPTO_THREAD_unlock (54 samples, 0.14%)OPENSSL_strnlen (7 samples, 0.02%)CRYPTO_strndup (15 samples, 0.04%)OPENSSL_strcasecmp (25 samples, 0.06%)OPENSSL_LH_retrieve (150 samples, 0.38%)[libcrypto.so.3] (142 samples, 0.36%)[libcrypto.so.3] (42 samples, 0.11%)[libcrypto.so.3] (298 samples, 0.76%)cfree (23 samples, 0.06%)[libc.so.6] (6 samples, 0.02%)[libcrypto.so.3] (652 samples, 1.67%)EVP_CIPHER_fetch (660 samples, 1.69%)[libcrypto.so.3] (658 samples, 1.68%)EVP_CIPHER_free (15 samples, 0.04%)EVP_CIPHER_up_ref (14 samples, 0.04%)CRYPTO_malloc (4 samples, 0.01%)[libc.so.6] (8 samples, 0.02%)malloc (7 samples, 0.02%)CRYPTO_zalloc (21 samples, 0.05%)CRYPTO_gcm128_init (149 samples, 0.38%)[libcrypto.so.3] (122 samples, 0.31%)[libcrypto.so.3] (188 samples, 0.48%)[libcrypto.so.3] (34 samples, 0.09%)EVP_CipherInit_ex (1,072 samples, 2.74%)EV..[libcrypto.so.3] (1,072 samples, 2.74%)[l..[libcrypto.so.3] (221 samples, 0.57%)zssp::crypto_impl::openssl::CipherCtx::cipher_init (1,074 samples, 2.75%)zs..[libc.so.6] (4 samples, 0.01%)malloc (15 samples, 0.04%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_dec (1,188 samples, 3.04%)<zs..zssp::crypto_impl::openssl::CipherCtx::new (22 samples, 0.06%)CRYPTO_zalloc (22 samples, 0.06%)core::slice::index::<impl core::ops::index::Index<I> for [T]>::index (5 samples, 0.01%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::index (5 samples, 0.01%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::get_unchecked (4 samples, 0.01%)<core::ops::range::Range<usize> as core::slice::index::SliceIndex<[T]>>::get_unchecked (4 samples, 0.01%)core::ptr::const_ptr::<impl *const T>::add (4 samples, 0.01%)core::ptr::const_ptr::<impl *const T>::offset (4 samples, 0.01%)core::slice::index::<impl core::ops::index::IndexMut<I> for [T]>::index_mut (6 samples, 0.02%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::index_mut (6 samples, 0.02%)std::io::impls::<impl std::io::Write for &mut W>::write (183 samples, 0.47%)<arrayvec::arrayvec::ArrayVec<u8,_> as std::io::Write>::write (183 samples, 0.47%)arrayvec::arrayvec::ArrayVec<T,_>::try_extend_from_slice (181 samples, 0.46%)core::intrinsics::copy_nonoverlapping (181 samples, 0.46%)[libc.so.6] (181 samples, 0.46%)zssp::zeta::receive_payload_in_place (6,109 samples, 15.63%)zssp::zeta::receive_payl..zssp::antireplay::Window<_,_>::update (13 samples, 0.03%)core::sync::atomic::AtomicU64::fetch_max (13 samples, 0.03%)core::sync::atomic::atomic_umax (13 samples, 0.03%)zssp::zssp::Context<Crypto>::receive (8,570 samples, 21.93%)zssp::zssp::Context<Crypto>::receivezssp::zssp::parse_fragment_header (78 samples, 0.20%)core::slice::<impl [T]>::copy_from_slice (6 samples, 0.02%)core::intrinsics::copy_nonoverlapping (6 samples, 0.02%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (60 samples, 0.15%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (60 samples, 0.15%)std::sys::unix::locks::futex_mutex::Mutex::unlock (58 samples, 0.15%)core::sync::atomic::AtomicU32::swap (53 samples, 0.14%)core::sync::atomic::atomic_swap (53 samples, 0.14%)std::sync::mutex::MutexGuard<T>::new (4 samples, 0.01%)std::sync::poison::Flag::guard (4 samples, 0.01%)std::thread::panicking (4 samples, 0.01%)std::panicking::panicking (4 samples, 0.01%)std::panicking::panic_count::count_is_zero (4 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (90 samples, 0.23%)std::sys::unix::locks::futex_mutex::Mutex::lock (86 samples, 0.22%)core::sync::atomic::AtomicU32::compare_exchange (80 samples, 0.20%)core::sync::atomic::atomic_compare_exchange (80 samples, 0.20%)<zssp::crypto_impl::openssl::Aes256OpenSSLEnc as zssp::crypto::aes::Aes256Enc>::encrypt_in_place (337 samples, 0.86%)zssp::crypto_impl::openssl::CipherCtx::update (184 samples, 0.47%)EVP_EncryptUpdate (183 samples, 0.47%)[libcrypto.so.3] (160 samples, 0.41%)[libcrypto.so.3] (121 samples, 0.31%)[libcrypto.so.3] (116 samples, 0.30%)CRYPTO_gcm128_encrypt (243 samples, 0.62%)[libcrypto.so.3] (137 samples, 0.35%)[libcrypto.so.3] (326 samples, 0.83%)[libcrypto.so.3] (21 samples, 0.05%)CRYPTO_gcm128_encrypt_ctr32 (442 samples, 1.13%)CRYPTO_gcm128_setiv (16 samples, 0.04%)[libcrypto.so.3] (15 samples, 0.04%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::encrypt (3,402 samples, 8.70%)<zssp::crypt..zssp::crypto_impl::openssl::CipherCtx::update (3,400 samples, 8.70%)zssp::crypto..EVP_EncryptUpdate (3,399 samples, 8.70%)EVP_EncryptU..[libcrypto.so.3] (3,327 samples, 8.51%)[libcrypto.s..[libcrypto.so.3] (3,322 samples, 8.50%)[libcrypto.s..[libcrypto.so.3] (3,300 samples, 8.44%)[libcrypto.s..[libcrypto.so.3] (2,562 samples, 6.56%)[libcrypt..[libcrypto.so.3] (2,334 samples, 5.97%)[libcryp..CRYPTO_clear_free (96 samples, 0.25%)OPENSSL_cleanse (96 samples, 0.25%)EVP_CIPHER_free (43 samples, 0.11%)cfree (23 samples, 0.06%)[libc.so.6] (5 samples, 0.01%)EVP_CIPHER_CTX_free (185 samples, 0.47%)EVP_CIPHER_CTX_reset (185 samples, 0.47%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc> (191 samples, 0.49%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::CipherCtx> (191 samples, 0.49%)<zssp::crypto_impl::openssl::CipherCtx as core::ops::drop::Drop>::drop (191 samples, 0.49%)cfree (5 samples, 0.01%)[libc.so.6] (4 samples, 0.01%)zssp::crypto_impl::openssl::CipherCtx::finalize (69 samples, 0.18%)EVP_EncryptFinal_ex (67 samples, 0.17%)[libcrypto.so.3] (51 samples, 0.13%)[libcrypto.so.3] (50 samples, 0.13%)[libcrypto.so.3] (45 samples, 0.12%)CRYPTO_gcm128_tag (45 samples, 0.12%)CRYPTO_gcm128_finish (37 samples, 0.09%)[libcrypto.so.3] (22 samples, 0.06%)[libc.so.6] (15 samples, 0.04%)OSSL_PARAM_locate (39 samples, 0.10%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::finish (345 samples, 0.88%)zssp::crypto_impl::openssl::CipherCtx::get_tag (84 samples, 0.21%)EVP_CIPHER_CTX_ctrl (83 samples, 0.21%)[libcrypto.so.3] (51 samples, 0.13%)[libc.so.6] (25 samples, 0.06%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)OSSL_PARAM_locate (41 samples, 0.10%)strcmp@plt (4 samples, 0.01%)[libcrypto.so.3] (49 samples, 0.13%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)EVP_CIPHER_CTX_set_padding (83 samples, 0.21%)asm_sysvec_reschedule_ipi (6 samples, 0.02%)sysvec_reschedule_ipi (6 samples, 0.02%)irqentry_exit (6 samples, 0.02%)irqentry_exit_to_user_mode (6 samples, 0.02%)exit_to_user_mode_prepare (6 samples, 0.02%)exit_to_user_mode_loop (6 samples, 0.02%)schedule (6 samples, 0.02%)__schedule (6 samples, 0.02%)finish_task_switch.isra.0 (6 samples, 0.02%)[libc.so.6] (21 samples, 0.05%)OSSL_PARAM_locate (41 samples, 0.10%)EVP_CIPHER_CTX_get_iv_length (82 samples, 0.21%)[libcrypto.so.3] (54 samples, 0.14%)[libc.so.6] (9 samples, 0.02%)OSSL_PARAM_locate (26 samples, 0.07%)EVP_CIPHER_CTX_get_key_length (48 samples, 0.12%)[libcrypto.so.3] (40 samples, 0.10%)CRYPTO_THREAD_read_lock (116 samples, 0.30%)pthread_rwlock_rdlock (112 samples, 0.29%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)pthread_rwlock_unlock (95 samples, 0.24%)asm_sysvec_reschedule_ipi (4 samples, 0.01%)sysvec_reschedule_ipi (4 samples, 0.01%)irqentry_exit (4 samples, 0.01%)irqentry_exit_to_user_mode (4 samples, 0.01%)exit_to_user_mode_prepare (4 samples, 0.01%)exit_to_user_mode_loop (4 samples, 0.01%)schedule (4 samples, 0.01%)__schedule (4 samples, 0.01%)finish_task_switch.isra.0 (4 samples, 0.01%)__perf_event_task_sched_in (4 samples, 0.01%)perf_pmu_nop_void (4 samples, 0.01%)CRYPTO_THREAD_unlock (101 samples, 0.26%)EVP_CIPHER_up_ref (39 samples, 0.10%)OPENSSL_LH_retrieve (56 samples, 0.14%)[libcrypto.so.3] (48 samples, 0.12%)[libcrypto.so.3] (6 samples, 0.02%)pthread_rwlock_rdlock (20 samples, 0.05%)CRYPTO_THREAD_read_lock (24 samples, 0.06%)pthread_rwlock_unlock (51 samples, 0.13%)CRYPTO_THREAD_unlock (54 samples, 0.14%)CRYPTO_strndup (10 samples, 0.03%)malloc (6 samples, 0.02%)OPENSSL_strcasecmp (44 samples, 0.11%)OPENSSL_LH_retrieve (173 samples, 0.44%)[libcrypto.so.3] (157 samples, 0.40%)[libcrypto.so.3] (53 samples, 0.14%)cfree (17 samples, 0.04%)[libc.so.6] (6 samples, 0.02%)[libcrypto.so.3] (290 samples, 0.74%)EVP_CIPHER_fetch (622 samples, 1.59%)[libcrypto.so.3] (620 samples, 1.59%)[libcrypto.so.3] (612 samples, 1.57%)EVP_CIPHER_free (14 samples, 0.04%)EVP_CIPHER_up_ref (18 samples, 0.05%)CRYPTO_malloc (4 samples, 0.01%)[libc.so.6] (9 samples, 0.02%)malloc (8 samples, 0.02%)CRYPTO_zalloc (24 samples, 0.06%)OPENSSL_init_crypto (4 samples, 0.01%)CRYPTO_gcm128_init (157 samples, 0.40%)[libcrypto.so.3] (141 samples, 0.36%)finish_task_switch.isra.0 (4 samples, 0.01%)__perf_event_task_sched_in (4 samples, 0.01%)perf_ctx_enable (4 samples, 0.01%)EVP_CipherInit_ex (1,051 samples, 2.69%)EV..[libcrypto.so.3] (1,051 samples, 2.69%)[l..[libcrypto.so.3] (235 samples, 0.60%)[libcrypto.so.3] (202 samples, 0.52%)[libcrypto.so.3] (43 samples, 0.11%)asm_sysvec_reschedule_ipi (6 samples, 0.02%)sysvec_reschedule_ipi (6 samples, 0.02%)irqentry_exit (6 samples, 0.02%)irqentry_exit_to_user_mode (6 samples, 0.02%)exit_to_user_mode_prepare (6 samples, 0.02%)exit_to_user_mode_loop (6 samples, 0.02%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)zssp::crypto_impl::openssl::CipherCtx::cipher_init (1,054 samples, 2.70%)zs..[libc.so.6] (4 samples, 0.01%)malloc (7 samples, 0.02%)CRYPTO_zalloc (13 samples, 0.03%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_enc (1,154 samples, 2.95%)<zs..zssp::crypto_impl::openssl::CipherCtx::new (14 samples, 0.04%)__rdl_alloc (6 samples, 0.02%)std::sys::unix::alloc::<impl core::alloc::global::GlobalAlloc for std::alloc::System>::alloc (6 samples, 0.02%)[libc.so.6] (49 samples, 0.13%)[libc.so.6] (850 samples, 2.17%)[..asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)__get_user_nocheck_4 (8 samples, 0.02%)futex_q_lock (13 samples, 0.03%)futex_q_unlock (5 samples, 0.01%)__x64_sys_futex (35 samples, 0.09%)do_futex (33 samples, 0.08%)futex_wait (32 samples, 0.08%)futex_wait_setup (28 samples, 0.07%)entry_SYSCALL_64_after_hwframe (37 samples, 0.09%)do_syscall_64 (37 samples, 0.09%)__lll_lock_wait_private (55 samples, 0.14%)futex_wake_mark (11 samples, 0.03%)wake_q_add_safe (4 samples, 0.01%)call_function_single_prep_ipi (4 samples, 0.01%)__smp_call_single_queue (17 samples, 0.04%)native_send_call_func_single_ipi (13 samples, 0.03%)x2apic_send_IPI (12 samples, 0.03%)native_write_msr (10 samples, 0.03%)llist_add_batch (10 samples, 0.03%)futex_wake (123 samples, 0.31%)wake_up_q (63 samples, 0.16%)try_to_wake_up (58 samples, 0.15%)ttwu_queue_wakelist (38 samples, 0.10%)__x64_sys_futex (133 samples, 0.34%)do_futex (132 samples, 0.34%)entry_SYSCALL_64_after_hwframe (145 samples, 0.37%)do_syscall_64 (144 samples, 0.37%)syscall_exit_to_user_mode (7 samples, 0.02%)exit_to_user_mode_prepare (5 samples, 0.01%)alloc::vec::Vec<T,A>::with_capacity_in (1,653 samples, 4.23%)alloc..alloc::raw_vec::RawVec<T,A>::with_capacity_in (1,653 samples, 4.23%)alloc..alloc::raw_vec::RawVec<T,A>::allocate_in (1,653 samples, 4.23%)alloc..<alloc::alloc::Global as core::alloc::Allocator>::allocate (1,651 samples, 4.22%)<allo..alloc::alloc::Global::alloc_impl (1,651 samples, 4.22%)alloc..alloc::alloc::alloc (1,651 samples, 4.22%)alloc..malloc (1,644 samples, 4.21%)malloc__lll_lock_wake_private (152 samples, 0.39%)alloc::slice::<impl [T]>::to_vec (1,857 samples, 4.75%)alloc:..alloc::slice::<impl [T]>::to_vec_in (1,857 samples, 4.75%)alloc:..alloc::slice::hack::to_vec (1,857 samples, 4.75%)alloc:..<T as alloc::slice::hack::ConvertVec>::to_vec (1,857 samples, 4.75%)<T as ..core::ptr::const_ptr::<impl *const T>::copy_to_nonoverlapping (204 samples, 0.52%)core::intrinsics::copy_nonoverlapping (204 samples, 0.52%)[libc.so.6] (203 samples, 0.52%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)core::sync::atomic::AtomicUsize::compare_exchange_weak (100 samples, 0.26%)core::sync::atomic::atomic_compare_exchange_weak (100 samples, 0.26%)std::sync::mpmc::array::Channel<T>::start_send (155 samples, 0.40%)core::sync::atomic::AtomicUsize::load (32 samples, 0.08%)core::sync::atomic::atomic_load (32 samples, 0.08%)core::ptr::mut_ptr::<impl *mut T>::write (4 samples, 0.01%)core::ptr::write (4 samples, 0.01%)std::sync::mpmc::array::Channel<T>::send (185 samples, 0.47%)std::sync::mpmc::array::Channel<T>::write (13 samples, 0.03%)std::sync::mpmc::waker::SyncWaker::notify (7 samples, 0.02%)core::sync::atomic::AtomicBool::load (5 samples, 0.01%)core::sync::atomic::atomic_load (5 samples, 0.01%)benchmark::bob_main::_{{closure}} (3,332 samples, 8.53%)benchmark::b..std::sync::mpsc::SyncSender<T>::send (1,474 samples, 3.77%)std:..std::sync::mpmc::Sender<T>::send (1,473 samples, 3.77%)std:..core::mem::drop (27 samples, 0.07%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<zssp::zeta::MutableState<benchmark::TestApplication>>> (27 samples, 0.07%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (27 samples, 0.07%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (27 samples, 0.07%)core::sync::atomic::AtomicU32::fetch_sub (27 samples, 0.07%)core::sync::atomic::atomic_sub (27 samples, 0.07%)core::slice::<impl [T]>::copy_from_slice (7 samples, 0.02%)core::intrinsics::copy_nonoverlapping (7 samples, 0.02%)core::sync::atomic::AtomicU32::compare_exchange_weak (35 samples, 0.09%)core::sync::atomic::atomic_compare_exchange_weak (35 samples, 0.09%)std::sync::rwlock::RwLock<T>::read (36 samples, 0.09%)std::sys::unix::locks::futex_rwlock::RwLock::read (36 samples, 0.09%)benchmark::bob_main (18,132 samples, 46.39%)benchmark::bob_mainzssp::zssp::Context<Crypto>::send (8,692 samples, 22.24%)zssp::zssp::Context<Crypto>::sendzssp::zeta::send_payload (8,691 samples, 22.24%)zssp::zeta::send_payloadzssp::zeta::get_counter (12 samples, 0.03%)core::sync::atomic::AtomicU64::fetch_add (11 samples, 0.03%)core::sync::atomic::atomic_add (11 samples, 0.03%)cfree (19 samples, 0.05%)clock_gettime (12 samples, 0.03%)core::hash::BuildHasher::hash_one (20 samples, 0.05%)core::hash::impls::<impl core::hash::Hash for &T>::hash (12 samples, 0.03%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::fragged::Fragged<alloc::vec::Vec<u8>,48_usize>>> (4 samples, 0.01%)core::result::Result<T,E>::unwrap (7 samples, 0.02%)pthread_rwlock_rdlock (21 samples, 0.05%)pthread_rwlock_unlock (15 samples, 0.04%)std::sync::mpmc::Receiver<T>::recv_timeout (12 samples, 0.03%)std::sync::mpmc::Sender<T>::send (15 samples, 0.04%)<std::sync::mpmc::select::Token as core::default::Default>::default (7 samples, 0.02%)std::sync::mpmc::array::Channel<T>::recv (24 samples, 0.06%)std::sync::mpmc::array::Channel<T>::send (11 samples, 0.03%)std::sync::mpmc::array::Channel<T>::start_recv (12 samples, 0.03%)std::sync::mpmc::utils::Backoff::new (9 samples, 0.02%)std::sync::mpmc::waker::SyncWaker::notify (19 samples, 0.05%)core::sync::atomic::AtomicBool::load (4 samples, 0.01%)core::sync::atomic::atomic_load (4 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (6 samples, 0.02%)std::sys::unix::time::Timespec::sub_timespec (6 samples, 0.02%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (15 samples, 0.04%)std::time::SystemTime::checked_add (5 samples, 0.01%)zssp::fragged::Fragged<Fragment,_>::assemble (21 samples, 0.05%)zssp::zeta::from_nonce (5 samples, 0.01%)alloc::vec::Vec<T,A>::with_capacity_in (14 samples, 0.04%)alloc::raw_vec::RawVec<T,A>::with_capacity_in (14 samples, 0.04%)alloc::raw_vec::RawVec<T,A>::allocate_in (14 samples, 0.04%)<alloc::alloc::Global as core::alloc::Allocator>::allocate (14 samples, 0.04%)alloc::alloc::Global::alloc_impl (14 samples, 0.04%)alloc::alloc::alloc (14 samples, 0.04%)alloc::slice::<impl [T]>::to_vec (20 samples, 0.05%)alloc::slice::<impl [T]>::to_vec_in (20 samples, 0.05%)alloc::slice::hack::to_vec (20 samples, 0.05%)<T as alloc::slice::hack::ConvertVec>::to_vec (20 samples, 0.05%)core::ptr::const_ptr::<impl *const T>::copy_to_nonoverlapping (6 samples, 0.02%)core::intrinsics::copy_nonoverlapping (6 samples, 0.02%)zssp::zeta::send_payload (50 samples, 0.13%)benchmark::bob_main::_{{closure}} (29 samples, 0.07%)std::sync::mpsc::SyncSender<T>::send (9 samples, 0.02%)core::ptr::drop_in_place<arrayvec::arrayvec::ArrayVec<alloc::vec::Vec<u8>,48_usize>> (7 samples, 0.02%)<arrayvec::arrayvec::ArrayVec<T,_> as core::ops::drop::Drop>::drop (7 samples, 0.02%)arrayvec::arrayvec::ArrayVec<T,_>::clear (7 samples, 0.02%)arrayvec::arrayvec_impl::ArrayVecImpl::clear (7 samples, 0.02%)arrayvec::arrayvec_impl::ArrayVecImpl::truncate (7 samples, 0.02%)core::ptr::drop_in_place<[alloc::vec::Vec<u8>]> (7 samples, 0.02%)core::ptr::drop_in_place<alloc::vec::Vec<u8>> (7 samples, 0.02%)core::ptr::drop_in_place<alloc::raw_vec::RawVec<u8>> (7 samples, 0.02%)<alloc::raw_vec::RawVec<T,A> as core::ops::drop::Drop>::drop (7 samples, 0.02%)<alloc::alloc::Global as core::alloc::Allocator>::deallocate (7 samples, 0.02%)alloc::alloc::dealloc (7 samples, 0.02%)cfree (5 samples, 0.01%)__lll_lock_wake_private (5 samples, 0.01%)__entry_text_start (5 samples, 0.01%)getrandom::imp::getrandom_inner (5 samples, 0.01%)zssp::zssp::Context<Crypto>::receive (70 samples, 0.18%)std::sync::mpmc::array::Channel<T>::start_recv (13 samples, 0.03%)[unknown] (37,550 samples, 96.08%)[unknown]zssp::zssp::parse_fragment_header (4 samples, 0.01%)EVP_DecryptUpdate (31 samples, 0.08%)__bss_start (36 samples, 0.09%)[libcrypto.so.3] (5 samples, 0.01%)_raw_spin_unlock (4 samples, 0.01%)preempt_schedule_thunk (4 samples, 0.01%)preempt_schedule (4 samples, 0.01%)__schedule (4 samples, 0.01%)finish_task_switch.isra.0 (4 samples, 0.01%)__perf_event_task_sched_in (4 samples, 0.01%)perf_ctx_enable (4 samples, 0.01%)entry_SYSCALL_64_after_hwframe (9 samples, 0.02%)do_syscall_64 (7 samples, 0.02%)__x64_sys_exit_group (6 samples, 0.02%)do_group_exit (6 samples, 0.02%)do_exit (6 samples, 0.02%)exit_mm (6 samples, 0.02%)mmput (6 samples, 0.02%)__mmput (6 samples, 0.02%)exit_mmap (6 samples, 0.02%)unmap_vmas (5 samples, 0.01%)unmap_single_vma (5 samples, 0.01%)unmap_page_range (5 samples, 0.01%)zap_pmd_range.isra.0 (5 samples, 0.01%)zap_pte_range (5 samples, 0.01%)entry_SYSCALL_64_safe_stack (15 samples, 0.04%)ret_from_fork (10 samples, 0.03%)schedule_tail (10 samples, 0.03%)finish_task_switch.isra.0 (10 samples, 0.03%)__perf_event_task_sched_in (10 samples, 0.03%)perf_ctx_enable (10 samples, 0.03%)syscall_return_via_sysret (9 samples, 0.02%)core::ptr::drop_in_place<arrayvec::arrayvec::ArrayVec<alloc::vec::Vec<u8>,48_usize>> (5 samples, 0.01%)<arrayvec::arrayvec::ArrayVec<T,_> as core::ops::drop::Drop>::drop (5 samples, 0.01%)arrayvec::arrayvec::ArrayVec<T,_>::clear (5 samples, 0.01%)arrayvec::arrayvec_impl::ArrayVecImpl::clear (5 samples, 0.01%)arrayvec::arrayvec_impl::ArrayVecImpl::truncate (5 samples, 0.01%)core::ptr::drop_in_place<[alloc::vec::Vec<u8>]> (5 samples, 0.01%)core::ptr::drop_in_place<alloc::vec::Vec<u8>> (5 samples, 0.01%)core::ptr::drop_in_place<alloc::raw_vec::RawVec<u8>> (5 samples, 0.01%)<alloc::raw_vec::RawVec<T,A> as core::ops::drop::Drop>::drop (5 samples, 0.01%)<alloc::alloc::Global as core::alloc::Allocator>::deallocate (5 samples, 0.01%)alloc::alloc::dealloc (5 samples, 0.01%)benchmark (39,077 samples, 99.99%)benchmarkzssp::zssp::Context<Crypto>::receive (41 samples, 0.10%)perf_event_exec (4 samples, 0.01%)perf_event_enable_on_exec (4 samples, 0.01%)ctx_resched (4 samples, 0.01%)perf_ctx_enable (4 samples, 0.01%)all (39,082 samples, 100%)perf-exec (5 samples, 0.01%)entry_SYSCALL_64_after_hwframe (5 samples, 0.01%)do_syscall_64 (5 samples, 0.01%)__x64_sys_execve (5 samples, 0.01%)do_execveat_common.isra.0 (5 samples, 0.01%)bprm_execve (5 samples, 0.01%)bprm_execve.part.0 (5 samples, 0.01%)exec_binprm (5 samples, 0.01%)search_binary_handler (5 samples, 0.01%)load_elf_binary (5 samples, 0.01%)begin_new_exec (5 samples, 0.01%) \ No newline at end of file diff --git a/src/zeta.rs b/src/zeta.rs index f6d3cb7..cd4ce9a 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -1676,16 +1676,11 @@ pub(crate) fn send_payload( mtu_sized_buffer[..HEADER_SIZE].copy_from_slice(&header); mtu_sized_buffer[FRAGMENT_NO_IDX] = fragment_no as u8; - cipher.encrypt( - &payload[i..j], - &mut mtu_sized_buffer[HEADER_SIZE..HEADER_SIZE + fragment_len], - ); + let fragment_start = &mut mtu_sized_buffer[HEADER_SIZE..HEADER_SIZE + fragment_len]; + cipher.encrypt(&payload[i..j], fragment_start); - state.hk_send.encrypt_in_place( - (&mut mtu_sized_buffer[HEADER_AUTH_START..HEADER_AUTH_END]) - .try_into() - .unwrap(), - ); + let header_auth = &mut mtu_sized_buffer[HEADER_AUTH_START..HEADER_AUTH_END]; + state.hk_send.encrypt_in_place(header_auth.try_into().unwrap()); if !send(&mut mtu_sized_buffer[..HEADER_SIZE + fragment_len]) { return Ok(()); @@ -1699,17 +1694,12 @@ pub(crate) fn send_payload( mtu_sized_buffer[..HEADER_SIZE].copy_from_slice(&header); mtu_sized_buffer[FRAGMENT_NO_IDX] = fragment_no as u8; - cipher.encrypt( - &payload[i..], - &mut mtu_sized_buffer[HEADER_SIZE..HEADER_SIZE + payload_rem], - ); + let fragment_start = &mut mtu_sized_buffer[HEADER_SIZE..HEADER_SIZE + payload_rem]; + cipher.encrypt(&payload[i..], fragment_start); mtu_sized_buffer[HEADER_SIZE + payload_rem..HEADER_SIZE + fragment_len].copy_from_slice(&cipher.finish()); - state.hk_send.encrypt_in_place( - (&mut mtu_sized_buffer[HEADER_AUTH_START..HEADER_AUTH_END]) - .try_into() - .unwrap(), - ); + let header_auth = &mut mtu_sized_buffer[HEADER_AUTH_START..HEADER_AUTH_END]; + state.hk_send.encrypt_in_place(header_auth.try_into().unwrap()); if !send(&mut mtu_sized_buffer[..HEADER_SIZE + fragment_len]) { return Ok(()); diff --git a/src/zssp.rs b/src/zssp.rs index 2996c0c..27b24a6 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -549,7 +549,8 @@ impl Context { challenge_packet.extend(challenge); let nonce = to_nonce(PACKET_TYPE_CHALLENGE, ctx.rng.lock().unwrap().next_u64()); challenge_packet[FRAGMENT_COUNT_IDX] = 1; - challenge_packet[PACKET_NONCE_START..HEADER_SIZE].copy_from_slice(&nonce[..PACKET_NONCE_SIZE]); + challenge_packet[PACKET_NONCE_START..HEADER_SIZE] + .copy_from_slice(&nonce[..PACKET_NONCE_SIZE]); set_header(&mut challenge_packet, 0, &nonce); send_unassociated_reply(&mut challenge_packet); From ef0cde79ce560c96d54e1bba5fe506255c8afe8a Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 14 Aug 2023 17:03:00 -0400 Subject: [PATCH 39/50] improved performance --- .gitignore | 3 +- flamegraph.svg | 2 +- src/crypto_impl/openssl.rs | 62 +++++++++++++++++++++++--------------- 3 files changed, 41 insertions(+), 26 deletions(-) diff --git a/.gitignore b/.gitignore index 790f4b6..68e7d6c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target -./perf* +perf*.data +perf*.old diff --git a/flamegraph.svg b/flamegraph.svg index 8075ab4..a00c032 100644 --- a/flamegraph.svg +++ b/flamegraph.svg @@ -488,4 +488,4 @@ function search(term) { function format_percent(n) { return n.toFixed(4) + "%"; } -]]>Flame Graph Reset ZoomSearch <zssp::crypto_impl::openssl::Aes256OpenSSLDec as zssp::crypto::aes::Aes256Dec>::decrypt_in_place (13 samples, 0.03%)zssp::crypto_impl::openssl::CipherCtx::update (5 samples, 0.01%)<zssp::crypto_impl::openssl::Aes256OpenSSLEnc as zssp::crypto::aes::Aes256Enc>::encrypt_in_place (35 samples, 0.09%)zssp::crypto_impl::openssl::CipherCtx::update (5 samples, 0.01%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::encrypt (71 samples, 0.18%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_enc (8 samples, 0.02%)CRYPTO_THREAD_read_lock (7 samples, 0.02%)CRYPTO_gcm128_decrypt (8 samples, 0.02%)CRYPTO_gcm128_decrypt_ctr32 (21 samples, 0.05%)CRYPTO_gcm128_encrypt (10 samples, 0.03%)CRYPTO_gcm128_encrypt_ctr32 (16 samples, 0.04%)CRYPTO_gcm128_finish (4 samples, 0.01%)CRYPTO_gcm128_init (9 samples, 0.02%)CRYPTO_gcm128_tag (5 samples, 0.01%)CRYPTO_zalloc (25 samples, 0.06%)EVP_CIPHER_CTX_ctrl (5 samples, 0.01%)EVP_CIPHER_CTX_reset (7 samples, 0.02%)EVP_CIPHER_CTX_set_padding (12 samples, 0.03%)EVP_CIPHER_fetch (6 samples, 0.02%)EVP_CIPHER_free (17 samples, 0.04%)EVP_DecryptUpdate (4 samples, 0.01%)EVP_EncryptFinal_ex (6 samples, 0.02%)OPENSSL_LH_retrieve (10 samples, 0.03%)OPENSSL_strnlen (5 samples, 0.01%)OSSL_PARAM_locate (121 samples, 0.31%)OSSL_PARAM_set_uint64 (4 samples, 0.01%)[libc.so.6] (60 samples, 0.15%)[libcrypto.so.3] (289 samples, 0.74%)cfree (13 samples, 0.03%)malloc (31 samples, 0.08%)pthread_rwlock_rdlock (5 samples, 0.01%)pthread_rwlock_unlock (7 samples, 0.02%)std::sync::mpmc::Sender<T>::send (9 samples, 0.02%)<std::sync::mpmc::select::Token as core::default::Default>::default (11 samples, 0.03%)std::sync::mpmc::array::Channel<T>::start_send (11 samples, 0.03%)std::sync::mpmc::array::Channel<T>::send (45 samples, 0.12%)std::sync::mpmc::array::Channel<T>::write (7 samples, 0.02%)<std::sync::mpmc::select::Token as core::default::Default>::default (5 samples, 0.01%)std::sync::mpmc::array::Channel<T>::try_recv (19 samples, 0.05%)std::sync::mpmc::utils::Backoff::new (11 samples, 0.03%)core::sync::atomic::AtomicBool::load (10 samples, 0.03%)core::sync::atomic::atomic_load (10 samples, 0.03%)std::sync::mpmc::waker::SyncWaker::notify (11 samples, 0.03%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (7 samples, 0.02%)std::time::Instant::elapsed (7 samples, 0.02%)syscall (10 samples, 0.03%)__entry_text_start (6 samples, 0.02%)[anon] (1,024 samples, 2.62%)[a..zssp::zeta::receive_payload_in_place (25 samples, 0.06%)std::io::impls::<impl std::io::Write for &mut W>::write (7 samples, 0.02%)<arrayvec::arrayvec::ArrayVec<u8,_> as std::io::Write>::write (7 samples, 0.02%)arrayvec::arrayvec::ArrayVec<T,_>::try_extend_from_slice (7 samples, 0.02%)core::intrinsics::copy_nonoverlapping (7 samples, 0.02%)<std::sync::mpmc::zero::ZeroToken as core::default::Default>::default (4 samples, 0.01%)EVP_DecryptUpdate (4 samples, 0.01%)[libc.so.6] (5 samples, 0.01%)std::sync::mpmc::array::Channel<T>::send (8 samples, 0.02%)std::sync::mpmc::array::Channel<T>::start_recv (9 samples, 0.02%)[benchmark] (54 samples, 0.14%)zssp::zssp::Context<Crypto>::receive (17 samples, 0.04%)CRYPTO_THREAD_read_lock (17 samples, 0.04%)CRYPTO_THREAD_run_once (4 samples, 0.01%)CRYPTO_THREAD_unlock (19 samples, 0.05%)CRYPTO_gcm128_finish (14 samples, 0.04%)CRYPTO_gcm128_setiv (7 samples, 0.02%)CRYPTO_get_ex_data (10 samples, 0.03%)OPENSSL_sk_value (4 samples, 0.01%)OSSL_PARAM_construct_size_t (5 samples, 0.01%)[libc.so.6] (17 samples, 0.04%)[libcrypto.so.3] (145 samples, 0.37%)pthread_getspecific (7 samples, 0.02%)pthread_rwlock_rdlock (22 samples, 0.06%)[libcrypto.so.3] (316 samples, 0.81%)pthread_rwlock_unlock (22 samples, 0.06%)<core::num::nonzero::NonZeroU32 as core::hash::Hash>::hash (14 samples, 0.04%)<zssp::crypto_impl::openssl::Aes256OpenSSLDec as zssp::crypto::aes::Aes256Dec>::decrypt_in_place (14 samples, 0.04%)<zssp::crypto_impl::openssl::Aes256OpenSSLEnc as zssp::crypto::aes::Aes256Enc>::encrypt_in_place (4 samples, 0.01%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::decrypt_in_place (80 samples, 0.20%)zssp::crypto_impl::openssl::CipherCtx::update (10 samples, 0.03%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::finish (5 samples, 0.01%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::encrypt (8 samples, 0.02%)zssp::crypto_impl::openssl::CipherCtx::update (8 samples, 0.02%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_dec (7 samples, 0.02%)CRYPTO_THREAD_read_lock (6 samples, 0.02%)CRYPTO_THREAD_unlock (11 samples, 0.03%)CRYPTO_gcm128_decrypt (10 samples, 0.03%)CRYPTO_gcm128_decrypt_ctr32 (22 samples, 0.06%)CRYPTO_gcm128_encrypt (15 samples, 0.04%)CRYPTO_gcm128_encrypt_ctr32 (28 samples, 0.07%)CRYPTO_strndup (5 samples, 0.01%)EVP_CIPHER_CTX_ctrl (10 samples, 0.03%)EVP_CIPHER_CTX_get_iv_length (4 samples, 0.01%)EVP_CIPHER_get_block_size (9 samples, 0.02%)EVP_DecryptUpdate (54 samples, 0.14%)EVP_EncryptUpdate (45 samples, 0.12%)OPENSSL_LH_retrieve (8 samples, 0.02%)OPENSSL_init_crypto (4 samples, 0.01%)OSSL_PARAM_locate (18 samples, 0.05%)[[vdso]] (8 samples, 0.02%)[benchmark] (13 samples, 0.03%)EVP_DecryptUpdate (13 samples, 0.03%)[libc.so.6] (88 samples, 0.23%)[libcrypto.so.3] (322 samples, 0.82%)__bss_start (11 samples, 0.03%)[libcrypto.so.3] (11 samples, 0.03%)__entry_text_start (39 samples, 0.10%)_copy_to_iter (4 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)_copy_to_iter (37 samples, 0.09%)copyout (29 samples, 0.07%)asm_sysvec_reschedule_ipi (10 samples, 0.03%)sysvec_reschedule_ipi (10 samples, 0.03%)irqentry_exit (10 samples, 0.03%)raw_irqentry_exit_cond_resched (10 samples, 0.03%)preempt_schedule_irq (10 samples, 0.03%)__schedule (10 samples, 0.03%)finish_task_switch.isra.0 (10 samples, 0.03%)__perf_event_task_sched_in (10 samples, 0.03%)perf_pmu_nop_void (5 samples, 0.01%)__memcpy (6 samples, 0.02%)chacha_block_generic (225 samples, 0.58%)chacha_permute (202 samples, 0.52%)__x64_sys_getrandom (367 samples, 0.94%)get_random_bytes_user (354 samples, 0.91%)crng_make_state (276 samples, 0.71%)crng_fast_key_erasure (253 samples, 0.65%)exit_to_user_mode_prepare (4 samples, 0.01%)exit_to_user_mode_prepare (13 samples, 0.03%)fpregs_assert_state_consistent (5 samples, 0.01%)do_syscall_64 (396 samples, 1.01%)syscall_exit_to_user_mode (21 samples, 0.05%)entry_SYSCALL_64_after_hwframe (403 samples, 1.03%)<rand_core::os::OsRng as rand_core::RngCore>::next_u64 (485 samples, 1.24%)rand_core::impls::next_u64_via_fill (485 samples, 1.24%)<rand_core::os::OsRng as rand_core::RngCore>::fill_bytes (485 samples, 1.24%)<rand_core::os::OsRng as rand_core::RngCore>::try_fill_bytes (485 samples, 1.24%)getrandom::getrandom (485 samples, 1.24%)getrandom::getrandom_uninit (485 samples, 1.24%)getrandom::imp::getrandom_inner (484 samples, 1.24%)getrandom::util_libc::sys_fill_exact (484 samples, 1.24%)getrandom::imp::getrandom_inner::_{{closure}} (482 samples, 1.23%)getrandom::imp::getrandom (482 samples, 1.23%)syscall (482 samples, 1.23%)syscall_return_via_sysret (11 samples, 0.03%)[libc.so.6] (18 samples, 0.05%)core::ptr::drop_in_place<alloc::sync::Arc<zssp::zeta::Session<benchmark::TestApplication>>> (7 samples, 0.02%)<alloc::sync::Arc<T> as core::ops::drop::Drop>::drop (7 samples, 0.02%)core::sync::atomic::AtomicUsize::fetch_sub (6 samples, 0.02%)core::sync::atomic::atomic_sub (6 samples, 0.02%)_ZN3std4sync4mpmc5array16Channel$LT$T$GT$10start_recv17h7800ca29c64cb868E.llvm.12455019271255371362 (7 samples, 0.02%)core::result::Result<T,E>::map_err (5 samples, 0.01%)core::mem::maybe_uninit::MaybeUninit<T>::assume_init (15 samples, 0.04%)std::sync::mpmc::array::Channel<T>::read (17 samples, 0.04%)core::slice::<impl [T]>::get_unchecked (8 samples, 0.02%)<usize as core::slice::index::SliceIndex<[T]>>::get_unchecked (8 samples, 0.02%)core::ptr::const_ptr::<impl *const T>::add (8 samples, 0.02%)core::ptr::const_ptr::<impl *const T>::offset (8 samples, 0.02%)core::sync::atomic::AtomicUsize::compare_exchange_weak (47 samples, 0.12%)core::sync::atomic::atomic_compare_exchange_weak (47 samples, 0.12%)core::sync::atomic::AtomicUsize::load (143 samples, 0.37%)core::sync::atomic::atomic_load (143 samples, 0.37%)core::sync::atomic::fence (16 samples, 0.04%)std::sync::mpsc::Receiver<T>::try_recv (299 samples, 0.77%)std::sync::mpmc::Receiver<T>::try_recv (299 samples, 0.77%)std::sync::mpmc::array::Channel<T>::try_recv (287 samples, 0.73%)std::sync::mpmc::array::Channel<T>::start_recv (248 samples, 0.63%)<std::time::Instant as core::ops::arith::Sub>::sub (6 samples, 0.02%)std::time::Instant::duration_since (6 samples, 0.02%)std::time::Instant::checked_duration_since (5 samples, 0.01%)std::sys::unix::time::inner::Instant::checked_sub_instant (5 samples, 0.01%)std::sys::unix::time::Timespec::sub_timespec (5 samples, 0.01%)std::time::Instant::elapsed (28 samples, 0.07%)std::time::Instant::now (22 samples, 0.06%)std::sys::unix::time::inner::Instant::now (22 samples, 0.06%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (22 samples, 0.06%)clock_gettime (21 samples, 0.05%)[[vdso]] (21 samples, 0.05%)[[vdso]] (17 samples, 0.04%)<T as core::convert::TryInto<U>>::try_into (386 samples, 0.99%)core::array::_<impl core::convert::TryFrom<&[T]> for [T: N]>::try_from (386 samples, 0.99%)core::result::Result<T,E>::map (386 samples, 0.99%)<std::sync::mpmc::zero::ZeroToken as core::default::Default>::default (4 samples, 0.01%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (61 samples, 0.16%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (61 samples, 0.16%)std::sys::unix::locks::futex_mutex::Mutex::unlock (58 samples, 0.15%)core::sync::atomic::AtomicU32::swap (56 samples, 0.14%)core::sync::atomic::atomic_swap (56 samples, 0.14%)std::sync::mutex::Mutex<T>::lock (67 samples, 0.17%)std::sys::unix::locks::futex_mutex::Mutex::lock (66 samples, 0.17%)core::sync::atomic::AtomicU32::compare_exchange (65 samples, 0.17%)core::sync::atomic::atomic_compare_exchange (65 samples, 0.17%)EVP_CIPHER_CTX_get_block_size (4 samples, 0.01%)<zssp::crypto_impl::openssl::Aes256OpenSSLDec as zssp::crypto::aes::Aes256Dec>::decrypt_in_place (362 samples, 0.93%)zssp::crypto_impl::openssl::CipherCtx::update (228 samples, 0.58%)EVP_DecryptUpdate (221 samples, 0.57%)[libcrypto.so.3] (203 samples, 0.52%)[libcrypto.so.3] (148 samples, 0.38%)[libcrypto.so.3] (137 samples, 0.35%)__rust_probestack (8 samples, 0.02%)alloc::sync::Weak<T>::upgrade::_{{closure}} (5 samples, 0.01%)core::sync::atomic::AtomicUsize::compare_exchange_weak (54 samples, 0.14%)core::sync::atomic::atomic_compare_exchange_weak (54 samples, 0.14%)alloc::sync::Weak<T>::upgrade (78 samples, 0.20%)core::sync::atomic::AtomicUsize::fetch_update (72 samples, 0.18%)core::sync::atomic::AtomicUsize::load (9 samples, 0.02%)core::sync::atomic::atomic_load (9 samples, 0.02%)core::option::Option<T>::map (82 samples, 0.21%)zssp::zssp::Context<Crypto>::receive::_{{closure}} (82 samples, 0.21%)zssp::zssp::Context<Crypto>::receive (4 samples, 0.01%)core::ptr::drop_in_place<alloc::sync::Arc<zssp::zeta::Session<benchmark::TestApplication>>> (59 samples, 0.15%)<alloc::sync::Arc<T> as core::ops::drop::Drop>::drop (59 samples, 0.15%)core::sync::atomic::AtomicUsize::fetch_sub (54 samples, 0.14%)core::sync::atomic::atomic_sub (54 samples, 0.14%)[libc.so.6] (33 samples, 0.08%)__entry_text_start (6 samples, 0.02%)futex_unqueue (8 samples, 0.02%)update_curr (13 samples, 0.03%)cpuacct_charge (4 samples, 0.01%)dequeue_task (24 samples, 0.06%)dequeue_task_fair (24 samples, 0.06%)dequeue_entity (24 samples, 0.06%)finish_task_switch.isra.0 (12 samples, 0.03%)raw_spin_rq_unlock (7 samples, 0.02%)prepare_task_switch (14 samples, 0.04%)__perf_event_task_sched_out (4 samples, 0.01%)psi_group_change (16 samples, 0.04%)psi_task_switch (31 samples, 0.08%)sched_clock_cpu (6 samples, 0.02%)__schedule (97 samples, 0.25%)futex_wait_queue (109 samples, 0.28%)schedule (102 samples, 0.26%)__get_user_nocheck_4 (11 samples, 0.03%)futex_q_lock (4 samples, 0.01%)futex_q_unlock (4 samples, 0.01%)futex_wait_setup (28 samples, 0.07%)__x64_sys_futex (156 samples, 0.40%)do_futex (154 samples, 0.39%)futex_wait (153 samples, 0.39%)__rseq_handle_notify_resume (10 samples, 0.03%)exit_to_user_mode_loop (17 samples, 0.04%)do_syscall_64 (183 samples, 0.47%)syscall_exit_to_user_mode (22 samples, 0.06%)exit_to_user_mode_prepare (22 samples, 0.06%)__lll_lock_wait_private (227 samples, 0.58%)entry_SYSCALL_64_after_hwframe (185 samples, 0.47%)[libc.so.6] (583 samples, 1.49%)__entry_text_start (7 samples, 0.02%)__x64_sys_futex (6 samples, 0.02%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)raw_irqentry_exit_cond_resched (5 samples, 0.01%)preempt_schedule_irq (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)_raw_spin_lock (8 samples, 0.02%)futex_hash (4 samples, 0.01%)_raw_spin_lock (18 samples, 0.05%)native_queued_spin_lock_slowpath (18 samples, 0.05%)get_futex_key (4 samples, 0.01%)futex_wake (53 samples, 0.14%)__x64_sys_futex (71 samples, 0.18%)do_futex (68 samples, 0.17%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)do_syscall_64 (86 samples, 0.22%)syscall_exit_to_user_mode (14 samples, 0.04%)exit_to_user_mode_prepare (13 samples, 0.03%)entry_SYSCALL_64_after_hwframe (94 samples, 0.24%)alloc::alloc::dealloc (730 samples, 1.87%)a..cfree (727 samples, 1.86%)c..__lll_lock_wake_private (106 samples, 0.27%)<alloc::alloc::Global as core::alloc::Allocator>::deallocate (732 samples, 1.87%)<..core::ptr::drop_in_place<arrayvec::arrayvec::ArrayVec<alloc::vec::Vec<u8>,48_usize>> (738 samples, 1.89%)c..<arrayvec::arrayvec::ArrayVec<T,_> as core::ops::drop::Drop>::drop (738 samples, 1.89%)<..arrayvec::arrayvec::ArrayVec<T,_>::clear (738 samples, 1.89%)a..arrayvec::arrayvec_impl::ArrayVecImpl::clear (738 samples, 1.89%)a..arrayvec::arrayvec_impl::ArrayVecImpl::truncate (738 samples, 1.89%)a..core::ptr::drop_in_place<[alloc::vec::Vec<u8>]> (736 samples, 1.88%)c..core::ptr::drop_in_place<alloc::vec::Vec<u8>> (736 samples, 1.88%)c..core::ptr::drop_in_place<alloc::raw_vec::RawVec<u8>> (736 samples, 1.88%)c..<alloc::raw_vec::RawVec<T,A> as core::ops::drop::Drop>::drop (736 samples, 1.88%)<..alloc::raw_vec::RawVec<T,A>::current_memory (4 samples, 0.01%)std::sync::poison::Flag::done (9 samples, 0.02%)std::thread::panicking (9 samples, 0.02%)std::panicking::panicking (9 samples, 0.02%)std::panicking::panic_count::count_is_zero (9 samples, 0.02%)core::sync::atomic::AtomicUsize::load (9 samples, 0.02%)core::sync::atomic::atomic_load (9 samples, 0.02%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (76 samples, 0.19%)std::sys::unix::locks::futex_mutex::Mutex::unlock (67 samples, 0.17%)core::sync::atomic::AtomicU32::swap (65 samples, 0.17%)core::sync::atomic::atomic_swap (65 samples, 0.17%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::fragged::Fragged<alloc::vec::Vec<u8>,48_usize>>> (77 samples, 0.20%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<std::collections::hash::map::HashMap<core::num::nonzero::NonZeroU32,alloc::sync::Weak<zssp::zeta::Session<benchmark::TestApplication>>>>> (77 samples, 0.20%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (77 samples, 0.20%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (77 samples, 0.20%)core::sync::atomic::AtomicU32::fetch_sub (70 samples, 0.18%)core::sync::atomic::atomic_sub (70 samples, 0.18%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<zssp::zeta::MutableState<benchmark::TestApplication>>> (51 samples, 0.13%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (51 samples, 0.13%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (51 samples, 0.13%)core::sync::atomic::AtomicU32::fetch_sub (48 samples, 0.12%)core::sync::atomic::atomic_sub (48 samples, 0.12%)<core::hash::sip::Sip13Rounds as core::hash::sip::Sip>::c_rounds (7 samples, 0.02%)core::num::<impl u64>::wrapping_add (6 samples, 0.02%)core::num::<impl u64>::rotate_left (14 samples, 0.04%)<std::collections::hash::map::DefaultHasher as core::hash::Hasher>::finish (51 samples, 0.13%)<core::hash::sip::SipHasher13 as core::hash::Hasher>::finish (51 samples, 0.13%)<core::hash::sip::Hasher<S> as core::hash::Hasher>::finish (51 samples, 0.13%)<core::hash::sip::Sip13Rounds as core::hash::sip::Sip>::d_rounds (34 samples, 0.09%)core::num::<impl u64>::wrapping_add (13 samples, 0.03%)hashbrown::map::make_hash (70 samples, 0.18%)core::hash::BuildHasher::hash_one (70 samples, 0.18%)core::hash::impls::<impl core::hash::Hash for &T>::hash (14 samples, 0.04%)<core::num::nonzero::NonZeroU32 as core::hash::Hash>::hash (14 samples, 0.04%)core::hash::impls::<impl core::hash::Hash for u32>::hash (14 samples, 0.04%)core::hash::Hasher::write_u32 (14 samples, 0.04%)<std::collections::hash::map::DefaultHasher as core::hash::Hasher>::write (14 samples, 0.04%)<core::hash::sip::SipHasher13 as core::hash::Hasher>::write (14 samples, 0.04%)<core::hash::sip::Hasher<S> as core::hash::Hasher>::write (14 samples, 0.04%)core::hash::sip::u8to64_le (11 samples, 0.03%)<hashbrown::raw::bitmask::BitMaskIter as core::iter::traits::iterator::Iterator>::next (4 samples, 0.01%)hashbrown::raw::RawTable<T,A>::find::_{{closure}} (8 samples, 0.02%)hashbrown::map::equivalent_key::_{{closure}} (8 samples, 0.02%)<core::num::nonzero::NonZeroU32 as core::cmp::PartialEq>::eq (8 samples, 0.02%)hashbrown::raw::h2 (9 samples, 0.02%)hashbrown::map::HashMap<K,V,S,A>::get_inner (109 samples, 0.28%)hashbrown::raw::RawTable<T,A>::get (39 samples, 0.10%)hashbrown::raw::RawTable<T,A>::find (39 samples, 0.10%)hashbrown::raw::RawTableInner<A>::find_inner (39 samples, 0.10%)hashbrown::raw::sse2::Group::load (13 samples, 0.03%)core::core_arch::x86::sse2::_mm_loadu_si128 (13 samples, 0.03%)core::intrinsics::copy_nonoverlapping (13 samples, 0.03%)std::collections::hash::map::HashMap<K,V,S>::get (113 samples, 0.29%)hashbrown::map::HashMap<K,V,S,A>::get (113 samples, 0.29%)zssp::zssp::Context<Crypto>::receive (4 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (6 samples, 0.02%)std::sync::mutex::MutexGuard<T>::new (14 samples, 0.04%)std::sync::poison::Flag::guard (8 samples, 0.02%)std::sync::mutex::Mutex<T>::lock (47 samples, 0.12%)std::sys::unix::locks::futex_mutex::Mutex::lock (33 samples, 0.08%)core::sync::atomic::AtomicU32::compare_exchange (32 samples, 0.08%)core::sync::atomic::atomic_compare_exchange (32 samples, 0.08%)core::sync::atomic::AtomicU32::compare_exchange_weak (117 samples, 0.30%)core::sync::atomic::atomic_compare_exchange_weak (117 samples, 0.30%)std::sync::rwlock::RwLock<T>::read (140 samples, 0.36%)std::sys::unix::locks::futex_rwlock::RwLock::read (140 samples, 0.36%)std::sys::unix::locks::futex_rwlock::is_read_lockable (6 samples, 0.02%)zssp::antireplay::Window<_,_>::check (7 samples, 0.02%)core::sync::atomic::AtomicU64::load (6 samples, 0.02%)core::sync::atomic::atomic_load (6 samples, 0.02%)arrayvec::arrayvec::ArrayVec<T,_>::push (16 samples, 0.04%)arrayvec::arrayvec_impl::ArrayVecImpl::push (16 samples, 0.04%)arrayvec::arrayvec_impl::ArrayVecImpl::try_push (16 samples, 0.04%)arrayvec::arrayvec_impl::ArrayVecImpl::push_unchecked (14 samples, 0.04%)core::ptr::write (12 samples, 0.03%)core::mem::maybe_uninit::MaybeUninit<T>::write (4 samples, 0.01%)zssp::fragged::Fragged<Fragment,_>::assemble (51 samples, 0.13%)<T as core::convert::TryInto<U>>::try_into (28 samples, 0.07%)core::array::_<impl core::convert::TryFrom<&[T]> for [T: N]>::try_from (28 samples, 0.07%)core::result::Result<T,E>::map (28 samples, 0.07%)zssp::zeta::from_nonce (33 samples, 0.08%)<core::slice::iter::IterMut<T> as core::iter::traits::iterator::Iterator>::next (6 samples, 0.02%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (76 samples, 0.19%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (76 samples, 0.19%)core::sync::atomic::AtomicU32::fetch_sub (75 samples, 0.19%)core::sync::atomic::atomic_sub (75 samples, 0.19%)CRYPTO_gcm128_decrypt (231 samples, 0.59%)[libcrypto.so.3] (86 samples, 0.22%)CRYPTO_gcm128_decrypt_ctr32 (440 samples, 1.13%)[libcrypto.so.3] (352 samples, 0.90%)[libcrypto.so.3] (27 samples, 0.07%)CRYPTO_gcm128_setiv (23 samples, 0.06%)[libcrypto.so.3] (20 samples, 0.05%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::decrypt_in_place (4,270 samples, 10.93%)<zssp::crypto_im..zssp::crypto_impl::openssl::CipherCtx::update (4,270 samples, 10.93%)zssp::crypto_imp..EVP_DecryptUpdate (4,266 samples, 10.92%)EVP_DecryptUpdate[libcrypto.so.3] (4,171 samples, 10.67%)[libcrypto.so.3][libcrypto.so.3] (4,167 samples, 10.66%)[libcrypto.so.3][libcrypto.so.3] (4,144 samples, 10.60%)[libcrypto.so.3][libcrypto.so.3] (3,418 samples, 8.75%)[libcrypto.s..[libcrypto.so.3] (3,257 samples, 8.33%)[libcrypto.s..asm_sysvec_reschedule_ipi (17 samples, 0.04%)sysvec_reschedule_ipi (16 samples, 0.04%)irqentry_exit (16 samples, 0.04%)irqentry_exit_to_user_mode (16 samples, 0.04%)exit_to_user_mode_prepare (16 samples, 0.04%)exit_to_user_mode_loop (16 samples, 0.04%)schedule (15 samples, 0.04%)__schedule (15 samples, 0.04%)finish_task_switch.isra.0 (15 samples, 0.04%)__perf_event_task_sched_in (15 samples, 0.04%)perf_ctx_enable (15 samples, 0.04%)CRYPTO_clear_free (92 samples, 0.24%)OPENSSL_cleanse (92 samples, 0.24%)EVP_CIPHER_free (27 samples, 0.07%)cfree (42 samples, 0.11%)[libc.so.6] (7 samples, 0.02%)EVP_CIPHER_CTX_free (184 samples, 0.47%)EVP_CIPHER_CTX_reset (180 samples, 0.46%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::AesGcmOpenSSLDec> (210 samples, 0.54%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::CipherCtx> (210 samples, 0.54%)<zssp::crypto_impl::openssl::CipherCtx as core::ops::drop::Drop>::drop (210 samples, 0.54%)cfree (25 samples, 0.06%)[libc.so.6] (15 samples, 0.04%)CRYPTO_gcm128_finish (29 samples, 0.07%)[libcrypto.so.3] (18 samples, 0.05%)[libcrypto.so.3] (33 samples, 0.08%)zssp::crypto_impl::openssl::CipherCtx::finalize (46 samples, 0.12%)EVP_DecryptFinal_ex (45 samples, 0.12%)[libcrypto.so.3] (38 samples, 0.10%)[libcrypto.so.3] (37 samples, 0.09%)OSSL_PARAM_get_octet_string (6 samples, 0.02%)[libc.so.6] (17 samples, 0.04%)OSSL_PARAM_locate (26 samples, 0.07%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::finish (333 samples, 0.85%)zssp::crypto_impl::openssl::CipherCtx::set_tag (77 samples, 0.20%)EVP_CIPHER_CTX_ctrl (77 samples, 0.20%)[libcrypto.so.3] (39 samples, 0.10%)OSSL_PARAM_locate_const (4 samples, 0.01%)[libc.so.6] (15 samples, 0.04%)OSSL_PARAM_locate (28 samples, 0.07%)EVP_CIPHER_CTX_set_padding (64 samples, 0.16%)[libcrypto.so.3] (42 samples, 0.11%)[libc.so.6] (25 samples, 0.06%)OSSL_PARAM_locate (48 samples, 0.12%)strcmp@plt (7 samples, 0.02%)EVP_CIPHER_CTX_get_iv_length (81 samples, 0.21%)[libcrypto.so.3] (63 samples, 0.16%)[libc.so.6] (20 samples, 0.05%)OSSL_PARAM_locate (41 samples, 0.10%)EVP_CIPHER_CTX_get_key_length (60 samples, 0.15%)[libcrypto.so.3] (53 samples, 0.14%)pthread_rwlock_rdlock (159 samples, 0.41%)CRYPTO_THREAD_read_lock (163 samples, 0.42%)pthread_rwlock_unlock (79 samples, 0.20%)CRYPTO_THREAD_unlock (86 samples, 0.22%)pthread_rwlock_unlock@plt (5 samples, 0.01%)EVP_CIPHER_up_ref (42 samples, 0.11%)OPENSSL_LH_retrieve (70 samples, 0.18%)[libcrypto.so.3] (57 samples, 0.15%)[libcrypto.so.3] (6 samples, 0.02%)pthread_rwlock_rdlock (43 samples, 0.11%)CRYPTO_THREAD_read_lock (47 samples, 0.12%)pthread_rwlock_unlock (66 samples, 0.17%)CRYPTO_THREAD_unlock (69 samples, 0.18%)CRYPTO_strndup (13 samples, 0.03%)OPENSSL_strcasecmp (38 samples, 0.10%)OPENSSL_LH_retrieve (162 samples, 0.41%)[libcrypto.so.3] (151 samples, 0.39%)[libcrypto.so.3] (36 samples, 0.09%)cfree (12 samples, 0.03%)[libc.so.6] (4 samples, 0.01%)[libcrypto.so.3] (310 samples, 0.79%)pthread_getspecific (7 samples, 0.02%)EVP_CIPHER_fetch (702 samples, 1.80%)E..[libcrypto.so.3] (699 samples, 1.79%)[..[libcrypto.so.3] (690 samples, 1.77%)EVP_CIPHER_free (18 samples, 0.05%)EVP_CIPHER_up_ref (27 samples, 0.07%)[libc.so.6] (10 samples, 0.03%)malloc (9 samples, 0.02%)CRYPTO_zalloc (22 samples, 0.06%)OPENSSL_init_crypto (4 samples, 0.01%)CRYPTO_gcm128_init (155 samples, 0.40%)[libcrypto.so.3] (126 samples, 0.32%)[libcrypto.so.3] (188 samples, 0.48%)[libcrypto.so.3] (32 samples, 0.08%)zssp::crypto_impl::openssl::CipherCtx::cipher_init (1,141 samples, 2.92%)zs..EVP_CipherInit_ex (1,141 samples, 2.92%)EV..[libcrypto.so.3] (1,141 samples, 2.92%)[l..[libcrypto.so.3] (224 samples, 0.57%)malloc (12 samples, 0.03%)CRYPTO_zalloc (17 samples, 0.04%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_dec (1,226 samples, 3.14%)<zs..zssp::crypto_impl::openssl::CipherCtx::new (20 samples, 0.05%)core::slice::index::<impl core::ops::index::Index<I> for [T]>::index (6 samples, 0.02%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::index (6 samples, 0.02%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::get_unchecked (5 samples, 0.01%)<core::ops::range::Range<usize> as core::slice::index::SliceIndex<[T]>>::get_unchecked (5 samples, 0.01%)core::ptr::const_ptr::<impl *const T>::add (4 samples, 0.01%)core::ptr::const_ptr::<impl *const T>::offset (4 samples, 0.01%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::index_mut (4 samples, 0.01%)core::slice::index::<impl core::ops::index::IndexMut<I> for [T]>::index_mut (5 samples, 0.01%)std::io::impls::<impl std::io::Write for &mut W>::write (232 samples, 0.59%)<arrayvec::arrayvec::ArrayVec<u8,_> as std::io::Write>::write (232 samples, 0.59%)arrayvec::arrayvec::ArrayVec<T,_>::try_extend_from_slice (230 samples, 0.59%)core::intrinsics::copy_nonoverlapping (228 samples, 0.58%)[libc.so.6] (227 samples, 0.58%)zssp::antireplay::Window<_,_>::update (27 samples, 0.07%)core::sync::atomic::AtomicU64::fetch_max (26 samples, 0.07%)core::sync::atomic::atomic_umax (26 samples, 0.07%)zssp::zeta::receive_payload_in_place (6,190 samples, 15.84%)zssp::zeta::receive_payl..zssp::zssp::Context<Crypto>::receive (8,570 samples, 21.93%)zssp::zssp::Context<Crypto>::receivezssp::zssp::parse_fragment_header (65 samples, 0.17%)core::slice::<impl [T]>::copy_from_slice (4 samples, 0.01%)core::intrinsics::copy_nonoverlapping (4 samples, 0.01%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (56 samples, 0.14%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (56 samples, 0.14%)std::sys::unix::locks::futex_mutex::Mutex::unlock (55 samples, 0.14%)core::sync::atomic::AtomicU32::swap (52 samples, 0.13%)core::sync::atomic::atomic_swap (52 samples, 0.13%)std::sync::mutex::Mutex<T>::lock (64 samples, 0.16%)std::sys::unix::locks::futex_mutex::Mutex::lock (61 samples, 0.16%)core::sync::atomic::AtomicU32::compare_exchange (55 samples, 0.14%)core::sync::atomic::atomic_compare_exchange (55 samples, 0.14%)<zssp::crypto_impl::openssl::Aes256OpenSSLEnc as zssp::crypto::aes::Aes256Enc>::encrypt_in_place (314 samples, 0.80%)zssp::crypto_impl::openssl::CipherCtx::update (188 samples, 0.48%)EVP_EncryptUpdate (183 samples, 0.47%)[libcrypto.so.3] (148 samples, 0.38%)[libcrypto.so.3] (106 samples, 0.27%)[libcrypto.so.3] (96 samples, 0.25%)CRYPTO_gcm128_encrypt (250 samples, 0.64%)[libcrypto.so.3] (143 samples, 0.37%)CRYPTO_gcm128_encrypt_ctr32 (415 samples, 1.06%)[libcrypto.so.3] (319 samples, 0.82%)[libcrypto.so.3] (28 samples, 0.07%)[libcrypto.so.3] (19 samples, 0.05%)CRYPTO_gcm128_setiv (25 samples, 0.06%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)perf_ctx_enable (18 samples, 0.05%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::encrypt (3,317 samples, 8.49%)<zssp::crypt..zssp::crypto_impl::openssl::CipherCtx::update (3,311 samples, 8.47%)zssp::crypto..EVP_EncryptUpdate (3,309 samples, 8.47%)EVP_EncryptU..[libcrypto.so.3] (3,257 samples, 8.33%)[libcrypto.s..[libcrypto.so.3] (3,255 samples, 8.33%)[libcrypto.s..[libcrypto.so.3] (3,235 samples, 8.28%)[libcrypto.s..[libcrypto.so.3] (2,506 samples, 6.41%)[libcryp..[libcrypto.so.3] (2,299 samples, 5.88%)[libcry..asm_sysvec_reschedule_ipi (20 samples, 0.05%)sysvec_reschedule_ipi (20 samples, 0.05%)irqentry_exit (20 samples, 0.05%)irqentry_exit_to_user_mode (20 samples, 0.05%)exit_to_user_mode_prepare (20 samples, 0.05%)exit_to_user_mode_loop (20 samples, 0.05%)schedule (19 samples, 0.05%)__schedule (19 samples, 0.05%)finish_task_switch.isra.0 (19 samples, 0.05%)__perf_event_task_sched_in (19 samples, 0.05%)CRYPTO_clear_free (107 samples, 0.27%)OPENSSL_cleanse (103 samples, 0.26%)EVP_CIPHER_free (30 samples, 0.08%)EVP_CIPHER_CTX_free (206 samples, 0.53%)EVP_CIPHER_CTX_reset (205 samples, 0.52%)cfree (52 samples, 0.13%)[libc.so.6] (11 samples, 0.03%)cfree (11 samples, 0.03%)[libc.so.6] (5 samples, 0.01%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc> (220 samples, 0.56%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::CipherCtx> (220 samples, 0.56%)<zssp::crypto_impl::openssl::CipherCtx as core::ops::drop::Drop>::drop (220 samples, 0.56%)[libcrypto.so.3] (15 samples, 0.04%)zssp::crypto_impl::openssl::CipherCtx::finalize (47 samples, 0.12%)EVP_EncryptFinal_ex (46 samples, 0.12%)[libcrypto.so.3] (33 samples, 0.08%)[libcrypto.so.3] (33 samples, 0.08%)[libcrypto.so.3] (29 samples, 0.07%)CRYPTO_gcm128_tag (29 samples, 0.07%)CRYPTO_gcm128_finish (22 samples, 0.06%)[libc.so.6] (32 samples, 0.08%)OSSL_PARAM_locate (53 samples, 0.14%)strcmp@plt (5 samples, 0.01%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::finish (358 samples, 0.92%)zssp::crypto_impl::openssl::CipherCtx::get_tag (91 samples, 0.23%)EVP_CIPHER_CTX_ctrl (91 samples, 0.23%)[libcrypto.so.3] (68 samples, 0.17%)OSSL_PARAM_set_octet_string (5 samples, 0.01%)[libc.so.6] (12 samples, 0.03%)EVP_CIPHER_CTX_set_padding (64 samples, 0.16%)[libcrypto.so.3] (38 samples, 0.10%)OSSL_PARAM_locate (32 samples, 0.08%)strcmp@plt (4 samples, 0.01%)[libc.so.6] (17 samples, 0.04%)OSSL_PARAM_locate (40 samples, 0.10%)strcmp@plt (5 samples, 0.01%)EVP_CIPHER_CTX_get_iv_length (79 samples, 0.20%)[libcrypto.so.3] (55 samples, 0.14%)[libc.so.6] (20 samples, 0.05%)OSSL_PARAM_locate (37 samples, 0.09%)EVP_CIPHER_CTX_get_key_length (63 samples, 0.16%)[libcrypto.so.3] (54 samples, 0.14%)pthread_rwlock_rdlock (130 samples, 0.33%)CRYPTO_THREAD_read_lock (136 samples, 0.35%)pthread_rwlock_unlock (73 samples, 0.19%)CRYPTO_THREAD_unlock (82 samples, 0.21%)pthread_rwlock_unlock@plt (5 samples, 0.01%)EVP_CIPHER_up_ref (24 samples, 0.06%)OPENSSL_LH_retrieve (75 samples, 0.19%)[libcrypto.so.3] (56 samples, 0.14%)[libcrypto.so.3] (5 samples, 0.01%)pthread_rwlock_rdlock (38 samples, 0.10%)CRYPTO_THREAD_read_lock (40 samples, 0.10%)pthread_rwlock_unlock (43 samples, 0.11%)CRYPTO_THREAD_unlock (45 samples, 0.12%)OPENSSL_strnlen (5 samples, 0.01%)CRYPTO_strndup (10 samples, 0.03%)malloc (5 samples, 0.01%)OPENSSL_strcasecmp (41 samples, 0.10%)OPENSSL_LH_retrieve (132 samples, 0.34%)[libcrypto.so.3] (118 samples, 0.30%)[libcrypto.so.3] (29 samples, 0.07%)cfree (19 samples, 0.05%)[libc.so.6] (7 samples, 0.02%)[libcrypto.so.3] (264 samples, 0.68%)EVP_CIPHER_fetch (603 samples, 1.54%)[libcrypto.so.3] (600 samples, 1.54%)[libcrypto.so.3] (594 samples, 1.52%)EVP_CIPHER_free (17 samples, 0.04%)EVP_CIPHER_up_ref (20 samples, 0.05%)OBJ_nid2sn (6 samples, 0.02%)malloc (8 samples, 0.02%)CRYPTO_zalloc (14 samples, 0.04%)OPENSSL_init_crypto (5 samples, 0.01%)CRYPTO_gcm128_init (161 samples, 0.41%)[libcrypto.so.3] (132 samples, 0.34%)[libcrypto.so.3] (201 samples, 0.51%)[libcrypto.so.3] (39 samples, 0.10%)zssp::crypto_impl::openssl::CipherCtx::cipher_init (1,042 samples, 2.67%)zs..EVP_CipherInit_ex (1,042 samples, 2.67%)EV..[libcrypto.so.3] (1,042 samples, 2.67%)[l..[libcrypto.so.3] (232 samples, 0.59%)[libc.so.6] (4 samples, 0.01%)malloc (9 samples, 0.02%)CRYPTO_zalloc (15 samples, 0.04%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_enc (1,125 samples, 2.88%)<z..zssp::crypto_impl::openssl::CipherCtx::new (16 samples, 0.04%)__rdl_alloc (9 samples, 0.02%)std::sys::unix::alloc::<impl core::alloc::global::GlobalAlloc for std::alloc::System>::alloc (9 samples, 0.02%)[libc.so.6] (54 samples, 0.14%)[libc.so.6] (925 samples, 2.37%)[l..__entry_text_start (4 samples, 0.01%)__get_user_nocheck_4 (5 samples, 0.01%)futex_q_lock (5 samples, 0.01%)futex_q_unlock (6 samples, 0.02%)futex_wait (24 samples, 0.06%)futex_wait_setup (19 samples, 0.05%)__x64_sys_futex (26 samples, 0.07%)do_futex (26 samples, 0.07%)do_syscall_64 (31 samples, 0.08%)syscall_exit_to_user_mode (4 samples, 0.01%)__lll_lock_wait_private (53 samples, 0.14%)entry_SYSCALL_64_after_hwframe (33 samples, 0.08%)__entry_text_start (4 samples, 0.01%)_raw_spin_lock (4 samples, 0.01%)futex_hash (4 samples, 0.01%)_raw_spin_lock (4 samples, 0.01%)native_queued_spin_lock_slowpath (4 samples, 0.01%)futex_wake_mark (13 samples, 0.03%)preempt_schedule_thunk (5 samples, 0.01%)preempt_schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_pmu_nop_void (5 samples, 0.01%)__smp_call_single_queue (12 samples, 0.03%)native_send_call_func_single_ipi (9 samples, 0.02%)x2apic_send_IPI (9 samples, 0.02%)native_write_msr (6 samples, 0.02%)llist_add_batch (17 samples, 0.04%)do_futex (136 samples, 0.35%)futex_wake (128 samples, 0.33%)wake_up_q (75 samples, 0.19%)try_to_wake_up (66 samples, 0.17%)ttwu_queue_wakelist (36 samples, 0.09%)__x64_sys_futex (137 samples, 0.35%)do_syscall_64 (141 samples, 0.36%)entry_SYSCALL_64_after_hwframe (144 samples, 0.37%)alloc::vec::Vec<T,A>::with_capacity_in (1,803 samples, 4.61%)alloc..alloc::raw_vec::RawVec<T,A>::with_capacity_in (1,803 samples, 4.61%)alloc..alloc::raw_vec::RawVec<T,A>::allocate_in (1,803 samples, 4.61%)alloc..<alloc::alloc::Global as core::alloc::Allocator>::allocate (1,801 samples, 4.61%)<allo..alloc::alloc::Global::alloc_impl (1,801 samples, 4.61%)alloc..alloc::alloc::alloc (1,801 samples, 4.61%)alloc..malloc (1,789 samples, 4.58%)malloc__lll_lock_wake_private (156 samples, 0.40%)alloc::slice::<impl [T]>::to_vec (2,000 samples, 5.12%)alloc:..alloc::slice::<impl [T]>::to_vec_in (2,000 samples, 5.12%)alloc:..alloc::slice::hack::to_vec (2,000 samples, 5.12%)alloc:..<T as alloc::slice::hack::ConvertVec>::to_vec (2,000 samples, 5.12%)<T as ..core::ptr::const_ptr::<impl *const T>::copy_to_nonoverlapping (197 samples, 0.50%)core::intrinsics::copy_nonoverlapping (197 samples, 0.50%)[libc.so.6] (194 samples, 0.50%)core::result::Result<T,E>::is_ok (4 samples, 0.01%)<std::sync::mpmc::select::Token as core::default::Default>::default (4 samples, 0.01%)std::sync::mpmc::array::Channel<T>::send (4 samples, 0.01%)core::sync::atomic::AtomicUsize::compare_exchange_weak (66 samples, 0.17%)core::sync::atomic::atomic_compare_exchange_weak (66 samples, 0.17%)std::sync::mpmc::array::Channel<T>::start_send (102 samples, 0.26%)core::sync::atomic::AtomicUsize::load (11 samples, 0.03%)core::sync::atomic::atomic_load (11 samples, 0.03%)core::sync::atomic::AtomicBool::load (6 samples, 0.02%)core::sync::atomic::atomic_load (5 samples, 0.01%)futex_wake_mark (8 samples, 0.02%)__smp_call_single_queue (7 samples, 0.02%)native_send_call_func_single_ipi (6 samples, 0.02%)x2apic_send_IPI (6 samples, 0.02%)native_write_msr (4 samples, 0.01%)llist_add_batch (7 samples, 0.02%)futex_wake (71 samples, 0.18%)wake_up_q (44 samples, 0.11%)try_to_wake_up (43 samples, 0.11%)ttwu_queue_wakelist (24 samples, 0.06%)__x64_sys_futex (75 samples, 0.19%)do_futex (75 samples, 0.19%)entry_SYSCALL_64_after_hwframe (76 samples, 0.19%)do_syscall_64 (76 samples, 0.19%)<core::slice::iter::Iter<T> as core::iter::traits::iterator::Iterator>::position (88 samples, 0.23%)std::sync::mpmc::waker::Waker::try_select::_{{closure}} (88 samples, 0.23%)std::sync::mpmc::context::Context::unpark (84 samples, 0.21%)std::thread::Thread::unpark (84 samples, 0.21%)std::sys_common::thread_parking::futex::Parker::unpark (84 samples, 0.21%)std::sys::unix::futex::futex_wake (81 samples, 0.21%)syscall (80 samples, 0.20%)std::sync::mpmc::waker::Waker::try_select (89 samples, 0.23%)std::sync::mpmc::array::Channel<T>::write (112 samples, 0.29%)std::sync::mpmc::waker::SyncWaker::notify (105 samples, 0.27%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_pmu_nop_void (5 samples, 0.01%)futex_wait_queue (6 samples, 0.02%)schedule (6 samples, 0.02%)__schedule (6 samples, 0.02%)std::sync::mpmc::context::Context::wait_until (7 samples, 0.02%)std::thread::park (7 samples, 0.02%)std::sys_common::thread_parking::futex::Parker::park (7 samples, 0.02%)std::sys::unix::futex::futex_wait (7 samples, 0.02%)syscall (7 samples, 0.02%)entry_SYSCALL_64_after_hwframe (7 samples, 0.02%)do_syscall_64 (7 samples, 0.02%)__x64_sys_futex (7 samples, 0.02%)do_futex (7 samples, 0.02%)futex_wait (7 samples, 0.02%)std::sync::mpmc::context::Context::with (8 samples, 0.02%)std::thread::local::LocalKey<T>::try_with (8 samples, 0.02%)std::sync::mpmc::context::Context::with::_{{closure}} (8 samples, 0.02%)std::sync::mpmc::context::Context::with::_{{closure}} (8 samples, 0.02%)std::sync::mpmc::array::Channel<T>::send::_{{closure}} (8 samples, 0.02%)core::hint::spin_loop (14 samples, 0.04%)core::core_arch::x86::sse2::_mm_pause (14 samples, 0.04%)std::sync::mpmc::array::Channel<T>::send (251 samples, 0.64%)std::sync::mpmc::utils::Backoff::spin_light (15 samples, 0.04%)benchmark::bob_main::_{{closure}} (3,468 samples, 8.87%)benchmark::bo..std::sync::mpsc::SyncSender<T>::send (1,464 samples, 3.75%)std:..std::sync::mpmc::Sender<T>::send (1,462 samples, 3.74%)std:..core::iter::range::<impl core::iter::traits::iterator::Iterator for core::ops::range::Range<A>>::next (6 samples, 0.02%)<core::ops::range::Range<T> as core::iter::range::RangeIteratorImpl>::spec_next (6 samples, 0.02%)core::mem::drop (25 samples, 0.06%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<zssp::zeta::MutableState<benchmark::TestApplication>>> (25 samples, 0.06%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (25 samples, 0.06%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (25 samples, 0.06%)core::sync::atomic::AtomicU32::fetch_sub (24 samples, 0.06%)core::sync::atomic::atomic_sub (24 samples, 0.06%)core::slice::<impl [T]>::copy_from_slice (14 samples, 0.04%)core::intrinsics::copy_nonoverlapping (14 samples, 0.04%)core::sync::atomic::AtomicU32::compare_exchange_weak (20 samples, 0.05%)core::sync::atomic::atomic_compare_exchange_weak (20 samples, 0.05%)std::sync::rwlock::RwLock<T>::read (22 samples, 0.06%)std::sys::unix::locks::futex_rwlock::RwLock::read (22 samples, 0.06%)benchmark::alice_main (18,175 samples, 46.50%)benchmark::alice_mainzssp::zssp::Context<Crypto>::send (8,691 samples, 22.24%)zssp::zssp::Context<Crypto>::sendzssp::zeta::send_payload (8,691 samples, 22.24%)zssp::zeta::send_payloadzssp::zeta::get_counter (14 samples, 0.04%)core::sync::atomic::AtomicU64::fetch_add (11 samples, 0.03%)core::sync::atomic::atomic_add (11 samples, 0.03%)[libc.so.6] (15 samples, 0.04%)arrayvec::arrayvec::ArrayVec<T,_>::clear (4 samples, 0.01%)arrayvec::arrayvec_impl::ArrayVecImpl::clear (4 samples, 0.01%)arrayvec::arrayvec_impl::ArrayVecImpl::truncate (4 samples, 0.01%)core::ptr::drop_in_place<alloc::sync::Arc<zssp::zeta::Session<benchmark::TestApplication>>> (10 samples, 0.03%)<alloc::sync::Arc<T> as core::ops::drop::Drop>::drop (10 samples, 0.03%)core::sync::atomic::AtomicUsize::fetch_sub (10 samples, 0.03%)core::sync::atomic::atomic_sub (10 samples, 0.03%)core::time::Duration::as_millis (5 samples, 0.01%)_ZN3std4sync4mpmc5array16Channel$LT$T$GT$10start_recv17h7800ca29c64cb868E.llvm.12455019271255371362 (4 samples, 0.01%)std::sync::mpmc::array::Channel<T>::read (6 samples, 0.02%)core::sync::atomic::AtomicUsize::compare_exchange_weak (65 samples, 0.17%)core::sync::atomic::atomic_compare_exchange_weak (65 samples, 0.17%)core::sync::atomic::AtomicUsize::load (84 samples, 0.21%)core::sync::atomic::atomic_load (84 samples, 0.21%)std::sync::mpmc::array::Channel<T>::start_recv (190 samples, 0.49%)core::sync::atomic::fence (4 samples, 0.01%)core::sync::atomic::AtomicU32::swap (5 samples, 0.01%)core::sync::atomic::atomic_swap (5 samples, 0.01%)[[vdso]] (4 samples, 0.01%)core::option::Option<T>::and_then (5 samples, 0.01%)std::sys::unix::futex::futex_wait::_{{closure}} (5 samples, 0.01%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (5 samples, 0.01%)clock_gettime (5 samples, 0.01%)futex_setup_timer (5 samples, 0.01%)hrtimer_init_sleeper (4 samples, 0.01%)__hrtimer_init (4 samples, 0.01%)futex_unqueue (4 samples, 0.01%)hrtimer_sleeper_start_expires (6 samples, 0.02%)hrtimer_start_range_ns (6 samples, 0.02%)update_curr (5 samples, 0.01%)dequeue_entity (14 samples, 0.04%)update_load_avg (5 samples, 0.01%)dequeue_task (18 samples, 0.05%)dequeue_task_fair (18 samples, 0.05%)finish_task_switch.isra.0 (14 samples, 0.04%)raw_spin_rq_unlock (6 samples, 0.02%)pick_next_task (5 samples, 0.01%)prepare_task_switch (6 samples, 0.02%)__perf_event_task_sched_out (4 samples, 0.01%)psi_group_change (12 samples, 0.03%)record_times (5 samples, 0.01%)psi_task_switch (22 samples, 0.06%)__schedule (72 samples, 0.18%)futex_wait_queue (90 samples, 0.23%)schedule (81 samples, 0.21%)__get_user_nocheck_4 (6 samples, 0.02%)futex_q_lock (7 samples, 0.02%)futex_wait_setup (19 samples, 0.05%)hrtimer_cancel (5 samples, 0.01%)hrtimer_try_to_cancel (5 samples, 0.01%)do_futex (131 samples, 0.34%)futex_wait (130 samples, 0.33%)__x64_sys_futex (143 samples, 0.37%)get_timespec64 (5 samples, 0.01%)exit_to_user_mode_loop (11 samples, 0.03%)__rseq_handle_notify_resume (4 samples, 0.01%)std::sync::mpmc::context::Context::wait_until (178 samples, 0.46%)std::thread::park_timeout (177 samples, 0.45%)std::sys_common::thread_parking::futex::Parker::park_timeout (175 samples, 0.45%)std::sys::unix::futex::futex_wait (170 samples, 0.43%)syscall (164 samples, 0.42%)entry_SYSCALL_64_after_hwframe (159 samples, 0.41%)do_syscall_64 (159 samples, 0.41%)syscall_exit_to_user_mode (15 samples, 0.04%)exit_to_user_mode_prepare (15 samples, 0.04%)core::sync::atomic::AtomicBool::store (8 samples, 0.02%)core::sync::atomic::atomic_store (8 samples, 0.02%)std::sync::mpmc::waker::Waker::register (5 samples, 0.01%)std::sync::mpmc::waker::Waker::register_with_packet (5 samples, 0.01%)alloc::vec::Vec<T,A>::push (5 samples, 0.01%)core::ptr::write (5 samples, 0.01%)std::sync::mpmc::waker::SyncWaker::register (24 samples, 0.06%)std::sync::mutex::Mutex<T>::lock (8 samples, 0.02%)std::sys::unix::locks::futex_mutex::Mutex::lock (8 samples, 0.02%)core::sync::atomic::AtomicU32::compare_exchange (8 samples, 0.02%)core::sync::atomic::atomic_compare_exchange (8 samples, 0.02%)std::sync::mpmc::context::Context::with (207 samples, 0.53%)std::thread::local::LocalKey<T>::try_with (207 samples, 0.53%)std::sync::mpmc::context::Context::with::_{{closure}} (207 samples, 0.53%)std::sync::mpmc::context::Context::with::_{{closure}} (207 samples, 0.53%)std::sync::mpmc::array::Channel<T>::recv::_{{closure}} (207 samples, 0.53%)std::sync::mpmc::Receiver<T>::recv_deadline (431 samples, 1.10%)std::sync::mpmc::array::Channel<T>::recv (431 samples, 1.10%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (5 samples, 0.01%)clock_gettime (5 samples, 0.01%)[[vdso]] (5 samples, 0.01%)std::sync::mpmc::Receiver<T>::recv_timeout (4 samples, 0.01%)[[vdso]] (131 samples, 0.34%)[[vdso]] (88 samples, 0.23%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (146 samples, 0.37%)clock_gettime (140 samples, 0.36%)__vdso_clock_gettime (6 samples, 0.02%)std::sync::mpsc::Receiver<T>::recv_timeout (605 samples, 1.55%)std::sync::mpmc::Receiver<T>::recv_timeout (602 samples, 1.54%)std::time::SystemTime::checked_add (12 samples, 0.03%)std::sys::unix::time::SystemTime::checked_add_duration (12 samples, 0.03%)std::sys::unix::time::Timespec::checked_add_duration (12 samples, 0.03%)core::option::Option<T>::and_then (4 samples, 0.01%)core::cmp::impls::<impl core::cmp::PartialOrd<&B> for &A>::ge (6 samples, 0.02%)core::cmp::PartialOrd::ge (6 samples, 0.02%)<std::sys::unix::time::Timespec as core::cmp::PartialOrd>::partial_cmp (5 samples, 0.01%)std::time::Instant::duration_since (22 samples, 0.06%)std::time::Instant::checked_duration_since (21 samples, 0.05%)std::sys::unix::time::inner::Instant::checked_sub_instant (21 samples, 0.05%)std::sys::unix::time::Timespec::sub_timespec (21 samples, 0.05%)<std::time::Instant as core::ops::arith::Sub>::sub (27 samples, 0.07%)std::time::Instant::elapsed (5 samples, 0.01%)[[vdso]] (148 samples, 0.38%)[[vdso]] (107 samples, 0.27%)std::time::Instant::elapsed (192 samples, 0.49%)std::time::Instant::now (159 samples, 0.41%)std::sys::unix::time::inner::Instant::now (159 samples, 0.41%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (155 samples, 0.40%)clock_gettime (154 samples, 0.39%)__vdso_clock_gettime (4 samples, 0.01%)<T as core::convert::TryInto<U>>::try_into (306 samples, 0.78%)core::array::_<impl core::convert::TryFrom<&[T]> for [T: N]>::try_from (306 samples, 0.78%)core::result::Result<T,E>::map (306 samples, 0.78%)<core::result::Result<T,E> as core::ops::try_trait::Try>::branch (13 samples, 0.03%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (61 samples, 0.16%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (61 samples, 0.16%)std::sys::unix::locks::futex_mutex::Mutex::unlock (60 samples, 0.15%)core::sync::atomic::AtomicU32::swap (56 samples, 0.14%)core::sync::atomic::atomic_swap (56 samples, 0.14%)std::sync::mutex::MutexGuard<T>::new (6 samples, 0.02%)std::sync::poison::Flag::guard (6 samples, 0.02%)std::thread::panicking (6 samples, 0.02%)std::panicking::panicking (6 samples, 0.02%)std::panicking::panic_count::count_is_zero (6 samples, 0.02%)std::sync::mutex::Mutex<T>::lock (77 samples, 0.20%)std::sys::unix::locks::futex_mutex::Mutex::lock (71 samples, 0.18%)core::sync::atomic::AtomicU32::compare_exchange (69 samples, 0.18%)core::sync::atomic::atomic_compare_exchange (69 samples, 0.18%)EVP_CIPHER_CTX_get_block_size (5 samples, 0.01%)<zssp::crypto_impl::openssl::Aes256OpenSSLDec as zssp::crypto::aes::Aes256Dec>::decrypt_in_place (344 samples, 0.88%)zssp::crypto_impl::openssl::CipherCtx::update (200 samples, 0.51%)EVP_DecryptUpdate (196 samples, 0.50%)[libcrypto.so.3] (166 samples, 0.42%)[libcrypto.so.3] (119 samples, 0.30%)[libcrypto.so.3] (113 samples, 0.29%)__rust_probestack (9 samples, 0.02%)core::cmp::Ord::max (5 samples, 0.01%)zssp::zssp::Context<Crypto>::receive (5 samples, 0.01%)core::num::nonzero::NonZeroU32::new (6 samples, 0.02%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_pmu_nop_void (5 samples, 0.01%)alloc::sync::Weak<T>::upgrade::_{{closure}} (7 samples, 0.02%)core::sync::atomic::AtomicUsize::compare_exchange_weak (43 samples, 0.11%)core::sync::atomic::atomic_compare_exchange_weak (43 samples, 0.11%)alloc::sync::Weak<T>::upgrade (63 samples, 0.16%)core::sync::atomic::AtomicUsize::fetch_update (61 samples, 0.16%)core::sync::atomic::AtomicUsize::load (6 samples, 0.02%)core::sync::atomic::atomic_load (6 samples, 0.02%)core::option::Option<T>::map (71 samples, 0.18%)zssp::zssp::Context<Crypto>::receive::_{{closure}} (71 samples, 0.18%)zssp::zssp::Context<Crypto>::receive (8 samples, 0.02%)core::ptr::drop_in_place<alloc::sync::Arc<zssp::zeta::Session<benchmark::TestApplication>>> (55 samples, 0.14%)<alloc::sync::Arc<T> as core::ops::drop::Drop>::drop (55 samples, 0.14%)core::sync::atomic::AtomicUsize::fetch_sub (53 samples, 0.14%)core::sync::atomic::atomic_sub (53 samples, 0.14%)__rdl_dealloc (4 samples, 0.01%)std::sys::unix::alloc::<impl core::alloc::global::GlobalAlloc for std::alloc::System>::dealloc (4 samples, 0.01%)[libc.so.6] (73 samples, 0.19%)__entry_text_start (13 samples, 0.03%)futex_unqueue (8 samples, 0.02%)update_curr (16 samples, 0.04%)cpuacct_charge (8 samples, 0.02%)dequeue_entity (23 samples, 0.06%)update_load_avg (4 samples, 0.01%)dequeue_task_fair (28 samples, 0.07%)dequeue_task (29 samples, 0.07%)__perf_event_task_sched_in (8 samples, 0.02%)perf_ctx_enable (8 samples, 0.02%)x86_pmu_enable (4 samples, 0.01%)intel_pmu_enable_all (4 samples, 0.01%)native_write_msr (4 samples, 0.01%)finish_task_switch.isra.0 (19 samples, 0.05%)raw_spin_rq_unlock (5 samples, 0.01%)pick_next_task_fair (5 samples, 0.01%)pick_next_task (10 samples, 0.03%)put_prev_task_fair (4 samples, 0.01%)prepare_task_switch (16 samples, 0.04%)__perf_event_task_sched_out (5 samples, 0.01%)_raw_spin_lock (4 samples, 0.01%)psi_group_change (22 samples, 0.06%)psi_task_switch (35 samples, 0.09%)futex_wait_queue (143 samples, 0.37%)schedule (136 samples, 0.35%)__schedule (130 samples, 0.33%)__get_user_nocheck_4 (8 samples, 0.02%)_raw_spin_lock (6 samples, 0.02%)futex_q_lock (8 samples, 0.02%)futex_wait (192 samples, 0.49%)futex_wait_setup (30 samples, 0.08%)__x64_sys_futex (201 samples, 0.51%)do_futex (200 samples, 0.51%)__rseq_handle_notify_resume (7 samples, 0.02%)rseq_update_cpu_node_id (5 samples, 0.01%)exit_to_user_mode_loop (12 samples, 0.03%)exit_to_user_mode_prepare (19 samples, 0.05%)do_syscall_64 (227 samples, 0.58%)syscall_exit_to_user_mode (22 samples, 0.06%)entry_SYSCALL_64_after_hwframe (233 samples, 0.60%)__lll_lock_wait_private (283 samples, 0.72%)[libc.so.6] (759 samples, 1.94%)[..__entry_text_start (8 samples, 0.02%)futex_hash (7 samples, 0.02%)_raw_spin_lock (12 samples, 0.03%)native_queued_spin_lock_slowpath (12 samples, 0.03%)__x64_sys_futex (65 samples, 0.17%)do_futex (61 samples, 0.16%)futex_wake (51 samples, 0.13%)entry_SYSCALL_64_after_hwframe (78 samples, 0.20%)do_syscall_64 (77 samples, 0.20%)syscall_exit_to_user_mode (6 samples, 0.02%)exit_to_user_mode_prepare (6 samples, 0.02%)alloc::alloc::dealloc (903 samples, 2.31%)a..cfree (896 samples, 2.29%)c..__lll_lock_wake_private (96 samples, 0.25%)<alloc::alloc::Global as core::alloc::Allocator>::deallocate (906 samples, 2.32%)<..core::ptr::drop_in_place<arrayvec::arrayvec::ArrayVec<alloc::vec::Vec<u8>,48_usize>> (914 samples, 2.34%)c..<arrayvec::arrayvec::ArrayVec<T,_> as core::ops::drop::Drop>::drop (914 samples, 2.34%)<..arrayvec::arrayvec::ArrayVec<T,_>::clear (914 samples, 2.34%)a..arrayvec::arrayvec_impl::ArrayVecImpl::clear (914 samples, 2.34%)a..arrayvec::arrayvec_impl::ArrayVecImpl::truncate (914 samples, 2.34%)a..core::ptr::drop_in_place<[alloc::vec::Vec<u8>]> (913 samples, 2.34%)c..core::ptr::drop_in_place<alloc::vec::Vec<u8>> (911 samples, 2.33%)c..core::ptr::drop_in_place<alloc::raw_vec::RawVec<u8>> (911 samples, 2.33%)c..<alloc::raw_vec::RawVec<T,A> as core::ops::drop::Drop>::drop (911 samples, 2.33%)<..alloc::raw_vec::RawVec<T,A>::current_memory (5 samples, 0.01%)std::sync::poison::Flag::done (9 samples, 0.02%)std::thread::panicking (9 samples, 0.02%)std::panicking::panicking (9 samples, 0.02%)std::panicking::panic_count::count_is_zero (9 samples, 0.02%)core::sync::atomic::AtomicUsize::load (5 samples, 0.01%)core::sync::atomic::atomic_load (5 samples, 0.01%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::fragged::Fragged<alloc::vec::Vec<u8>,48_usize>>> (71 samples, 0.18%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (71 samples, 0.18%)std::sys::unix::locks::futex_mutex::Mutex::unlock (62 samples, 0.16%)core::sync::atomic::AtomicU32::swap (61 samples, 0.16%)core::sync::atomic::atomic_swap (61 samples, 0.16%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<std::collections::hash::map::HashMap<core::num::nonzero::NonZeroU32,alloc::sync::Weak<zssp::zeta::Session<benchmark::TestApplication>>>>> (58 samples, 0.15%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (58 samples, 0.15%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (58 samples, 0.15%)core::sync::atomic::AtomicU32::fetch_sub (53 samples, 0.14%)core::sync::atomic::atomic_sub (53 samples, 0.14%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<zssp::zeta::MutableState<benchmark::TestApplication>>> (48 samples, 0.12%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (48 samples, 0.12%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (48 samples, 0.12%)core::sync::atomic::AtomicU32::fetch_sub (44 samples, 0.11%)core::sync::atomic::atomic_sub (44 samples, 0.11%)core::num::<impl u64>::rotate_left (6 samples, 0.02%)<core::hash::sip::Sip13Rounds as core::hash::sip::Sip>::c_rounds (11 samples, 0.03%)core::num::<impl u64>::rotate_left (11 samples, 0.03%)<std::collections::hash::map::DefaultHasher as core::hash::Hasher>::finish (51 samples, 0.13%)<core::hash::sip::SipHasher13 as core::hash::Hasher>::finish (51 samples, 0.13%)<core::hash::sip::Hasher<S> as core::hash::Hasher>::finish (51 samples, 0.13%)<core::hash::sip::Sip13Rounds as core::hash::sip::Sip>::d_rounds (30 samples, 0.08%)core::num::<impl u64>::wrapping_add (12 samples, 0.03%)<std::collections::hash::map::RandomState as core::hash::BuildHasher>::build_hasher (5 samples, 0.01%)hashbrown::map::make_hash (76 samples, 0.19%)core::hash::BuildHasher::hash_one (76 samples, 0.19%)core::hash::impls::<impl core::hash::Hash for &T>::hash (17 samples, 0.04%)<core::num::nonzero::NonZeroU32 as core::hash::Hash>::hash (17 samples, 0.04%)core::hash::impls::<impl core::hash::Hash for u32>::hash (17 samples, 0.04%)core::hash::Hasher::write_u32 (17 samples, 0.04%)<std::collections::hash::map::DefaultHasher as core::hash::Hasher>::write (17 samples, 0.04%)<core::hash::sip::SipHasher13 as core::hash::Hasher>::write (17 samples, 0.04%)<core::hash::sip::Hasher<S> as core::hash::Hasher>::write (17 samples, 0.04%)core::hash::sip::u8to64_le (7 samples, 0.02%)<hashbrown::raw::bitmask::BitMaskIter as core::iter::traits::iterator::Iterator>::next (9 samples, 0.02%)hashbrown::raw::bitmask::BitMask::lowest_set_bit (5 samples, 0.01%)hashbrown::map::equivalent_key::_{{closure}} (7 samples, 0.02%)<core::num::nonzero::NonZeroU32 as core::cmp::PartialEq>::eq (7 samples, 0.02%)hashbrown::raw::RawTable<T,A>::find::_{{closure}} (11 samples, 0.03%)hashbrown::raw::Bucket<T>::as_ref (4 samples, 0.01%)hashbrown::raw::Bucket<T>::as_ptr (4 samples, 0.01%)core::ptr::mut_ptr::<impl *mut T>::sub (4 samples, 0.01%)core::ptr::mut_ptr::<impl *mut T>::offset (4 samples, 0.01%)hashbrown::raw::h2 (10 samples, 0.03%)hashbrown::map::HashMap<K,V,S,A>::get_inner (119 samples, 0.30%)hashbrown::raw::RawTable<T,A>::get (43 samples, 0.11%)hashbrown::raw::RawTable<T,A>::find (43 samples, 0.11%)hashbrown::raw::RawTableInner<A>::find_inner (43 samples, 0.11%)hashbrown::raw::sse2::Group::match_byte (4 samples, 0.01%)core::core_arch::x86::sse2::_mm_movemask_epi8 (4 samples, 0.01%)std::collections::hash::map::HashMap<K,V,S>::get (122 samples, 0.31%)hashbrown::map::HashMap<K,V,S,A>::get (122 samples, 0.31%)std::sync::mutex::MutexGuard<T>::new (8 samples, 0.02%)std::sync::poison::Flag::guard (5 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (47 samples, 0.12%)std::sys::unix::locks::futex_mutex::Mutex::lock (37 samples, 0.09%)core::sync::atomic::AtomicU32::compare_exchange (37 samples, 0.09%)core::sync::atomic::atomic_compare_exchange (37 samples, 0.09%)core::sync::atomic::AtomicU32::compare_exchange_weak (108 samples, 0.28%)core::sync::atomic::atomic_compare_exchange_weak (108 samples, 0.28%)std::sync::rwlock::RwLock<T>::read (129 samples, 0.33%)std::sys::unix::locks::futex_rwlock::RwLock::read (129 samples, 0.33%)std::sys::unix::locks::futex_rwlock::is_read_lockable (7 samples, 0.02%)zssp::antireplay::Window<_,_>::check (11 samples, 0.03%)core::sync::atomic::AtomicU64::load (6 samples, 0.02%)core::sync::atomic::atomic_load (6 samples, 0.02%)arrayvec::arrayvec::ArrayVec<T,_>::push (19 samples, 0.05%)arrayvec::arrayvec_impl::ArrayVecImpl::push (19 samples, 0.05%)arrayvec::arrayvec_impl::ArrayVecImpl::try_push (19 samples, 0.05%)arrayvec::arrayvec_impl::ArrayVecImpl::push_unchecked (13 samples, 0.03%)core::ptr::write (12 samples, 0.03%)core::mem::maybe_uninit::MaybeUninit<T>::assume_init_read (4 samples, 0.01%)core::ptr::const_ptr::<impl *const T>::read (4 samples, 0.01%)core::ptr::read (4 samples, 0.01%)core::mem::maybe_uninit::MaybeUninit<T>::assume_init (4 samples, 0.01%)core::mem::maybe_uninit::MaybeUninit<T>::write (4 samples, 0.01%)zssp::fragged::Fragged<Fragment,_>::assemble (5 samples, 0.01%)zssp::fragged::Fragged<Fragment,_>::assemble (62 samples, 0.16%)<T as core::convert::TryInto<U>>::try_into (26 samples, 0.07%)core::array::_<impl core::convert::TryFrom<&[T]> for [T: N]>::try_from (26 samples, 0.07%)core::result::Result<T,E>::map (26 samples, 0.07%)zssp::zeta::from_nonce (31 samples, 0.08%)core::num::<impl u64>::from_be_bytes (4 samples, 0.01%)core::num::<impl u64>::from_be (4 samples, 0.01%)core::num::<impl u64>::swap_bytes (4 samples, 0.01%)<core::slice::iter::IterMut<T> as core::iter::traits::iterator::Iterator>::next (9 samples, 0.02%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (55 samples, 0.14%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (55 samples, 0.14%)core::sync::atomic::AtomicU32::fetch_sub (53 samples, 0.14%)core::sync::atomic::atomic_sub (53 samples, 0.14%)[libcrypto.so.3] (103 samples, 0.26%)CRYPTO_gcm128_decrypt (234 samples, 0.60%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (4 samples, 0.01%)[libcrypto.so.3] (17 samples, 0.04%)CRYPTO_gcm128_decrypt_ctr32 (452 samples, 1.16%)[libcrypto.so.3] (355 samples, 0.91%)CRYPTO_gcm128_setiv (15 samples, 0.04%)[libcrypto.so.3] (14 samples, 0.04%)asm_sysvec_apic_timer_interrupt (5 samples, 0.01%)sysvec_apic_timer_interrupt (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)__perf_event_task_sched_in (9 samples, 0.02%)perf_ctx_enable (9 samples, 0.02%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::decrypt_in_place (4,297 samples, 10.99%)<zssp::crypto_im..zssp::crypto_impl::openssl::CipherCtx::update (4,297 samples, 10.99%)zssp::crypto_imp..EVP_DecryptUpdate (4,294 samples, 10.99%)EVP_DecryptUpdate[libcrypto.so.3] (4,195 samples, 10.73%)[libcrypto.so.3][libcrypto.so.3] (4,187 samples, 10.71%)[libcrypto.so.3][libcrypto.so.3] (4,169 samples, 10.67%)[libcrypto.so.3][libcrypto.so.3] (3,429 samples, 8.77%)[libcrypto.s..[libcrypto.so.3] (3,280 samples, 8.39%)[libcrypto.s..asm_sysvec_reschedule_ipi (10 samples, 0.03%)sysvec_reschedule_ipi (10 samples, 0.03%)irqentry_exit (10 samples, 0.03%)irqentry_exit_to_user_mode (10 samples, 0.03%)exit_to_user_mode_prepare (10 samples, 0.03%)exit_to_user_mode_loop (10 samples, 0.03%)schedule (10 samples, 0.03%)__schedule (10 samples, 0.03%)finish_task_switch.isra.0 (10 samples, 0.03%)CRYPTO_clear_free (106 samples, 0.27%)OPENSSL_cleanse (103 samples, 0.26%)EVP_CIPHER_free (44 samples, 0.11%)EVP_CIPHER_CTX_free (199 samples, 0.51%)EVP_CIPHER_CTX_reset (198 samples, 0.51%)cfree (37 samples, 0.09%)[libc.so.6] (5 samples, 0.01%)cfree (9 samples, 0.02%)[libc.so.6] (4 samples, 0.01%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::AesGcmOpenSSLDec> (212 samples, 0.54%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::CipherCtx> (212 samples, 0.54%)<zssp::crypto_impl::openssl::CipherCtx as core::ops::drop::Drop>::drop (212 samples, 0.54%)[libcrypto.so.3] (26 samples, 0.07%)[libcrypto.so.3] (26 samples, 0.07%)[libcrypto.so.3] (26 samples, 0.07%)CRYPTO_gcm128_finish (24 samples, 0.06%)[libcrypto.so.3] (18 samples, 0.05%)zssp::crypto_impl::openssl::CipherCtx::finalize (36 samples, 0.09%)EVP_DecryptFinal_ex (36 samples, 0.09%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)OSSL_PARAM_get_octet_string (7 samples, 0.02%)[libcrypto.so.3] (5 samples, 0.01%)[libc.so.6] (12 samples, 0.03%)OSSL_PARAM_locate (30 samples, 0.08%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::finish (336 samples, 0.86%)zssp::crypto_impl::openssl::CipherCtx::set_tag (88 samples, 0.23%)EVP_CIPHER_CTX_ctrl (88 samples, 0.23%)[libcrypto.so.3] (49 samples, 0.13%)[libc.so.6] (27 samples, 0.07%)OSSL_PARAM_locate (38 samples, 0.10%)EVP_CIPHER_CTX_set_padding (90 samples, 0.23%)[libcrypto.so.3] (53 samples, 0.14%)[libc.so.6] (24 samples, 0.06%)OSSL_PARAM_locate (48 samples, 0.12%)EVP_CIPHER_CTX_get_iv_length (82 samples, 0.21%)[libcrypto.so.3] (60 samples, 0.15%)[libc.so.6] (18 samples, 0.05%)OSSL_PARAM_locate (34 samples, 0.09%)EVP_CIPHER_CTX_get_key_length (51 samples, 0.13%)[libcrypto.so.3] (42 samples, 0.11%)CRYPTO_THREAD_read_lock (155 samples, 0.40%)pthread_rwlock_rdlock (151 samples, 0.39%)pthread_rwlock_unlock (88 samples, 0.23%)CRYPTO_THREAD_unlock (91 samples, 0.23%)EVP_CIPHER_up_ref (29 samples, 0.07%)OPENSSL_LH_retrieve (59 samples, 0.15%)[libcrypto.so.3] (51 samples, 0.13%)pthread_rwlock_rdlock (40 samples, 0.10%)CRYPTO_THREAD_read_lock (43 samples, 0.11%)pthread_rwlock_unlock (49 samples, 0.13%)CRYPTO_THREAD_unlock (54 samples, 0.14%)OPENSSL_strnlen (7 samples, 0.02%)CRYPTO_strndup (15 samples, 0.04%)OPENSSL_strcasecmp (25 samples, 0.06%)OPENSSL_LH_retrieve (150 samples, 0.38%)[libcrypto.so.3] (142 samples, 0.36%)[libcrypto.so.3] (42 samples, 0.11%)[libcrypto.so.3] (298 samples, 0.76%)cfree (23 samples, 0.06%)[libc.so.6] (6 samples, 0.02%)[libcrypto.so.3] (652 samples, 1.67%)EVP_CIPHER_fetch (660 samples, 1.69%)[libcrypto.so.3] (658 samples, 1.68%)EVP_CIPHER_free (15 samples, 0.04%)EVP_CIPHER_up_ref (14 samples, 0.04%)CRYPTO_malloc (4 samples, 0.01%)[libc.so.6] (8 samples, 0.02%)malloc (7 samples, 0.02%)CRYPTO_zalloc (21 samples, 0.05%)CRYPTO_gcm128_init (149 samples, 0.38%)[libcrypto.so.3] (122 samples, 0.31%)[libcrypto.so.3] (188 samples, 0.48%)[libcrypto.so.3] (34 samples, 0.09%)EVP_CipherInit_ex (1,072 samples, 2.74%)EV..[libcrypto.so.3] (1,072 samples, 2.74%)[l..[libcrypto.so.3] (221 samples, 0.57%)zssp::crypto_impl::openssl::CipherCtx::cipher_init (1,074 samples, 2.75%)zs..[libc.so.6] (4 samples, 0.01%)malloc (15 samples, 0.04%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_dec (1,188 samples, 3.04%)<zs..zssp::crypto_impl::openssl::CipherCtx::new (22 samples, 0.06%)CRYPTO_zalloc (22 samples, 0.06%)core::slice::index::<impl core::ops::index::Index<I> for [T]>::index (5 samples, 0.01%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::index (5 samples, 0.01%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::get_unchecked (4 samples, 0.01%)<core::ops::range::Range<usize> as core::slice::index::SliceIndex<[T]>>::get_unchecked (4 samples, 0.01%)core::ptr::const_ptr::<impl *const T>::add (4 samples, 0.01%)core::ptr::const_ptr::<impl *const T>::offset (4 samples, 0.01%)core::slice::index::<impl core::ops::index::IndexMut<I> for [T]>::index_mut (6 samples, 0.02%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::index_mut (6 samples, 0.02%)std::io::impls::<impl std::io::Write for &mut W>::write (183 samples, 0.47%)<arrayvec::arrayvec::ArrayVec<u8,_> as std::io::Write>::write (183 samples, 0.47%)arrayvec::arrayvec::ArrayVec<T,_>::try_extend_from_slice (181 samples, 0.46%)core::intrinsics::copy_nonoverlapping (181 samples, 0.46%)[libc.so.6] (181 samples, 0.46%)zssp::zeta::receive_payload_in_place (6,109 samples, 15.63%)zssp::zeta::receive_payl..zssp::antireplay::Window<_,_>::update (13 samples, 0.03%)core::sync::atomic::AtomicU64::fetch_max (13 samples, 0.03%)core::sync::atomic::atomic_umax (13 samples, 0.03%)zssp::zssp::Context<Crypto>::receive (8,570 samples, 21.93%)zssp::zssp::Context<Crypto>::receivezssp::zssp::parse_fragment_header (78 samples, 0.20%)core::slice::<impl [T]>::copy_from_slice (6 samples, 0.02%)core::intrinsics::copy_nonoverlapping (6 samples, 0.02%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (60 samples, 0.15%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (60 samples, 0.15%)std::sys::unix::locks::futex_mutex::Mutex::unlock (58 samples, 0.15%)core::sync::atomic::AtomicU32::swap (53 samples, 0.14%)core::sync::atomic::atomic_swap (53 samples, 0.14%)std::sync::mutex::MutexGuard<T>::new (4 samples, 0.01%)std::sync::poison::Flag::guard (4 samples, 0.01%)std::thread::panicking (4 samples, 0.01%)std::panicking::panicking (4 samples, 0.01%)std::panicking::panic_count::count_is_zero (4 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (90 samples, 0.23%)std::sys::unix::locks::futex_mutex::Mutex::lock (86 samples, 0.22%)core::sync::atomic::AtomicU32::compare_exchange (80 samples, 0.20%)core::sync::atomic::atomic_compare_exchange (80 samples, 0.20%)<zssp::crypto_impl::openssl::Aes256OpenSSLEnc as zssp::crypto::aes::Aes256Enc>::encrypt_in_place (337 samples, 0.86%)zssp::crypto_impl::openssl::CipherCtx::update (184 samples, 0.47%)EVP_EncryptUpdate (183 samples, 0.47%)[libcrypto.so.3] (160 samples, 0.41%)[libcrypto.so.3] (121 samples, 0.31%)[libcrypto.so.3] (116 samples, 0.30%)CRYPTO_gcm128_encrypt (243 samples, 0.62%)[libcrypto.so.3] (137 samples, 0.35%)[libcrypto.so.3] (326 samples, 0.83%)[libcrypto.so.3] (21 samples, 0.05%)CRYPTO_gcm128_encrypt_ctr32 (442 samples, 1.13%)CRYPTO_gcm128_setiv (16 samples, 0.04%)[libcrypto.so.3] (15 samples, 0.04%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::encrypt (3,402 samples, 8.70%)<zssp::crypt..zssp::crypto_impl::openssl::CipherCtx::update (3,400 samples, 8.70%)zssp::crypto..EVP_EncryptUpdate (3,399 samples, 8.70%)EVP_EncryptU..[libcrypto.so.3] (3,327 samples, 8.51%)[libcrypto.s..[libcrypto.so.3] (3,322 samples, 8.50%)[libcrypto.s..[libcrypto.so.3] (3,300 samples, 8.44%)[libcrypto.s..[libcrypto.so.3] (2,562 samples, 6.56%)[libcrypt..[libcrypto.so.3] (2,334 samples, 5.97%)[libcryp..CRYPTO_clear_free (96 samples, 0.25%)OPENSSL_cleanse (96 samples, 0.25%)EVP_CIPHER_free (43 samples, 0.11%)cfree (23 samples, 0.06%)[libc.so.6] (5 samples, 0.01%)EVP_CIPHER_CTX_free (185 samples, 0.47%)EVP_CIPHER_CTX_reset (185 samples, 0.47%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc> (191 samples, 0.49%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::CipherCtx> (191 samples, 0.49%)<zssp::crypto_impl::openssl::CipherCtx as core::ops::drop::Drop>::drop (191 samples, 0.49%)cfree (5 samples, 0.01%)[libc.so.6] (4 samples, 0.01%)zssp::crypto_impl::openssl::CipherCtx::finalize (69 samples, 0.18%)EVP_EncryptFinal_ex (67 samples, 0.17%)[libcrypto.so.3] (51 samples, 0.13%)[libcrypto.so.3] (50 samples, 0.13%)[libcrypto.so.3] (45 samples, 0.12%)CRYPTO_gcm128_tag (45 samples, 0.12%)CRYPTO_gcm128_finish (37 samples, 0.09%)[libcrypto.so.3] (22 samples, 0.06%)[libc.so.6] (15 samples, 0.04%)OSSL_PARAM_locate (39 samples, 0.10%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::finish (345 samples, 0.88%)zssp::crypto_impl::openssl::CipherCtx::get_tag (84 samples, 0.21%)EVP_CIPHER_CTX_ctrl (83 samples, 0.21%)[libcrypto.so.3] (51 samples, 0.13%)[libc.so.6] (25 samples, 0.06%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)OSSL_PARAM_locate (41 samples, 0.10%)strcmp@plt (4 samples, 0.01%)[libcrypto.so.3] (49 samples, 0.13%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)EVP_CIPHER_CTX_set_padding (83 samples, 0.21%)asm_sysvec_reschedule_ipi (6 samples, 0.02%)sysvec_reschedule_ipi (6 samples, 0.02%)irqentry_exit (6 samples, 0.02%)irqentry_exit_to_user_mode (6 samples, 0.02%)exit_to_user_mode_prepare (6 samples, 0.02%)exit_to_user_mode_loop (6 samples, 0.02%)schedule (6 samples, 0.02%)__schedule (6 samples, 0.02%)finish_task_switch.isra.0 (6 samples, 0.02%)[libc.so.6] (21 samples, 0.05%)OSSL_PARAM_locate (41 samples, 0.10%)EVP_CIPHER_CTX_get_iv_length (82 samples, 0.21%)[libcrypto.so.3] (54 samples, 0.14%)[libc.so.6] (9 samples, 0.02%)OSSL_PARAM_locate (26 samples, 0.07%)EVP_CIPHER_CTX_get_key_length (48 samples, 0.12%)[libcrypto.so.3] (40 samples, 0.10%)CRYPTO_THREAD_read_lock (116 samples, 0.30%)pthread_rwlock_rdlock (112 samples, 0.29%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)pthread_rwlock_unlock (95 samples, 0.24%)asm_sysvec_reschedule_ipi (4 samples, 0.01%)sysvec_reschedule_ipi (4 samples, 0.01%)irqentry_exit (4 samples, 0.01%)irqentry_exit_to_user_mode (4 samples, 0.01%)exit_to_user_mode_prepare (4 samples, 0.01%)exit_to_user_mode_loop (4 samples, 0.01%)schedule (4 samples, 0.01%)__schedule (4 samples, 0.01%)finish_task_switch.isra.0 (4 samples, 0.01%)__perf_event_task_sched_in (4 samples, 0.01%)perf_pmu_nop_void (4 samples, 0.01%)CRYPTO_THREAD_unlock (101 samples, 0.26%)EVP_CIPHER_up_ref (39 samples, 0.10%)OPENSSL_LH_retrieve (56 samples, 0.14%)[libcrypto.so.3] (48 samples, 0.12%)[libcrypto.so.3] (6 samples, 0.02%)pthread_rwlock_rdlock (20 samples, 0.05%)CRYPTO_THREAD_read_lock (24 samples, 0.06%)pthread_rwlock_unlock (51 samples, 0.13%)CRYPTO_THREAD_unlock (54 samples, 0.14%)CRYPTO_strndup (10 samples, 0.03%)malloc (6 samples, 0.02%)OPENSSL_strcasecmp (44 samples, 0.11%)OPENSSL_LH_retrieve (173 samples, 0.44%)[libcrypto.so.3] (157 samples, 0.40%)[libcrypto.so.3] (53 samples, 0.14%)cfree (17 samples, 0.04%)[libc.so.6] (6 samples, 0.02%)[libcrypto.so.3] (290 samples, 0.74%)EVP_CIPHER_fetch (622 samples, 1.59%)[libcrypto.so.3] (620 samples, 1.59%)[libcrypto.so.3] (612 samples, 1.57%)EVP_CIPHER_free (14 samples, 0.04%)EVP_CIPHER_up_ref (18 samples, 0.05%)CRYPTO_malloc (4 samples, 0.01%)[libc.so.6] (9 samples, 0.02%)malloc (8 samples, 0.02%)CRYPTO_zalloc (24 samples, 0.06%)OPENSSL_init_crypto (4 samples, 0.01%)CRYPTO_gcm128_init (157 samples, 0.40%)[libcrypto.so.3] (141 samples, 0.36%)finish_task_switch.isra.0 (4 samples, 0.01%)__perf_event_task_sched_in (4 samples, 0.01%)perf_ctx_enable (4 samples, 0.01%)EVP_CipherInit_ex (1,051 samples, 2.69%)EV..[libcrypto.so.3] (1,051 samples, 2.69%)[l..[libcrypto.so.3] (235 samples, 0.60%)[libcrypto.so.3] (202 samples, 0.52%)[libcrypto.so.3] (43 samples, 0.11%)asm_sysvec_reschedule_ipi (6 samples, 0.02%)sysvec_reschedule_ipi (6 samples, 0.02%)irqentry_exit (6 samples, 0.02%)irqentry_exit_to_user_mode (6 samples, 0.02%)exit_to_user_mode_prepare (6 samples, 0.02%)exit_to_user_mode_loop (6 samples, 0.02%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)zssp::crypto_impl::openssl::CipherCtx::cipher_init (1,054 samples, 2.70%)zs..[libc.so.6] (4 samples, 0.01%)malloc (7 samples, 0.02%)CRYPTO_zalloc (13 samples, 0.03%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_enc (1,154 samples, 2.95%)<zs..zssp::crypto_impl::openssl::CipherCtx::new (14 samples, 0.04%)__rdl_alloc (6 samples, 0.02%)std::sys::unix::alloc::<impl core::alloc::global::GlobalAlloc for std::alloc::System>::alloc (6 samples, 0.02%)[libc.so.6] (49 samples, 0.13%)[libc.so.6] (850 samples, 2.17%)[..asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)__get_user_nocheck_4 (8 samples, 0.02%)futex_q_lock (13 samples, 0.03%)futex_q_unlock (5 samples, 0.01%)__x64_sys_futex (35 samples, 0.09%)do_futex (33 samples, 0.08%)futex_wait (32 samples, 0.08%)futex_wait_setup (28 samples, 0.07%)entry_SYSCALL_64_after_hwframe (37 samples, 0.09%)do_syscall_64 (37 samples, 0.09%)__lll_lock_wait_private (55 samples, 0.14%)futex_wake_mark (11 samples, 0.03%)wake_q_add_safe (4 samples, 0.01%)call_function_single_prep_ipi (4 samples, 0.01%)__smp_call_single_queue (17 samples, 0.04%)native_send_call_func_single_ipi (13 samples, 0.03%)x2apic_send_IPI (12 samples, 0.03%)native_write_msr (10 samples, 0.03%)llist_add_batch (10 samples, 0.03%)futex_wake (123 samples, 0.31%)wake_up_q (63 samples, 0.16%)try_to_wake_up (58 samples, 0.15%)ttwu_queue_wakelist (38 samples, 0.10%)__x64_sys_futex (133 samples, 0.34%)do_futex (132 samples, 0.34%)entry_SYSCALL_64_after_hwframe (145 samples, 0.37%)do_syscall_64 (144 samples, 0.37%)syscall_exit_to_user_mode (7 samples, 0.02%)exit_to_user_mode_prepare (5 samples, 0.01%)alloc::vec::Vec<T,A>::with_capacity_in (1,653 samples, 4.23%)alloc..alloc::raw_vec::RawVec<T,A>::with_capacity_in (1,653 samples, 4.23%)alloc..alloc::raw_vec::RawVec<T,A>::allocate_in (1,653 samples, 4.23%)alloc..<alloc::alloc::Global as core::alloc::Allocator>::allocate (1,651 samples, 4.22%)<allo..alloc::alloc::Global::alloc_impl (1,651 samples, 4.22%)alloc..alloc::alloc::alloc (1,651 samples, 4.22%)alloc..malloc (1,644 samples, 4.21%)malloc__lll_lock_wake_private (152 samples, 0.39%)alloc::slice::<impl [T]>::to_vec (1,857 samples, 4.75%)alloc:..alloc::slice::<impl [T]>::to_vec_in (1,857 samples, 4.75%)alloc:..alloc::slice::hack::to_vec (1,857 samples, 4.75%)alloc:..<T as alloc::slice::hack::ConvertVec>::to_vec (1,857 samples, 4.75%)<T as ..core::ptr::const_ptr::<impl *const T>::copy_to_nonoverlapping (204 samples, 0.52%)core::intrinsics::copy_nonoverlapping (204 samples, 0.52%)[libc.so.6] (203 samples, 0.52%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)core::sync::atomic::AtomicUsize::compare_exchange_weak (100 samples, 0.26%)core::sync::atomic::atomic_compare_exchange_weak (100 samples, 0.26%)std::sync::mpmc::array::Channel<T>::start_send (155 samples, 0.40%)core::sync::atomic::AtomicUsize::load (32 samples, 0.08%)core::sync::atomic::atomic_load (32 samples, 0.08%)core::ptr::mut_ptr::<impl *mut T>::write (4 samples, 0.01%)core::ptr::write (4 samples, 0.01%)std::sync::mpmc::array::Channel<T>::send (185 samples, 0.47%)std::sync::mpmc::array::Channel<T>::write (13 samples, 0.03%)std::sync::mpmc::waker::SyncWaker::notify (7 samples, 0.02%)core::sync::atomic::AtomicBool::load (5 samples, 0.01%)core::sync::atomic::atomic_load (5 samples, 0.01%)benchmark::bob_main::_{{closure}} (3,332 samples, 8.53%)benchmark::b..std::sync::mpsc::SyncSender<T>::send (1,474 samples, 3.77%)std:..std::sync::mpmc::Sender<T>::send (1,473 samples, 3.77%)std:..core::mem::drop (27 samples, 0.07%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<zssp::zeta::MutableState<benchmark::TestApplication>>> (27 samples, 0.07%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (27 samples, 0.07%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (27 samples, 0.07%)core::sync::atomic::AtomicU32::fetch_sub (27 samples, 0.07%)core::sync::atomic::atomic_sub (27 samples, 0.07%)core::slice::<impl [T]>::copy_from_slice (7 samples, 0.02%)core::intrinsics::copy_nonoverlapping (7 samples, 0.02%)core::sync::atomic::AtomicU32::compare_exchange_weak (35 samples, 0.09%)core::sync::atomic::atomic_compare_exchange_weak (35 samples, 0.09%)std::sync::rwlock::RwLock<T>::read (36 samples, 0.09%)std::sys::unix::locks::futex_rwlock::RwLock::read (36 samples, 0.09%)benchmark::bob_main (18,132 samples, 46.39%)benchmark::bob_mainzssp::zssp::Context<Crypto>::send (8,692 samples, 22.24%)zssp::zssp::Context<Crypto>::sendzssp::zeta::send_payload (8,691 samples, 22.24%)zssp::zeta::send_payloadzssp::zeta::get_counter (12 samples, 0.03%)core::sync::atomic::AtomicU64::fetch_add (11 samples, 0.03%)core::sync::atomic::atomic_add (11 samples, 0.03%)cfree (19 samples, 0.05%)clock_gettime (12 samples, 0.03%)core::hash::BuildHasher::hash_one (20 samples, 0.05%)core::hash::impls::<impl core::hash::Hash for &T>::hash (12 samples, 0.03%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::fragged::Fragged<alloc::vec::Vec<u8>,48_usize>>> (4 samples, 0.01%)core::result::Result<T,E>::unwrap (7 samples, 0.02%)pthread_rwlock_rdlock (21 samples, 0.05%)pthread_rwlock_unlock (15 samples, 0.04%)std::sync::mpmc::Receiver<T>::recv_timeout (12 samples, 0.03%)std::sync::mpmc::Sender<T>::send (15 samples, 0.04%)<std::sync::mpmc::select::Token as core::default::Default>::default (7 samples, 0.02%)std::sync::mpmc::array::Channel<T>::recv (24 samples, 0.06%)std::sync::mpmc::array::Channel<T>::send (11 samples, 0.03%)std::sync::mpmc::array::Channel<T>::start_recv (12 samples, 0.03%)std::sync::mpmc::utils::Backoff::new (9 samples, 0.02%)std::sync::mpmc::waker::SyncWaker::notify (19 samples, 0.05%)core::sync::atomic::AtomicBool::load (4 samples, 0.01%)core::sync::atomic::atomic_load (4 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (6 samples, 0.02%)std::sys::unix::time::Timespec::sub_timespec (6 samples, 0.02%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (15 samples, 0.04%)std::time::SystemTime::checked_add (5 samples, 0.01%)zssp::fragged::Fragged<Fragment,_>::assemble (21 samples, 0.05%)zssp::zeta::from_nonce (5 samples, 0.01%)alloc::vec::Vec<T,A>::with_capacity_in (14 samples, 0.04%)alloc::raw_vec::RawVec<T,A>::with_capacity_in (14 samples, 0.04%)alloc::raw_vec::RawVec<T,A>::allocate_in (14 samples, 0.04%)<alloc::alloc::Global as core::alloc::Allocator>::allocate (14 samples, 0.04%)alloc::alloc::Global::alloc_impl (14 samples, 0.04%)alloc::alloc::alloc (14 samples, 0.04%)alloc::slice::<impl [T]>::to_vec (20 samples, 0.05%)alloc::slice::<impl [T]>::to_vec_in (20 samples, 0.05%)alloc::slice::hack::to_vec (20 samples, 0.05%)<T as alloc::slice::hack::ConvertVec>::to_vec (20 samples, 0.05%)core::ptr::const_ptr::<impl *const T>::copy_to_nonoverlapping (6 samples, 0.02%)core::intrinsics::copy_nonoverlapping (6 samples, 0.02%)zssp::zeta::send_payload (50 samples, 0.13%)benchmark::bob_main::_{{closure}} (29 samples, 0.07%)std::sync::mpsc::SyncSender<T>::send (9 samples, 0.02%)core::ptr::drop_in_place<arrayvec::arrayvec::ArrayVec<alloc::vec::Vec<u8>,48_usize>> (7 samples, 0.02%)<arrayvec::arrayvec::ArrayVec<T,_> as core::ops::drop::Drop>::drop (7 samples, 0.02%)arrayvec::arrayvec::ArrayVec<T,_>::clear (7 samples, 0.02%)arrayvec::arrayvec_impl::ArrayVecImpl::clear (7 samples, 0.02%)arrayvec::arrayvec_impl::ArrayVecImpl::truncate (7 samples, 0.02%)core::ptr::drop_in_place<[alloc::vec::Vec<u8>]> (7 samples, 0.02%)core::ptr::drop_in_place<alloc::vec::Vec<u8>> (7 samples, 0.02%)core::ptr::drop_in_place<alloc::raw_vec::RawVec<u8>> (7 samples, 0.02%)<alloc::raw_vec::RawVec<T,A> as core::ops::drop::Drop>::drop (7 samples, 0.02%)<alloc::alloc::Global as core::alloc::Allocator>::deallocate (7 samples, 0.02%)alloc::alloc::dealloc (7 samples, 0.02%)cfree (5 samples, 0.01%)__lll_lock_wake_private (5 samples, 0.01%)__entry_text_start (5 samples, 0.01%)getrandom::imp::getrandom_inner (5 samples, 0.01%)zssp::zssp::Context<Crypto>::receive (70 samples, 0.18%)std::sync::mpmc::array::Channel<T>::start_recv (13 samples, 0.03%)[unknown] (37,550 samples, 96.08%)[unknown]zssp::zssp::parse_fragment_header (4 samples, 0.01%)EVP_DecryptUpdate (31 samples, 0.08%)__bss_start (36 samples, 0.09%)[libcrypto.so.3] (5 samples, 0.01%)_raw_spin_unlock (4 samples, 0.01%)preempt_schedule_thunk (4 samples, 0.01%)preempt_schedule (4 samples, 0.01%)__schedule (4 samples, 0.01%)finish_task_switch.isra.0 (4 samples, 0.01%)__perf_event_task_sched_in (4 samples, 0.01%)perf_ctx_enable (4 samples, 0.01%)entry_SYSCALL_64_after_hwframe (9 samples, 0.02%)do_syscall_64 (7 samples, 0.02%)__x64_sys_exit_group (6 samples, 0.02%)do_group_exit (6 samples, 0.02%)do_exit (6 samples, 0.02%)exit_mm (6 samples, 0.02%)mmput (6 samples, 0.02%)__mmput (6 samples, 0.02%)exit_mmap (6 samples, 0.02%)unmap_vmas (5 samples, 0.01%)unmap_single_vma (5 samples, 0.01%)unmap_page_range (5 samples, 0.01%)zap_pmd_range.isra.0 (5 samples, 0.01%)zap_pte_range (5 samples, 0.01%)entry_SYSCALL_64_safe_stack (15 samples, 0.04%)ret_from_fork (10 samples, 0.03%)schedule_tail (10 samples, 0.03%)finish_task_switch.isra.0 (10 samples, 0.03%)__perf_event_task_sched_in (10 samples, 0.03%)perf_ctx_enable (10 samples, 0.03%)syscall_return_via_sysret (9 samples, 0.02%)core::ptr::drop_in_place<arrayvec::arrayvec::ArrayVec<alloc::vec::Vec<u8>,48_usize>> (5 samples, 0.01%)<arrayvec::arrayvec::ArrayVec<T,_> as core::ops::drop::Drop>::drop (5 samples, 0.01%)arrayvec::arrayvec::ArrayVec<T,_>::clear (5 samples, 0.01%)arrayvec::arrayvec_impl::ArrayVecImpl::clear (5 samples, 0.01%)arrayvec::arrayvec_impl::ArrayVecImpl::truncate (5 samples, 0.01%)core::ptr::drop_in_place<[alloc::vec::Vec<u8>]> (5 samples, 0.01%)core::ptr::drop_in_place<alloc::vec::Vec<u8>> (5 samples, 0.01%)core::ptr::drop_in_place<alloc::raw_vec::RawVec<u8>> (5 samples, 0.01%)<alloc::raw_vec::RawVec<T,A> as core::ops::drop::Drop>::drop (5 samples, 0.01%)<alloc::alloc::Global as core::alloc::Allocator>::deallocate (5 samples, 0.01%)alloc::alloc::dealloc (5 samples, 0.01%)benchmark (39,077 samples, 99.99%)benchmarkzssp::zssp::Context<Crypto>::receive (41 samples, 0.10%)perf_event_exec (4 samples, 0.01%)perf_event_enable_on_exec (4 samples, 0.01%)ctx_resched (4 samples, 0.01%)perf_ctx_enable (4 samples, 0.01%)all (39,082 samples, 100%)perf-exec (5 samples, 0.01%)entry_SYSCALL_64_after_hwframe (5 samples, 0.01%)do_syscall_64 (5 samples, 0.01%)__x64_sys_execve (5 samples, 0.01%)do_execveat_common.isra.0 (5 samples, 0.01%)bprm_execve (5 samples, 0.01%)bprm_execve.part.0 (5 samples, 0.01%)exec_binprm (5 samples, 0.01%)search_binary_handler (5 samples, 0.01%)load_elf_binary (5 samples, 0.01%)begin_new_exec (5 samples, 0.01%) \ No newline at end of file +]]>Flame Graph Reset ZoomSearch <zssp::crypto_impl::openssl::Aes256OpenSSLDec as zssp::crypto::aes::Aes256Dec>::decrypt_in_place (13 samples, 0.03%)zssp::crypto_impl::openssl::CipherCtx::update (7 samples, 0.02%)<zssp::crypto_impl::openssl::Aes256OpenSSLEnc as zssp::crypto::aes::Aes256Enc>::encrypt_in_place (28 samples, 0.07%)zssp::crypto_impl::openssl::CipherCtx::update (4 samples, 0.01%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::decrypt_in_place (68 samples, 0.18%)zssp::crypto_impl::openssl::CipherCtx::finalize (4 samples, 0.01%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::finish (6 samples, 0.02%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::encrypt (102 samples, 0.26%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_dec (4 samples, 0.01%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_enc (5 samples, 0.01%)CRYPTO_gcm128_decrypt (8 samples, 0.02%)CRYPTO_gcm128_decrypt_ctr32 (22 samples, 0.06%)CRYPTO_gcm128_encrypt (17 samples, 0.04%)CRYPTO_gcm128_encrypt_ctr32 (15 samples, 0.04%)CRYPTO_gcm128_finish (7 samples, 0.02%)CRYPTO_gcm128_tag (4 samples, 0.01%)EVP_CIPHER_CTX_ctrl (9 samples, 0.02%)EVP_CIPHER_CTX_set_padding (14 samples, 0.04%)EVP_CIPHER_get_block_size (12 samples, 0.03%)EVP_CipherInit_ex (9 samples, 0.02%)EVP_DecryptUpdate (13 samples, 0.03%)EVP_EncryptFinal_ex (5 samples, 0.01%)OSSL_PARAM_locate (103 samples, 0.27%)[benchmark] (6 samples, 0.02%)EVP_DecryptUpdate (6 samples, 0.02%)[libc.so.6] (82 samples, 0.21%)[libcrypto.so.3] (124 samples, 0.32%)__lll_lock_wake_private (4 samples, 0.01%)malloc (13 samples, 0.03%)std::sync::mpmc::Sender<T>::send (5 samples, 0.01%)<std::sync::mpmc::select::Token as core::default::Default>::default (8 samples, 0.02%)std::sync::mpmc::array::Channel<T>::start_send (14 samples, 0.04%)std::sync::mpmc::array::Channel<T>::send (53 samples, 0.14%)std::sync::mpmc::array::Channel<T>::write (10 samples, 0.03%)std::sync::mpmc::array::Channel<T>::start_recv (8 samples, 0.02%)<std::sync::mpmc::select::Token as core::default::Default>::default (5 samples, 0.01%)std::sync::mpmc::array::Channel<T>::try_recv (24 samples, 0.06%)std::sync::mpmc::array::Channel<T>::read (8 samples, 0.02%)std::sync::mpmc::utils::Backoff::new (9 samples, 0.02%)std::sync::mpmc::waker::SyncWaker::notify (13 samples, 0.03%)core::sync::atomic::AtomicBool::load (8 samples, 0.02%)core::sync::atomic::atomic_load (8 samples, 0.02%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (7 samples, 0.02%)<std::time::Instant as core::ops::arith::Sub>::sub (4 samples, 0.01%)std::time::Instant::duration_since (4 samples, 0.01%)std::time::Instant::checked_duration_since (4 samples, 0.01%)std::sys::unix::time::inner::Instant::checked_sub_instant (4 samples, 0.01%)std::time::Instant::elapsed (21 samples, 0.05%)syscall (12 samples, 0.03%)__entry_text_start (10 samples, 0.03%)[anon] (912 samples, 2.35%)[..zssp::zeta::receive_payload_in_place (23 samples, 0.06%)std::io::impls::<impl std::io::Write for &mut W>::write (5 samples, 0.01%)<arrayvec::arrayvec::ArrayVec<u8,_> as std::io::Write>::write (5 samples, 0.01%)arrayvec::arrayvec::ArrayVec<T,_>::try_extend_from_slice (5 samples, 0.01%)core::intrinsics::copy_nonoverlapping (5 samples, 0.01%)[libc.so.6] (6 samples, 0.02%)__rust_probestack (5 samples, 0.01%)[benchmark] (38 samples, 0.10%)zssp::zssp::Context<Crypto>::receive (22 samples, 0.06%)CRYPTO_gcm128_finish (11 samples, 0.03%)[libcrypto.so.3] (93 samples, 0.24%)std::sync::mpmc::utils::Backoff::new (5 samples, 0.01%)[libcrypto.so.3] (123 samples, 0.32%)<core::num::nonzero::NonZeroU32 as core::hash::Hash>::hash (20 samples, 0.05%)<std::sync::mpmc::zero::ZeroToken as core::default::Default>::default (7 samples, 0.02%)<zssp::crypto_impl::openssl::Aes256OpenSSLDec as zssp::crypto::aes::Aes256Dec>::decrypt_in_place (17 samples, 0.04%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::decrypt_in_place (14 samples, 0.04%)zssp::crypto_impl::openssl::CipherCtx::update (14 samples, 0.04%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::encrypt (5 samples, 0.01%)zssp::crypto_impl::openssl::CipherCtx::update (5 samples, 0.01%)CRYPTO_gcm128_decrypt (6 samples, 0.02%)CRYPTO_gcm128_decrypt_ctr32 (25 samples, 0.06%)CRYPTO_gcm128_encrypt (18 samples, 0.05%)CRYPTO_gcm128_encrypt_ctr32 (33 samples, 0.08%)CRYPTO_gcm128_finish (4 samples, 0.01%)CRYPTO_gcm128_setiv (4 samples, 0.01%)EVP_CIPHER_CTX_ctrl (16 samples, 0.04%)EVP_CIPHER_CTX_get_iv_length (11 samples, 0.03%)EVP_CIPHER_get_block_size (4 samples, 0.01%)EVP_CipherInit_ex (4 samples, 0.01%)EVP_DecryptUpdate (46 samples, 0.12%)EVP_EncryptUpdate (45 samples, 0.12%)OSSL_PARAM_locate (33 samples, 0.08%)[[vdso]] (16 samples, 0.04%)[benchmark] (9 samples, 0.02%)EVP_DecryptUpdate (9 samples, 0.02%)[libc.so.6] (73 samples, 0.19%)[libcrypto.so.3] (329 samples, 0.85%)__bss_start (20 samples, 0.05%)[libcrypto.so.3] (20 samples, 0.05%)__lll_lock_wait_private (4 samples, 0.01%)__entry_text_start (35 samples, 0.09%)crng_make_state (5 samples, 0.01%)_copy_to_iter (38 samples, 0.10%)copyout (28 samples, 0.07%)copyout (5 samples, 0.01%)__memcpy (14 samples, 0.04%)chacha_block_generic (267 samples, 0.69%)chacha_permute (242 samples, 0.62%)__x64_sys_getrandom (440 samples, 1.13%)get_random_bytes_user (428 samples, 1.10%)crng_make_state (343 samples, 0.88%)crng_fast_key_erasure (302 samples, 0.78%)exit_to_user_mode_prepare (19 samples, 0.05%)do_syscall_64 (474 samples, 1.22%)syscall_exit_to_user_mode (24 samples, 0.06%)entry_SYSCALL_64_after_hwframe (494 samples, 1.27%)syscall_exit_to_user_mode (4 samples, 0.01%)<rand_core::os::OsRng as rand_core::RngCore>::next_u64 (593 samples, 1.53%)rand_core::impls::next_u64_via_fill (593 samples, 1.53%)<rand_core::os::OsRng as rand_core::RngCore>::fill_bytes (593 samples, 1.53%)<rand_core::os::OsRng as rand_core::RngCore>::try_fill_bytes (587 samples, 1.51%)getrandom::getrandom (587 samples, 1.51%)getrandom::getrandom_uninit (587 samples, 1.51%)getrandom::imp::getrandom_inner (586 samples, 1.51%)getrandom::util_libc::sys_fill_exact (583 samples, 1.50%)getrandom::imp::getrandom_inner::_{{closure}} (577 samples, 1.49%)getrandom::imp::getrandom (577 samples, 1.49%)syscall (575 samples, 1.48%)syscall_return_via_sysret (9 samples, 0.02%)[libc.so.6] (20 samples, 0.05%)arrayvec::arrayvec::ArrayVec<T,_>::clear (7 samples, 0.02%)arrayvec::arrayvec_impl::ArrayVecImpl::clear (7 samples, 0.02%)arrayvec::arrayvec_impl::ArrayVecImpl::truncate (7 samples, 0.02%)core::ptr::drop_in_place<alloc::sync::Arc<zssp::zeta::Session<benchmark::TestApplication>>> (12 samples, 0.03%)<alloc::sync::Arc<T> as core::ops::drop::Drop>::drop (12 samples, 0.03%)core::sync::atomic::AtomicUsize::fetch_sub (12 samples, 0.03%)core::sync::atomic::atomic_sub (12 samples, 0.03%)<std::sync::mpmc::select::Token as core::default::Default>::default (5 samples, 0.01%)_ZN3std4sync4mpmc5array16Channel$LT$T$GT$10start_recv17h7800ca29c64cb868E.llvm.12455019271255371362 (9 samples, 0.02%)core::mem::maybe_uninit::MaybeUninit<T>::assume_init (20 samples, 0.05%)std::sync::mpmc::array::Channel<T>::read (25 samples, 0.06%)std::sync::mpmc::waker::SyncWaker::notify (5 samples, 0.01%)core::sync::atomic::AtomicUsize::compare_exchange_weak (44 samples, 0.11%)core::sync::atomic::atomic_compare_exchange_weak (44 samples, 0.11%)core::sync::atomic::AtomicUsize::load (177 samples, 0.46%)core::sync::atomic::atomic_load (177 samples, 0.46%)core::sync::atomic::fence (10 samples, 0.03%)std::sync::mpsc::Receiver<T>::try_recv (337 samples, 0.87%)std::sync::mpmc::Receiver<T>::try_recv (337 samples, 0.87%)std::sync::mpmc::array::Channel<T>::try_recv (326 samples, 0.84%)std::sync::mpmc::array::Channel<T>::start_recv (270 samples, 0.70%)<std::time::Instant as core::ops::arith::Sub>::sub (8 samples, 0.02%)std::time::Instant::duration_since (8 samples, 0.02%)std::time::Instant::checked_duration_since (8 samples, 0.02%)std::sys::unix::time::inner::Instant::checked_sub_instant (8 samples, 0.02%)std::sys::unix::time::Timespec::sub_timespec (8 samples, 0.02%)[[vdso]] (33 samples, 0.08%)[[vdso]] (19 samples, 0.05%)std::time::Instant::elapsed (44 samples, 0.11%)std::time::Instant::now (36 samples, 0.09%)std::sys::unix::time::inner::Instant::now (36 samples, 0.09%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (35 samples, 0.09%)clock_gettime (35 samples, 0.09%)<T as core::convert::TryInto<U>>::try_into (401 samples, 1.03%)core::array::_<impl core::convert::TryFrom<&[T]> for [T: N]>::try_from (401 samples, 1.03%)core::result::Result<T,E>::map (401 samples, 1.03%)<core::result::Result<T,E> as core::ops::try_trait::Try>::branch (4 samples, 0.01%)<zssp::crypto_impl::openssl::Aes256OpenSSLDec as zssp::crypto::aes::Aes256Dec>::decrypt_in_place (5 samples, 0.01%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (71 samples, 0.18%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (71 samples, 0.18%)std::sys::unix::locks::futex_mutex::Mutex::unlock (64 samples, 0.16%)core::sync::atomic::AtomicU32::swap (60 samples, 0.15%)core::sync::atomic::atomic_swap (60 samples, 0.15%)std::sync::mutex::MutexGuard<T>::new (5 samples, 0.01%)std::sync::poison::Flag::guard (5 samples, 0.01%)std::thread::panicking (5 samples, 0.01%)std::panicking::panicking (5 samples, 0.01%)std::panicking::panic_count::count_is_zero (5 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (77 samples, 0.20%)std::sys::unix::locks::futex_mutex::Mutex::lock (72 samples, 0.19%)core::sync::atomic::AtomicU32::compare_exchange (70 samples, 0.18%)core::sync::atomic::atomic_compare_exchange (70 samples, 0.18%)EVP_CIPHER_CTX_get_block_size (5 samples, 0.01%)<zssp::crypto_impl::openssl::Aes256OpenSSLDec as zssp::crypto::aes::Aes256Dec>::decrypt_in_place (394 samples, 1.01%)zssp::crypto_impl::openssl::CipherCtx::update (243 samples, 0.63%)EVP_DecryptUpdate (242 samples, 0.62%)[libcrypto.so.3] (204 samples, 0.53%)[libcrypto.so.3] (152 samples, 0.39%)[libcrypto.so.3] (144 samples, 0.37%)__rust_probestack (9 samples, 0.02%)core::cmp::Ord::max (4 samples, 0.01%)zssp::zssp::Context<Crypto>::receive (4 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)core::num::nonzero::NonZeroU32::new (13 samples, 0.03%)asm_sysvec_reschedule_ipi (10 samples, 0.03%)sysvec_reschedule_ipi (10 samples, 0.03%)irqentry_exit (10 samples, 0.03%)irqentry_exit_to_user_mode (10 samples, 0.03%)exit_to_user_mode_prepare (10 samples, 0.03%)exit_to_user_mode_loop (10 samples, 0.03%)schedule (10 samples, 0.03%)__schedule (10 samples, 0.03%)finish_task_switch.isra.0 (10 samples, 0.03%)__perf_event_task_sched_in (10 samples, 0.03%)perf_pmu_nop_void (5 samples, 0.01%)alloc::sync::Weak<T>::upgrade::_{{closure}} (9 samples, 0.02%)core::sync::atomic::AtomicUsize::compare_exchange_weak (61 samples, 0.16%)core::sync::atomic::atomic_compare_exchange_weak (61 samples, 0.16%)alloc::sync::Weak<T>::upgrade (81 samples, 0.21%)core::sync::atomic::AtomicUsize::fetch_update (78 samples, 0.20%)core::sync::atomic::AtomicUsize::load (4 samples, 0.01%)core::sync::atomic::atomic_load (4 samples, 0.01%)core::option::Option<T>::map (82 samples, 0.21%)zssp::zssp::Context<Crypto>::receive::_{{closure}} (82 samples, 0.21%)core::ptr::drop_in_place<alloc::sync::Arc<zssp::zeta::Session<benchmark::TestApplication>>> (55 samples, 0.14%)<alloc::sync::Arc<T> as core::ops::drop::Drop>::drop (55 samples, 0.14%)core::sync::atomic::AtomicUsize::fetch_sub (53 samples, 0.14%)core::sync::atomic::atomic_sub (53 samples, 0.14%)__rdl_dealloc (4 samples, 0.01%)std::sys::unix::alloc::<impl core::alloc::global::GlobalAlloc for std::alloc::System>::dealloc (4 samples, 0.01%)__rust_dealloc (5 samples, 0.01%)[libc.so.6] (48 samples, 0.12%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_pmu_nop_void (5 samples, 0.01%)__entry_text_start (14 samples, 0.04%)futex_unqueue (10 samples, 0.03%)update_curr (11 samples, 0.03%)cpuacct_charge (5 samples, 0.01%)update_load_avg (8 samples, 0.02%)dequeue_entity (28 samples, 0.07%)dequeue_task (37 samples, 0.10%)dequeue_task_fair (36 samples, 0.09%)__perf_event_task_sched_in (8 samples, 0.02%)perf_ctx_enable (8 samples, 0.02%)finish_task_switch.isra.0 (18 samples, 0.05%)raw_spin_rq_unlock (9 samples, 0.02%)pick_next_task_fair (4 samples, 0.01%)newidle_balance (4 samples, 0.01%)pick_next_task (11 samples, 0.03%)prepare_task_switch (14 samples, 0.04%)__perf_event_task_sched_out (5 samples, 0.01%)psi_group_change (26 samples, 0.07%)psi_task_switch (44 samples, 0.11%)__schedule (149 samples, 0.38%)futex_wait_queue (165 samples, 0.42%)schedule (159 samples, 0.41%)__get_user_nocheck_4 (16 samples, 0.04%)_raw_spin_lock (5 samples, 0.01%)futex_q_lock (4 samples, 0.01%)futex_q_unlock (7 samples, 0.02%)futex_wait_setup (41 samples, 0.11%)futex_wait (230 samples, 0.59%)do_futex (236 samples, 0.61%)__x64_sys_futex (247 samples, 0.64%)__rseq_handle_notify_resume (7 samples, 0.02%)exit_to_user_mode_loop (21 samples, 0.05%)exit_to_user_mode_prepare (27 samples, 0.07%)do_syscall_64 (283 samples, 0.73%)syscall_exit_to_user_mode (31 samples, 0.08%)[libc.so.6] (785 samples, 2.02%)[..__lll_lock_wait_private (346 samples, 0.89%)entry_SYSCALL_64_after_hwframe (288 samples, 0.74%)__entry_text_start (13 samples, 0.03%)futex_hash (7 samples, 0.02%)_raw_spin_lock (25 samples, 0.06%)native_queued_spin_lock_slowpath (25 samples, 0.06%)do_futex (66 samples, 0.17%)futex_wake (54 samples, 0.14%)__x64_sys_futex (72 samples, 0.19%)exit_to_user_mode_prepare (10 samples, 0.03%)do_syscall_64 (90 samples, 0.23%)syscall_exit_to_user_mode (16 samples, 0.04%)entry_SYSCALL_64_after_hwframe (98 samples, 0.25%)alloc::alloc::dealloc (965 samples, 2.48%)al..cfree (956 samples, 2.46%)cf..__lll_lock_wake_private (119 samples, 0.31%)<alloc::alloc::Global as core::alloc::Allocator>::deallocate (970 samples, 2.50%)<a..zssp::zssp::Context<Crypto>::receive (5 samples, 0.01%)core::ptr::drop_in_place<arrayvec::arrayvec::ArrayVec<alloc::vec::Vec<u8>,48_usize>> (986 samples, 2.54%)co..<arrayvec::arrayvec::ArrayVec<T,_> as core::ops::drop::Drop>::drop (986 samples, 2.54%)<a..arrayvec::arrayvec::ArrayVec<T,_>::clear (986 samples, 2.54%)ar..arrayvec::arrayvec_impl::ArrayVecImpl::clear (986 samples, 2.54%)ar..arrayvec::arrayvec_impl::ArrayVecImpl::truncate (986 samples, 2.54%)ar..core::ptr::drop_in_place<[alloc::vec::Vec<u8>]> (984 samples, 2.53%)co..core::ptr::drop_in_place<alloc::vec::Vec<u8>> (981 samples, 2.53%)co..core::ptr::drop_in_place<alloc::raw_vec::RawVec<u8>> (981 samples, 2.53%)co..<alloc::raw_vec::RawVec<T,A> as core::ops::drop::Drop>::drop (981 samples, 2.53%)<a..alloc::raw_vec::RawVec<T,A>::current_memory (8 samples, 0.02%)core::alloc::layout::Layout::array (6 samples, 0.02%)core::alloc::layout::Layout::array::inner (6 samples, 0.02%)std::sync::poison::Flag::done (10 samples, 0.03%)std::thread::panicking (10 samples, 0.03%)std::panicking::panicking (10 samples, 0.03%)std::panicking::panic_count::count_is_zero (10 samples, 0.03%)core::sync::atomic::AtomicUsize::load (9 samples, 0.02%)core::sync::atomic::atomic_load (9 samples, 0.02%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (78 samples, 0.20%)std::sys::unix::locks::futex_mutex::Mutex::unlock (68 samples, 0.18%)core::sync::atomic::AtomicU32::swap (66 samples, 0.17%)core::sync::atomic::atomic_swap (66 samples, 0.17%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::fragged::Fragged<alloc::vec::Vec<u8>,48_usize>>> (88 samples, 0.23%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::fragged::Fragged<alloc::vec::Vec<u8>,48_usize>>> (5 samples, 0.01%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<std::collections::hash::map::HashMap<core::num::nonzero::NonZeroU32,alloc::sync::Weak<zssp::zeta::Session<benchmark::TestApplication>>>>> (85 samples, 0.22%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (85 samples, 0.22%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (85 samples, 0.22%)core::sync::atomic::AtomicU32::fetch_sub (76 samples, 0.20%)core::sync::atomic::atomic_sub (76 samples, 0.20%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<zssp::zeta::MutableState<benchmark::TestApplication>>> (66 samples, 0.17%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (66 samples, 0.17%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (66 samples, 0.17%)core::sync::atomic::AtomicU32::fetch_sub (57 samples, 0.15%)core::sync::atomic::atomic_sub (57 samples, 0.15%)core::num::<impl u64>::rotate_left (4 samples, 0.01%)<core::hash::sip::Sip13Rounds as core::hash::sip::Sip>::c_rounds (20 samples, 0.05%)core::num::<impl u64>::wrapping_add (4 samples, 0.01%)core::num::<impl u64>::rotate_left (7 samples, 0.02%)<std::collections::hash::map::DefaultHasher as core::hash::Hasher>::finish (62 samples, 0.16%)<core::hash::sip::SipHasher13 as core::hash::Hasher>::finish (62 samples, 0.16%)<core::hash::sip::Hasher<S> as core::hash::Hasher>::finish (62 samples, 0.16%)<core::hash::sip::Sip13Rounds as core::hash::sip::Sip>::d_rounds (32 samples, 0.08%)core::num::<impl u64>::wrapping_add (14 samples, 0.04%)<std::collections::hash::map::RandomState as core::hash::BuildHasher>::build_hasher (17 samples, 0.04%)core::hash::sip::SipHasher13::new_with_keys (8 samples, 0.02%)core::hash::sip::Hasher<S>::new_with_keys (8 samples, 0.02%)core::hash::sip::Hasher<S>::reset (8 samples, 0.02%)hashbrown::map::make_hash (104 samples, 0.27%)core::hash::BuildHasher::hash_one (104 samples, 0.27%)core::hash::impls::<impl core::hash::Hash for &T>::hash (23 samples, 0.06%)<core::num::nonzero::NonZeroU32 as core::hash::Hash>::hash (23 samples, 0.06%)core::hash::impls::<impl core::hash::Hash for u32>::hash (23 samples, 0.06%)core::hash::Hasher::write_u32 (23 samples, 0.06%)<std::collections::hash::map::DefaultHasher as core::hash::Hasher>::write (23 samples, 0.06%)<core::hash::sip::SipHasher13 as core::hash::Hasher>::write (23 samples, 0.06%)<core::hash::sip::Hasher<S> as core::hash::Hasher>::write (23 samples, 0.06%)core::hash::sip::u8to64_le (15 samples, 0.04%)hashbrown::raw::RawTable<T,A>::find::_{{closure}} (5 samples, 0.01%)hashbrown::raw::h2 (8 samples, 0.02%)hashbrown::raw::sse2::Group::load (29 samples, 0.07%)core::core_arch::x86::sse2::_mm_loadu_si128 (29 samples, 0.07%)core::intrinsics::copy_nonoverlapping (29 samples, 0.07%)hashbrown::map::HashMap<K,V,S,A>::get_inner (163 samples, 0.42%)hashbrown::raw::RawTable<T,A>::get (59 samples, 0.15%)hashbrown::raw::RawTable<T,A>::find (59 samples, 0.15%)hashbrown::raw::RawTableInner<A>::find_inner (59 samples, 0.15%)hashbrown::raw::sse2::Group::match_byte (7 samples, 0.02%)core::core_arch::x86::sse2::_mm_movemask_epi8 (7 samples, 0.02%)std::collections::hash::map::HashMap<K,V,S>::get (164 samples, 0.42%)hashbrown::map::HashMap<K,V,S,A>::get (164 samples, 0.42%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (42 samples, 0.11%)std::sys::unix::locks::futex_mutex::Mutex::lock (38 samples, 0.10%)core::sync::atomic::AtomicU32::compare_exchange (30 samples, 0.08%)core::sync::atomic::atomic_compare_exchange (30 samples, 0.08%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)core::sync::atomic::AtomicU32::compare_exchange_weak (128 samples, 0.33%)core::sync::atomic::atomic_compare_exchange_weak (128 samples, 0.33%)std::sync::rwlock::RwLock<T>::read (162 samples, 0.42%)std::sys::unix::locks::futex_rwlock::RwLock::read (162 samples, 0.42%)std::sys::unix::locks::futex_rwlock::is_read_lockable (12 samples, 0.03%)core::num::<impl u64>::wrapping_add (5 samples, 0.01%)zssp::antireplay::Window<_,_>::check (13 samples, 0.03%)core::sync::atomic::AtomicU64::load (7 samples, 0.02%)core::sync::atomic::atomic_load (7 samples, 0.02%)arrayvec::arrayvec::ArrayVec<T,_>::push (20 samples, 0.05%)arrayvec::arrayvec_impl::ArrayVecImpl::push (20 samples, 0.05%)arrayvec::arrayvec_impl::ArrayVecImpl::try_push (20 samples, 0.05%)arrayvec::arrayvec_impl::ArrayVecImpl::push_unchecked (13 samples, 0.03%)core::ptr::write (12 samples, 0.03%)core::array::equality::_<impl core::cmp::PartialEq<[B: N]> for [A: N]>::ne (5 samples, 0.01%)<T as core::array::equality::SpecArrayEq<U,_>>::spec_ne (5 samples, 0.01%)<T as core::array::equality::SpecArrayEq<U,_>>::spec_eq (5 samples, 0.01%)core::mem::maybe_uninit::MaybeUninit<T>::assume_init_read (7 samples, 0.02%)core::ptr::const_ptr::<impl *const T>::read (7 samples, 0.02%)core::ptr::read (7 samples, 0.02%)core::mem::maybe_uninit::MaybeUninit<T>::assume_init (7 samples, 0.02%)core::num::<impl u64>::wrapping_shl (6 samples, 0.02%)core::num::<impl u64>::unchecked_shl (6 samples, 0.02%)zssp::fragged::Fragged<Fragment,_>::assemble (98 samples, 0.25%)zssp::fragged::Fragged<Fragment,_>::drop_in_place (5 samples, 0.01%)<T as core::convert::TryInto<U>>::try_into (26 samples, 0.07%)core::array::_<impl core::convert::TryFrom<&[T]> for [T: N]>::try_from (26 samples, 0.07%)core::result::Result<T,E>::map (26 samples, 0.07%)zssp::zeta::from_nonce (32 samples, 0.08%)core::num::<impl u64>::from_be_bytes (6 samples, 0.02%)core::num::<impl u64>::from_be (6 samples, 0.02%)core::num::<impl u64>::swap_bytes (6 samples, 0.02%)<alloc::vec::Vec<T,A> as core::convert::AsMut<[T]>>::as_mut (4 samples, 0.01%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (69 samples, 0.18%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (69 samples, 0.18%)core::sync::atomic::AtomicU32::fetch_sub (68 samples, 0.18%)core::sync::atomic::atomic_sub (68 samples, 0.18%)CRYPTO_gcm128_decrypt (249 samples, 0.64%)[libcrypto.so.3] (120 samples, 0.31%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)CRYPTO_gcm128_decrypt_ctr32 (478 samples, 1.23%)[libcrypto.so.3] (360 samples, 0.93%)[libcrypto.so.3] (22 samples, 0.06%)CRYPTO_gcm128_setiv (23 samples, 0.06%)[libcrypto.so.3] (20 samples, 0.05%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::decrypt_in_place (4,848 samples, 12.48%)<zssp::crypto_impl:..zssp::crypto_impl::openssl::CipherCtx::update (4,846 samples, 12.48%)zssp::crypto_impl::..EVP_DecryptUpdate (4,846 samples, 12.48%)EVP_DecryptUpdate[libcrypto.so.3] (4,745 samples, 12.22%)[libcrypto.so.3][libcrypto.so.3] (4,741 samples, 12.21%)[libcrypto.so.3][libcrypto.so.3] (4,719 samples, 12.15%)[libcrypto.so.3][libcrypto.so.3] (3,937 samples, 10.14%)[libcrypto.so.3][libcrypto.so.3] (3,769 samples, 9.71%)[libcrypto.so...asm_sysvec_apic_timer_interrupt (6 samples, 0.02%)sysvec_apic_timer_interrupt (6 samples, 0.02%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (4 samples, 0.01%)__schedule (4 samples, 0.01%)finish_task_switch.isra.0 (4 samples, 0.01%)__perf_event_task_sched_in (4 samples, 0.01%)perf_ctx_enable (4 samples, 0.01%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::AesGcmOpenSSLDec> (13 samples, 0.03%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (13 samples, 0.03%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (13 samples, 0.03%)std::sys::unix::locks::futex_mutex::Mutex::unlock (12 samples, 0.03%)core::sync::atomic::AtomicU32::swap (12 samples, 0.03%)core::sync::atomic::atomic_swap (12 samples, 0.03%)[libc.so.6] (6 samples, 0.02%)[libcrypto.so.3] (20 samples, 0.05%)CRYPTO_gcm128_finish (40 samples, 0.10%)zssp::crypto_impl::openssl::CipherCtx::finalize (59 samples, 0.15%)EVP_DecryptFinal_ex (59 samples, 0.15%)[libcrypto.so.3] (50 samples, 0.13%)[libcrypto.so.3] (50 samples, 0.13%)[libcrypto.so.3] (46 samples, 0.12%)OSSL_PARAM_get_octet_string (5 samples, 0.01%)[libcrypto.so.3] (4 samples, 0.01%)OSSL_PARAM_locate (27 samples, 0.07%)[libc.so.6] (16 samples, 0.04%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::finish (145 samples, 0.37%)zssp::crypto_impl::openssl::CipherCtx::set_tag (70 samples, 0.18%)EVP_CIPHER_CTX_ctrl (70 samples, 0.18%)[libcrypto.so.3] (40 samples, 0.10%)std::sync::mutex::Mutex<T>::lock (18 samples, 0.05%)std::sys::unix::locks::futex_mutex::Mutex::lock (18 samples, 0.05%)core::sync::atomic::AtomicU32::compare_exchange (17 samples, 0.04%)core::sync::atomic::atomic_compare_exchange (17 samples, 0.04%)[libc.so.6] (14 samples, 0.04%)OSSL_PARAM_locate (37 samples, 0.10%)strcmp@plt (5 samples, 0.01%)EVP_CIPHER_CTX_get_iv_length (70 samples, 0.18%)[libcrypto.so.3] (47 samples, 0.12%)[libc.so.6] (22 samples, 0.06%)OSSL_PARAM_locate (50 samples, 0.13%)strcmp@plt (5 samples, 0.01%)EVP_CIPHER_CTX_set_padding (92 samples, 0.24%)[libcrypto.so.3] (72 samples, 0.19%)OSSL_PARAM_locate_const (5 samples, 0.01%)EVP_CIPHER_free (13 samples, 0.03%)EVP_CIPHER_up_ref (46 samples, 0.12%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_dec (292 samples, 0.75%)zssp::crypto_impl::openssl::CipherCtx::cipher_init (273 samples, 0.70%)EVP_CipherInit_ex (272 samples, 0.70%)[libcrypto.so.3] (272 samples, 0.70%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::index_mut (6 samples, 0.02%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::get_unchecked_mut (4 samples, 0.01%)<core::ops::range::Range<usize> as core::slice::index::SliceIndex<[T]>>::get_unchecked_mut (4 samples, 0.01%)core::slice::index::<impl core::ops::index::IndexMut<I> for [T]>::index_mut (7 samples, 0.02%)arrayvec::arrayvec::ArrayVec<T,_>::get_unchecked_ptr (6 samples, 0.02%)core::ptr::mut_ptr::<impl *mut T>::add (6 samples, 0.02%)core::ptr::mut_ptr::<impl *mut T>::offset (6 samples, 0.02%)std::io::impls::<impl std::io::Write for &mut W>::write (253 samples, 0.65%)<arrayvec::arrayvec::ArrayVec<u8,_> as std::io::Write>::write (253 samples, 0.65%)arrayvec::arrayvec::ArrayVec<T,_>::try_extend_from_slice (250 samples, 0.64%)core::intrinsics::copy_nonoverlapping (244 samples, 0.63%)[libc.so.6] (242 samples, 0.62%)zssp::antireplay::Window<_,_>::update (15 samples, 0.04%)core::sync::atomic::AtomicU64::fetch_max (15 samples, 0.04%)core::sync::atomic::atomic_umax (15 samples, 0.04%)zssp::zeta::receive_payload_in_place (5,655 samples, 14.56%)zssp::zeta::receive_pa..zssp::zssp::Context<Crypto>::receive (8,525 samples, 21.95%)zssp::zssp::Context<Crypto>::receivezssp::zssp::parse_fragment_header (74 samples, 0.19%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (60 samples, 0.15%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (60 samples, 0.15%)std::sys::unix::locks::futex_mutex::Mutex::unlock (59 samples, 0.15%)core::sync::atomic::AtomicU32::swap (54 samples, 0.14%)core::sync::atomic::atomic_swap (54 samples, 0.14%)std::sync::mutex::Mutex<T>::lock (79 samples, 0.20%)std::sys::unix::locks::futex_mutex::Mutex::lock (77 samples, 0.20%)core::sync::atomic::AtomicU32::compare_exchange (74 samples, 0.19%)core::sync::atomic::atomic_compare_exchange (74 samples, 0.19%)<zssp::crypto_impl::openssl::Aes256OpenSSLEnc as zssp::crypto::aes::Aes256Enc>::encrypt_in_place (325 samples, 0.84%)zssp::crypto_impl::openssl::CipherCtx::update (180 samples, 0.46%)EVP_EncryptUpdate (180 samples, 0.46%)[libcrypto.so.3] (154 samples, 0.40%)[libcrypto.so.3] (123 samples, 0.32%)[libcrypto.so.3] (115 samples, 0.30%)<std::sync::mutex::MutexGuard<T> as core::ops::deref::Deref>::deref (5 samples, 0.01%)CRYPTO_gcm128_encrypt (264 samples, 0.68%)[libcrypto.so.3] (144 samples, 0.37%)[libcrypto.so.3] (20 samples, 0.05%)CRYPTO_gcm128_encrypt_ctr32 (501 samples, 1.29%)[libcrypto.so.3] (349 samples, 0.90%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)CRYPTO_gcm128_setiv (23 samples, 0.06%)[libcrypto.so.3] (20 samples, 0.05%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::encrypt (3,870 samples, 9.97%)<zssp::crypto_..zssp::crypto_impl::openssl::CipherCtx::update (3,865 samples, 9.95%)zssp::crypto_i..EVP_EncryptUpdate (3,859 samples, 9.94%)EVP_EncryptUpd..[libcrypto.so.3] (3,809 samples, 9.81%)[libcrypto.so...[libcrypto.so.3] (3,805 samples, 9.80%)[libcrypto.so...[libcrypto.so.3] (3,789 samples, 9.76%)[libcrypto.so...[libcrypto.so.3] (2,964 samples, 7.63%)[libcrypto..[libcrypto.so.3] (2,713 samples, 6.99%)[libcrypt..asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_pmu_nop_void (5 samples, 0.01%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc> (7 samples, 0.02%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (7 samples, 0.02%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (7 samples, 0.02%)std::sys::unix::locks::futex_mutex::Mutex::unlock (6 samples, 0.02%)core::sync::atomic::AtomicU32::swap (6 samples, 0.02%)core::sync::atomic::atomic_swap (6 samples, 0.02%)[libcrypto.so.3] (31 samples, 0.08%)zssp::crypto_impl::openssl::CipherCtx::finalize (75 samples, 0.19%)EVP_EncryptFinal_ex (75 samples, 0.19%)[libcrypto.so.3] (61 samples, 0.16%)[libcrypto.so.3] (59 samples, 0.15%)[libcrypto.so.3] (56 samples, 0.14%)CRYPTO_gcm128_tag (55 samples, 0.14%)CRYPTO_gcm128_finish (42 samples, 0.11%)[libc.so.6] (29 samples, 0.07%)OSSL_PARAM_locate (60 samples, 0.15%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::finish (177 samples, 0.46%)zssp::crypto_impl::openssl::CipherCtx::get_tag (94 samples, 0.24%)EVP_CIPHER_CTX_ctrl (94 samples, 0.24%)[libcrypto.so.3] (69 samples, 0.18%)OSSL_PARAM_set_octet_string (5 samples, 0.01%)[libc.so.6] (5 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (15 samples, 0.04%)std::sys::unix::locks::futex_mutex::Mutex::lock (13 samples, 0.03%)core::sync::atomic::AtomicU32::compare_exchange (11 samples, 0.03%)core::sync::atomic::atomic_compare_exchange (11 samples, 0.03%)[libc.so.6] (25 samples, 0.06%)EVP_CIPHER_CTX_get_iv_length (76 samples, 0.20%)[libcrypto.so.3] (53 samples, 0.14%)OSSL_PARAM_locate (46 samples, 0.12%)[libc.so.6] (37 samples, 0.10%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)OSSL_PARAM_locate (49 samples, 0.13%)strcmp@plt (5 samples, 0.01%)EVP_CIPHER_CTX_set_padding (95 samples, 0.24%)[libcrypto.so.3] (62 samples, 0.16%)OSSL_PARAM_locate_const (5 samples, 0.01%)EVP_CIPHER_free (14 samples, 0.04%)EVP_CIPHER_up_ref (28 samples, 0.07%)EVP_CipherInit_ex (245 samples, 0.63%)[libcrypto.so.3] (245 samples, 0.63%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_enc (262 samples, 0.67%)zssp::crypto_impl::openssl::CipherCtx::cipher_init (247 samples, 0.64%)__rdl_alloc (10 samples, 0.03%)std::sys::unix::alloc::<impl core::alloc::global::GlobalAlloc for std::alloc::System>::alloc (10 samples, 0.03%)__rust_alloc (5 samples, 0.01%)[libc.so.6] (74 samples, 0.19%)asm_exc_page_fault (5 samples, 0.01%)exc_page_fault (5 samples, 0.01%)do_user_addr_fault (5 samples, 0.01%)handle_mm_fault (5 samples, 0.01%)__handle_mm_fault (5 samples, 0.01%)handle_pte_fault (5 samples, 0.01%)do_anonymous_page (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)finish_task_switch.isra.0 (13 samples, 0.03%)__perf_event_task_sched_in (13 samples, 0.03%)perf_pmu_nop_void (8 samples, 0.02%)[libc.so.6] (1,111 samples, 2.86%)[l..asm_sysvec_reschedule_ipi (16 samples, 0.04%)sysvec_reschedule_ipi (16 samples, 0.04%)irqentry_exit (16 samples, 0.04%)irqentry_exit_to_user_mode (16 samples, 0.04%)exit_to_user_mode_prepare (16 samples, 0.04%)exit_to_user_mode_loop (16 samples, 0.04%)schedule (14 samples, 0.04%)__schedule (14 samples, 0.04%)__entry_text_start (10 samples, 0.03%)__get_user_nocheck_4 (9 samples, 0.02%)_raw_spin_lock (6 samples, 0.02%)futex_q_lock (10 samples, 0.03%)futex_q_unlock (12 samples, 0.03%)futex_wait_setup (44 samples, 0.11%)get_futex_key (4 samples, 0.01%)futex_wait (52 samples, 0.13%)__x64_sys_futex (57 samples, 0.15%)do_futex (54 samples, 0.14%)entry_SYSCALL_64_after_hwframe (62 samples, 0.16%)do_syscall_64 (60 samples, 0.15%)__lll_lock_wait_private (101 samples, 0.26%)__entry_text_start (7 samples, 0.02%)_raw_spin_lock (9 samples, 0.02%)_raw_spin_lock (12 samples, 0.03%)native_queued_spin_lock_slowpath (12 samples, 0.03%)futex_wake_mark (24 samples, 0.06%)__futex_unqueue (4 samples, 0.01%)__smp_call_single_queue (20 samples, 0.05%)native_send_call_func_single_ipi (17 samples, 0.04%)x2apic_send_IPI (17 samples, 0.04%)native_write_msr (15 samples, 0.04%)llist_add_batch (17 samples, 0.04%)try_to_wake_up (89 samples, 0.23%)ttwu_queue_wakelist (55 samples, 0.14%)futex_wake (196 samples, 0.50%)wake_up_q (96 samples, 0.25%)do_futex (210 samples, 0.54%)__x64_sys_futex (214 samples, 0.55%)entry_SYSCALL_64_after_hwframe (224 samples, 0.58%)do_syscall_64 (222 samples, 0.57%)__lll_lock_wake_private (239 samples, 0.62%)alloc::vec::Vec<T,A>::with_capacity_in (2,224 samples, 5.73%)alloc::..alloc::raw_vec::RawVec<T,A>::with_capacity_in (2,224 samples, 5.73%)alloc::..alloc::raw_vec::RawVec<T,A>::allocate_in (2,224 samples, 5.73%)alloc::..<alloc::alloc::Global as core::alloc::Allocator>::allocate (2,221 samples, 5.72%)<alloc:..alloc::alloc::Global::alloc_impl (2,221 samples, 5.72%)alloc::..alloc::alloc::alloc (2,221 samples, 5.72%)alloc::..malloc (2,206 samples, 5.68%)mallocalloc::slice::<impl [T]>::to_vec (2,451 samples, 6.31%)alloc::s..alloc::slice::<impl [T]>::to_vec_in (2,451 samples, 6.31%)alloc::s..alloc::slice::hack::to_vec (2,451 samples, 6.31%)alloc::s..<T as alloc::slice::hack::ConvertVec>::to_vec (2,451 samples, 6.31%)<T as al..core::ptr::const_ptr::<impl *const T>::copy_to_nonoverlapping (227 samples, 0.58%)core::intrinsics::copy_nonoverlapping (227 samples, 0.58%)[libc.so.6] (222 samples, 0.57%)<std::sync::mpmc::select::Token as core::default::Default>::default (5 samples, 0.01%)std::sync::mpmc::array::Channel<T>::send (4 samples, 0.01%)core::sync::atomic::AtomicUsize::compare_exchange_weak (80 samples, 0.21%)core::sync::atomic::atomic_compare_exchange_weak (80 samples, 0.21%)std::sync::mpmc::array::Channel<T>::start_send (113 samples, 0.29%)core::sync::atomic::AtomicUsize::load (7 samples, 0.02%)core::sync::atomic::atomic_load (7 samples, 0.02%)core::sync::atomic::AtomicU32::swap (6 samples, 0.02%)core::sync::atomic::atomic_swap (6 samples, 0.02%)futex_wake_mark (7 samples, 0.02%)__smp_call_single_queue (6 samples, 0.02%)__x64_sys_futex (57 samples, 0.15%)do_futex (57 samples, 0.15%)futex_wake (55 samples, 0.14%)wake_up_q (36 samples, 0.09%)try_to_wake_up (35 samples, 0.09%)ttwu_queue_wakelist (14 samples, 0.04%)std::sync::mpmc::array::Channel<T>::write (91 samples, 0.23%)std::sync::mpmc::waker::SyncWaker::notify (85 samples, 0.22%)std::sync::mpmc::waker::Waker::try_select (74 samples, 0.19%)<core::slice::iter::Iter<T> as core::iter::traits::iterator::Iterator>::position (74 samples, 0.19%)std::sync::mpmc::waker::Waker::try_select::_{{closure}} (74 samples, 0.19%)std::sync::mpmc::context::Context::unpark (72 samples, 0.19%)std::thread::Thread::unpark (72 samples, 0.19%)std::sys_common::thread_parking::futex::Parker::unpark (72 samples, 0.19%)std::sys::unix::futex::futex_wake (66 samples, 0.17%)syscall (66 samples, 0.17%)entry_SYSCALL_64_after_hwframe (61 samples, 0.16%)do_syscall_64 (61 samples, 0.16%)std::sync::mpmc::context::Context::wait_until (4 samples, 0.01%)std::thread::park (4 samples, 0.01%)std::sys_common::thread_parking::futex::Parker::park (4 samples, 0.01%)std::sys::unix::futex::futex_wait (4 samples, 0.01%)syscall (4 samples, 0.01%)entry_SYSCALL_64_after_hwframe (4 samples, 0.01%)do_syscall_64 (4 samples, 0.01%)std::sync::mpmc::context::Context::with (5 samples, 0.01%)std::thread::local::LocalKey<T>::try_with (5 samples, 0.01%)std::sync::mpmc::context::Context::with::_{{closure}} (5 samples, 0.01%)std::sync::mpmc::context::Context::with::_{{closure}} (5 samples, 0.01%)std::sync::mpmc::array::Channel<T>::send::_{{closure}} (5 samples, 0.01%)benchmark::bob_main::_{{closure}} (4,067 samples, 10.47%)benchmark::bob_..std::sync::mpsc::SyncSender<T>::send (1,615 samples, 4.16%)std::..std::sync::mpmc::Sender<T>::send (1,613 samples, 4.15%)std::..std::sync::mpmc::array::Channel<T>::send (265 samples, 0.68%)std::sync::mpmc::utils::Backoff::spin_light (25 samples, 0.06%)core::hint::spin_loop (25 samples, 0.06%)core::core_arch::x86::sse2::_mm_pause (25 samples, 0.06%)core::mem::drop (16 samples, 0.04%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<zssp::zeta::MutableState<benchmark::TestApplication>>> (16 samples, 0.04%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (16 samples, 0.04%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (16 samples, 0.04%)core::sync::atomic::AtomicU32::fetch_sub (15 samples, 0.04%)core::sync::atomic::atomic_sub (15 samples, 0.04%)core::slice::<impl [T]>::copy_from_slice (6 samples, 0.02%)core::intrinsics::copy_nonoverlapping (6 samples, 0.02%)core::slice::index::<impl core::ops::index::IndexMut<I> for [T]>::index_mut (4 samples, 0.01%)<core::ops::range::Range<usize> as core::slice::index::SliceIndex<[T]>>::index_mut (4 samples, 0.01%)std::sync::rwlock::RwLock<T>::read (23 samples, 0.06%)std::sys::unix::locks::futex_rwlock::RwLock::read (23 samples, 0.06%)core::sync::atomic::AtomicU32::compare_exchange_weak (21 samples, 0.05%)core::sync::atomic::atomic_compare_exchange_weak (21 samples, 0.05%)benchmark::alice_main (18,418 samples, 47.43%)benchmark::alice_mainzssp::zssp::Context<Crypto>::send (8,789 samples, 22.63%)zssp::zssp::Context<Crypto>::sendzssp::zeta::send_payload (8,789 samples, 22.63%)zssp::zeta::send_payloadzssp::zeta::get_counter (18 samples, 0.05%)core::sync::atomic::AtomicU64::fetch_add (16 samples, 0.04%)core::sync::atomic::atomic_add (16 samples, 0.04%)[libc.so.6] (15 samples, 0.04%)core::ptr::drop_in_place<alloc::sync::Arc<zssp::zeta::Session<benchmark::TestApplication>>> (15 samples, 0.04%)<alloc::sync::Arc<T> as core::ops::drop::Drop>::drop (15 samples, 0.04%)core::sync::atomic::AtomicUsize::fetch_sub (14 samples, 0.04%)core::sync::atomic::atomic_sub (14 samples, 0.04%)core::time::Duration::as_millis (8 samples, 0.02%)core::result::Result<T,E>::map_err (4 samples, 0.01%)std::sync::mpmc::array::Channel<T>::read (7 samples, 0.02%)std::sync::mpmc::waker::SyncWaker::notify (6 samples, 0.02%)core::slice::<impl [T]>::get_unchecked (6 samples, 0.02%)<usize as core::slice::index::SliceIndex<[T]>>::get_unchecked (6 samples, 0.02%)core::ptr::const_ptr::<impl *const T>::add (6 samples, 0.02%)core::ptr::const_ptr::<impl *const T>::offset (6 samples, 0.02%)core::sync::atomic::AtomicUsize::compare_exchange_weak (90 samples, 0.23%)core::sync::atomic::atomic_compare_exchange_weak (90 samples, 0.23%)core::sync::atomic::AtomicUsize::load (63 samples, 0.16%)core::sync::atomic::atomic_load (63 samples, 0.16%)std::sync::mpmc::array::Channel<T>::start_recv (200 samples, 0.52%)core::sync::atomic::AtomicUsize::load (5 samples, 0.01%)core::sync::atomic::atomic_load (5 samples, 0.01%)core::sync::atomic::AtomicU32::swap (4 samples, 0.01%)core::sync::atomic::atomic_swap (4 samples, 0.01%)core::option::Option<T>::and_then (7 samples, 0.02%)std::sys::unix::futex::futex_wait::_{{closure}} (7 samples, 0.02%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (7 samples, 0.02%)clock_gettime (7 samples, 0.02%)[[vdso]] (7 samples, 0.02%)[[vdso]] (4 samples, 0.01%)futex_unqueue (7 samples, 0.02%)enqueue_hrtimer (5 samples, 0.01%)hrtimer_sleeper_start_expires (6 samples, 0.02%)hrtimer_start_range_ns (6 samples, 0.02%)__hrtimer_start_range_ns (6 samples, 0.02%)update_curr (5 samples, 0.01%)dequeue_task (14 samples, 0.04%)dequeue_task_fair (14 samples, 0.04%)dequeue_entity (14 samples, 0.04%)update_load_avg (5 samples, 0.01%)__perf_event_task_sched_in (11 samples, 0.03%)perf_ctx_enable (11 samples, 0.03%)finish_task_switch.isra.0 (16 samples, 0.04%)raw_spin_rq_unlock (4 samples, 0.01%)pick_next_task (4 samples, 0.01%)prepare_task_switch (4 samples, 0.01%)psi_group_change (8 samples, 0.02%)psi_task_switch (13 samples, 0.03%)futex_wait_queue (78 samples, 0.20%)schedule (68 samples, 0.18%)__schedule (66 samples, 0.17%)__get_user_nocheck_4 (4 samples, 0.01%)futex_q_lock (10 samples, 0.03%)futex_wait_setup (18 samples, 0.05%)hrtimer_cancel (7 samples, 0.02%)hrtimer_try_to_cancel (6 samples, 0.02%)futex_wait (117 samples, 0.30%)do_futex (120 samples, 0.31%)__x64_sys_futex (130 samples, 0.33%)get_timespec64 (4 samples, 0.01%)_copy_from_user (4 samples, 0.01%)exit_to_user_mode_prepare (8 samples, 0.02%)exit_to_user_mode_loop (8 samples, 0.02%)__rseq_handle_notify_resume (5 samples, 0.01%)std::sys_common::thread_parking::futex::Parker::park_timeout (157 samples, 0.40%)std::sys::unix::futex::futex_wait (152 samples, 0.39%)syscall (145 samples, 0.37%)entry_SYSCALL_64_after_hwframe (140 samples, 0.36%)do_syscall_64 (140 samples, 0.36%)syscall_exit_to_user_mode (9 samples, 0.02%)std::thread::park_timeout (159 samples, 0.41%)std::sync::mpmc::context::Context::wait_until (168 samples, 0.43%)core::sync::atomic::AtomicBool::store (6 samples, 0.02%)core::sync::atomic::atomic_store (6 samples, 0.02%)std::sync::mpmc::context::Context::with (187 samples, 0.48%)std::thread::local::LocalKey<T>::try_with (187 samples, 0.48%)std::sync::mpmc::context::Context::with::_{{closure}} (187 samples, 0.48%)std::sync::mpmc::context::Context::with::_{{closure}} (187 samples, 0.48%)std::sync::mpmc::array::Channel<T>::recv::_{{closure}} (185 samples, 0.48%)std::sync::mpmc::waker::SyncWaker::register (15 samples, 0.04%)std::sync::mutex::Mutex<T>::lock (6 samples, 0.02%)std::sys::unix::locks::futex_mutex::Mutex::lock (6 samples, 0.02%)core::sync::atomic::AtomicU32::compare_exchange (6 samples, 0.02%)core::sync::atomic::atomic_compare_exchange (6 samples, 0.02%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (4 samples, 0.01%)clock_gettime (4 samples, 0.01%)[[vdso]] (4 samples, 0.01%)std::sync::mpmc::Receiver<T>::recv_deadline (418 samples, 1.08%)std::sync::mpmc::array::Channel<T>::recv (416 samples, 1.07%)[[vdso]] (164 samples, 0.42%)[[vdso]] (114 samples, 0.29%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (175 samples, 0.45%)clock_gettime (173 samples, 0.45%)__vdso_clock_gettime (6 samples, 0.02%)std::sync::mpsc::Receiver<T>::recv_timeout (634 samples, 1.63%)std::sync::mpmc::Receiver<T>::recv_timeout (628 samples, 1.62%)std::time::SystemTime::checked_add (17 samples, 0.04%)std::sys::unix::time::SystemTime::checked_add_duration (16 samples, 0.04%)std::sys::unix::time::Timespec::checked_add_duration (16 samples, 0.04%)core::option::Option<T>::and_then (8 samples, 0.02%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)core::cmp::impls::<impl core::cmp::PartialOrd<&B> for &A>::ge (21 samples, 0.05%)core::cmp::PartialOrd::ge (21 samples, 0.05%)<std::sys::unix::time::Timespec as core::cmp::PartialOrd>::partial_cmp (15 samples, 0.04%)std::time::Instant::duration_since (45 samples, 0.12%)std::time::Instant::checked_duration_since (45 samples, 0.12%)std::sys::unix::time::inner::Instant::checked_sub_instant (45 samples, 0.12%)std::sys::unix::time::Timespec::sub_timespec (45 samples, 0.12%)core::time::Duration::new (5 samples, 0.01%)<std::time::Instant as core::ops::arith::Sub>::sub (48 samples, 0.12%)[[vdso]] (111 samples, 0.29%)[[vdso]] (145 samples, 0.37%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (4 samples, 0.01%)perf_ctx_enable (4 samples, 0.01%)std::time::Instant::elapsed (208 samples, 0.54%)std::time::Instant::now (158 samples, 0.41%)std::sys::unix::time::inner::Instant::now (158 samples, 0.41%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (155 samples, 0.40%)clock_gettime (151 samples, 0.39%)__vdso_clock_gettime (4 samples, 0.01%)<T as core::convert::TryInto<U>>::try_into (317 samples, 0.82%)core::array::_<impl core::convert::TryFrom<&[T]> for [T: N]>::try_from (317 samples, 0.82%)core::result::Result<T,E>::map (317 samples, 0.82%)<core::result::Result<T,E> as core::ops::try_trait::Try>::branch (6 samples, 0.02%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (76 samples, 0.20%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (76 samples, 0.20%)std::sys::unix::locks::futex_mutex::Mutex::unlock (75 samples, 0.19%)core::sync::atomic::AtomicU32::swap (71 samples, 0.18%)core::sync::atomic::atomic_swap (71 samples, 0.18%)std::sync::mutex::MutexGuard<T>::new (6 samples, 0.02%)std::sync::poison::Flag::guard (6 samples, 0.02%)std::thread::panicking (6 samples, 0.02%)std::panicking::panicking (6 samples, 0.02%)std::panicking::panic_count::count_is_zero (6 samples, 0.02%)std::sync::mutex::Mutex<T>::lock (73 samples, 0.19%)std::sys::unix::locks::futex_mutex::Mutex::lock (67 samples, 0.17%)core::sync::atomic::AtomicU32::compare_exchange (66 samples, 0.17%)core::sync::atomic::atomic_compare_exchange (66 samples, 0.17%)<zssp::crypto_impl::openssl::Aes256OpenSSLDec as zssp::crypto::aes::Aes256Dec>::decrypt_in_place (355 samples, 0.91%)zssp::crypto_impl::openssl::CipherCtx::update (206 samples, 0.53%)EVP_DecryptUpdate (202 samples, 0.52%)[libcrypto.so.3] (165 samples, 0.42%)[libcrypto.so.3] (125 samples, 0.32%)[libcrypto.so.3] (116 samples, 0.30%)__rust_probestack (6 samples, 0.02%)core::cmp::Ord::max (5 samples, 0.01%)zssp::zssp::Context<Crypto>::receive (5 samples, 0.01%)alloc::sync::Weak<T>::upgrade::_{{closure}} (6 samples, 0.02%)core::sync::atomic::AtomicUsize::compare_exchange_weak (73 samples, 0.19%)core::sync::atomic::atomic_compare_exchange_weak (73 samples, 0.19%)alloc::sync::Weak<T>::upgrade (92 samples, 0.24%)core::sync::atomic::AtomicUsize::fetch_update (88 samples, 0.23%)core::sync::atomic::AtomicUsize::load (6 samples, 0.02%)core::sync::atomic::atomic_load (6 samples, 0.02%)core::option::Option<T>::map (99 samples, 0.25%)zssp::zssp::Context<Crypto>::receive::_{{closure}} (99 samples, 0.25%)zssp::zssp::Context<Crypto>::receive (7 samples, 0.02%)core::ptr::drop_in_place<alloc::sync::Arc<zssp::zeta::Session<benchmark::TestApplication>>> (45 samples, 0.12%)<alloc::sync::Arc<T> as core::ops::drop::Drop>::drop (45 samples, 0.12%)core::sync::atomic::AtomicUsize::fetch_sub (43 samples, 0.11%)core::sync::atomic::atomic_sub (43 samples, 0.11%)__rdl_dealloc (4 samples, 0.01%)std::sys::unix::alloc::<impl core::alloc::global::GlobalAlloc for std::alloc::System>::dealloc (4 samples, 0.01%)__rust_dealloc (9 samples, 0.02%)[libc.so.6] (76 samples, 0.20%)__entry_text_start (13 samples, 0.03%)futex_unqueue (17 samples, 0.04%)_raw_spin_lock (4 samples, 0.01%)cpuacct_charge (10 samples, 0.03%)update_curr (18 samples, 0.05%)dequeue_entity (32 samples, 0.08%)update_load_avg (5 samples, 0.01%)dequeue_task (42 samples, 0.11%)dequeue_task_fair (42 samples, 0.11%)__perf_event_task_sched_in (9 samples, 0.02%)perf_ctx_enable (9 samples, 0.02%)x86_pmu_enable (9 samples, 0.02%)intel_pmu_enable_all (9 samples, 0.02%)native_write_msr (9 samples, 0.02%)finish_task_switch.isra.0 (29 samples, 0.07%)raw_spin_rq_unlock (12 samples, 0.03%)pick_next_task_fair (4 samples, 0.01%)pick_next_task (10 samples, 0.03%)put_prev_task_fair (6 samples, 0.02%)prepare_task_switch (15 samples, 0.04%)__perf_event_task_sched_out (6 samples, 0.02%)psi_group_change (38 samples, 0.10%)psi_task_switch (52 samples, 0.13%)__schedule (179 samples, 0.46%)update_rq_clock (5 samples, 0.01%)sched_clock_cpu (5 samples, 0.01%)sched_clock (4 samples, 0.01%)native_sched_clock (4 samples, 0.01%)futex_wait_queue (199 samples, 0.51%)schedule (187 samples, 0.48%)__get_user_nocheck_4 (26 samples, 0.07%)_raw_spin_lock (4 samples, 0.01%)futex_hash (5 samples, 0.01%)futex_q_lock (5 samples, 0.01%)futex_q_unlock (5 samples, 0.01%)futex_wait (278 samples, 0.72%)futex_wait_setup (52 samples, 0.13%)do_futex (282 samples, 0.73%)__x64_sys_futex (292 samples, 0.75%)__get_user_8 (5 samples, 0.01%)rseq_ip_fixup (11 samples, 0.03%)exit_to_user_mode_loop (24 samples, 0.06%)__rseq_handle_notify_resume (15 samples, 0.04%)exit_to_user_mode_prepare (31 samples, 0.08%)do_syscall_64 (331 samples, 0.85%)syscall_exit_to_user_mode (36 samples, 0.09%)__lll_lock_wait_private (397 samples, 1.02%)entry_SYSCALL_64_after_hwframe (335 samples, 0.86%)[libc.so.6] (911 samples, 2.35%)[..__entry_text_start (15 samples, 0.04%)do_syscall_64 (4 samples, 0.01%)_raw_spin_lock (5 samples, 0.01%)futex_hash (8 samples, 0.02%)_raw_spin_lock (25 samples, 0.06%)native_queued_spin_lock_slowpath (25 samples, 0.06%)_raw_spin_unlock (4 samples, 0.01%)preempt_schedule_thunk (4 samples, 0.01%)preempt_schedule (4 samples, 0.01%)__schedule (4 samples, 0.01%)finish_task_switch.isra.0 (4 samples, 0.01%)__perf_event_task_sched_in (4 samples, 0.01%)perf_ctx_enable (4 samples, 0.01%)futex_wake (77 samples, 0.20%)do_futex (97 samples, 0.25%)__x64_sys_futex (101 samples, 0.26%)do_syscall_64 (113 samples, 0.29%)syscall_exit_to_user_mode (6 samples, 0.02%)exit_to_user_mode_prepare (5 samples, 0.01%)alloc::alloc::dealloc (1,146 samples, 2.95%)all..cfree (1,133 samples, 2.92%)cf..__lll_lock_wake_private (146 samples, 0.38%)entry_SYSCALL_64_after_hwframe (120 samples, 0.31%)<alloc::alloc::Global as core::alloc::Allocator>::deallocate (1,151 samples, 2.96%)<al..zssp::zssp::Context<Crypto>::receive (5 samples, 0.01%)core::ptr::drop_in_place<arrayvec::arrayvec::ArrayVec<alloc::vec::Vec<u8>,48_usize>> (1,169 samples, 3.01%)cor..<arrayvec::arrayvec::ArrayVec<T,_> as core::ops::drop::Drop>::drop (1,169 samples, 3.01%)<ar..arrayvec::arrayvec::ArrayVec<T,_>::clear (1,169 samples, 3.01%)arr..arrayvec::arrayvec_impl::ArrayVecImpl::clear (1,169 samples, 3.01%)arr..arrayvec::arrayvec_impl::ArrayVecImpl::truncate (1,169 samples, 3.01%)arr..core::ptr::drop_in_place<[alloc::vec::Vec<u8>]> (1,162 samples, 2.99%)cor..core::ptr::drop_in_place<alloc::vec::Vec<u8>> (1,161 samples, 2.99%)cor..core::ptr::drop_in_place<alloc::raw_vec::RawVec<u8>> (1,161 samples, 2.99%)cor..<alloc::raw_vec::RawVec<T,A> as core::ops::drop::Drop>::drop (1,161 samples, 2.99%)<al..alloc::raw_vec::RawVec<T,A>::current_memory (9 samples, 0.02%)std::sync::poison::Flag::done (4 samples, 0.01%)std::thread::panicking (4 samples, 0.01%)std::panicking::panicking (4 samples, 0.01%)std::panicking::panic_count::count_is_zero (4 samples, 0.01%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (83 samples, 0.21%)std::sys::unix::locks::futex_mutex::Mutex::unlock (79 samples, 0.20%)core::sync::atomic::AtomicU32::swap (76 samples, 0.20%)core::sync::atomic::atomic_swap (76 samples, 0.20%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::fragged::Fragged<alloc::vec::Vec<u8>,48_usize>>> (87 samples, 0.22%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::fragged::Fragged<alloc::vec::Vec<u8>,48_usize>>> (4 samples, 0.01%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<std::collections::hash::map::HashMap<core::num::nonzero::NonZeroU32,alloc::sync::Weak<zssp::zeta::Session<benchmark::TestApplication>>>>> (88 samples, 0.23%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (88 samples, 0.23%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (88 samples, 0.23%)core::sync::atomic::AtomicU32::fetch_sub (80 samples, 0.21%)core::sync::atomic::atomic_sub (80 samples, 0.21%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<zssp::zeta::MutableState<benchmark::TestApplication>>> (59 samples, 0.15%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (59 samples, 0.15%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (59 samples, 0.15%)core::sync::atomic::AtomicU32::fetch_sub (54 samples, 0.14%)core::sync::atomic::atomic_sub (54 samples, 0.14%)core::slice::index::<impl core::ops::index::IndexMut<I> for [T]>::index_mut (6 samples, 0.02%)<core::ops::range::Range<usize> as core::slice::index::SliceIndex<[T]>>::index_mut (6 samples, 0.02%)<core::ops::range::Range<usize> as core::slice::index::SliceIndex<[T]>>::get_unchecked_mut (6 samples, 0.02%)core::ptr::mut_ptr::<impl *mut T>::add (6 samples, 0.02%)core::ptr::mut_ptr::<impl *mut T>::offset (6 samples, 0.02%)core::num::<impl u64>::rotate_left (9 samples, 0.02%)<core::hash::sip::Sip13Rounds as core::hash::sip::Sip>::c_rounds (22 samples, 0.06%)core::num::<impl u64>::wrapping_add (6 samples, 0.02%)core::num::<impl u64>::rotate_left (15 samples, 0.04%)<std::collections::hash::map::DefaultHasher as core::hash::Hasher>::finish (78 samples, 0.20%)<core::hash::sip::SipHasher13 as core::hash::Hasher>::finish (78 samples, 0.20%)<core::hash::sip::Hasher<S> as core::hash::Hasher>::finish (78 samples, 0.20%)<core::hash::sip::Sip13Rounds as core::hash::sip::Sip>::d_rounds (44 samples, 0.11%)core::num::<impl u64>::wrapping_add (20 samples, 0.05%)hashbrown::map::make_hash (98 samples, 0.25%)core::hash::BuildHasher::hash_one (98 samples, 0.25%)core::hash::impls::<impl core::hash::Hash for &T>::hash (15 samples, 0.04%)<core::num::nonzero::NonZeroU32 as core::hash::Hash>::hash (15 samples, 0.04%)core::hash::impls::<impl core::hash::Hash for u32>::hash (15 samples, 0.04%)core::hash::Hasher::write_u32 (15 samples, 0.04%)<std::collections::hash::map::DefaultHasher as core::hash::Hasher>::write (15 samples, 0.04%)<core::hash::sip::SipHasher13 as core::hash::Hasher>::write (15 samples, 0.04%)<core::hash::sip::Hasher<S> as core::hash::Hasher>::write (15 samples, 0.04%)core::hash::sip::u8to64_le (10 samples, 0.03%)<hashbrown::raw::bitmask::BitMaskIter as core::iter::traits::iterator::Iterator>::next (6 samples, 0.02%)hashbrown::map::equivalent_key::_{{closure}} (10 samples, 0.03%)<core::num::nonzero::NonZeroU32 as core::cmp::PartialEq>::eq (10 samples, 0.03%)hashbrown::raw::RawTable<T,A>::find::_{{closure}} (17 samples, 0.04%)hashbrown::raw::Bucket<T>::as_ref (7 samples, 0.02%)hashbrown::raw::Bucket<T>::as_ptr (7 samples, 0.02%)core::ptr::mut_ptr::<impl *mut T>::sub (7 samples, 0.02%)core::ptr::mut_ptr::<impl *mut T>::offset (7 samples, 0.02%)hashbrown::raw::h2 (14 samples, 0.04%)hashbrown::map::HashMap<K,V,S,A>::get_inner (149 samples, 0.38%)hashbrown::raw::RawTable<T,A>::get (51 samples, 0.13%)hashbrown::raw::RawTable<T,A>::find (51 samples, 0.13%)hashbrown::raw::RawTableInner<A>::find_inner (51 samples, 0.13%)hashbrown::raw::sse2::Group::match_byte (4 samples, 0.01%)core::core_arch::x86::sse2::_mm_movemask_epi8 (4 samples, 0.01%)std::collections::hash::map::HashMap<K,V,S>::get (153 samples, 0.39%)hashbrown::map::HashMap<K,V,S,A>::get (153 samples, 0.39%)zssp::zssp::Context<Crypto>::receive (4 samples, 0.01%)std::sync::mutex::MutexGuard<T>::new (5 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (50 samples, 0.13%)std::sys::unix::locks::futex_mutex::Mutex::lock (44 samples, 0.11%)core::sync::atomic::AtomicU32::compare_exchange (42 samples, 0.11%)core::sync::atomic::atomic_compare_exchange (42 samples, 0.11%)core::sync::atomic::AtomicU32::compare_exchange_weak (135 samples, 0.35%)core::sync::atomic::atomic_compare_exchange_weak (135 samples, 0.35%)std::sync::rwlock::RwLock<T>::read (158 samples, 0.41%)std::sys::unix::locks::futex_rwlock::RwLock::read (158 samples, 0.41%)std::sys::unix::locks::futex_rwlock::is_read_lockable (8 samples, 0.02%)zssp::antireplay::Window<_,_>::check (10 samples, 0.03%)core::sync::atomic::AtomicU64::load (6 samples, 0.02%)core::sync::atomic::atomic_load (6 samples, 0.02%)arrayvec::arrayvec::ArrayVec<T,_>::push (19 samples, 0.05%)arrayvec::arrayvec_impl::ArrayVecImpl::push (19 samples, 0.05%)arrayvec::arrayvec_impl::ArrayVecImpl::try_push (19 samples, 0.05%)arrayvec::arrayvec_impl::ArrayVecImpl::push_unchecked (11 samples, 0.03%)core::ptr::write (11 samples, 0.03%)zssp::fragged::Fragged<Fragment,_>::assemble (59 samples, 0.15%)<T as core::convert::TryInto<U>>::try_into (39 samples, 0.10%)core::array::_<impl core::convert::TryFrom<&[T]> for [T: N]>::try_from (39 samples, 0.10%)core::result::Result<T,E>::map (39 samples, 0.10%)zssp::zeta::from_nonce (44 samples, 0.11%)<core::slice::iter::IterMut<T> as core::iter::traits::iterator::Iterator>::next (4 samples, 0.01%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (60 samples, 0.15%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (60 samples, 0.15%)core::sync::atomic::AtomicU32::fetch_sub (60 samples, 0.15%)core::sync::atomic::atomic_sub (60 samples, 0.15%)EVP_CIPHER_CTX_get_block_size (5 samples, 0.01%)CRYPTO_gcm128_decrypt (268 samples, 0.69%)[libcrypto.so.3] (104 samples, 0.27%)CRYPTO_gcm128_decrypt_ctr32 (512 samples, 1.32%)[libcrypto.so.3] (396 samples, 1.02%)[libcrypto.so.3] (25 samples, 0.06%)CRYPTO_gcm128_setiv (25 samples, 0.06%)[libcrypto.so.3] (22 samples, 0.06%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::decrypt_in_place (4,728 samples, 12.18%)<zssp::crypto_impl..zssp::crypto_impl::openssl::CipherCtx::update (4,725 samples, 12.17%)zssp::crypto_impl:..EVP_DecryptUpdate (4,725 samples, 12.17%)EVP_DecryptUpdate[libcrypto.so.3] (4,634 samples, 11.93%)[libcrypto.so.3][libcrypto.so.3] (4,632 samples, 11.93%)[libcrypto.so.3][libcrypto.so.3] (4,611 samples, 11.87%)[libcrypto.so.3][libcrypto.so.3] (3,756 samples, 9.67%)[libcrypto.so...[libcrypto.so.3] (3,594 samples, 9.26%)[libcrypto.so..core::ptr::drop_in_place<zssp::crypto_impl::openssl::AesGcmOpenSSLDec> (7 samples, 0.02%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (7 samples, 0.02%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (7 samples, 0.02%)std::sys::unix::locks::futex_mutex::Mutex::unlock (6 samples, 0.02%)core::sync::atomic::AtomicU32::swap (6 samples, 0.02%)core::sync::atomic::atomic_swap (6 samples, 0.02%)[libc.so.6] (4 samples, 0.01%)CRYPTO_gcm128_finish (47 samples, 0.12%)[libcrypto.so.3] (28 samples, 0.07%)zssp::crypto_impl::openssl::CipherCtx::finalize (63 samples, 0.16%)EVP_DecryptFinal_ex (63 samples, 0.16%)[libcrypto.so.3] (58 samples, 0.15%)[libcrypto.so.3] (56 samples, 0.14%)[libcrypto.so.3] (51 samples, 0.13%)OSSL_PARAM_locate (27 samples, 0.07%)[libc.so.6] (12 samples, 0.03%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::finish (152 samples, 0.39%)zssp::crypto_impl::openssl::CipherCtx::set_tag (82 samples, 0.21%)EVP_CIPHER_CTX_ctrl (82 samples, 0.21%)[libcrypto.so.3] (41 samples, 0.11%)std::sync::mutex::Mutex<T>::lock (16 samples, 0.04%)std::sys::unix::locks::futex_mutex::Mutex::lock (16 samples, 0.04%)core::sync::atomic::AtomicU32::compare_exchange (15 samples, 0.04%)core::sync::atomic::atomic_compare_exchange (15 samples, 0.04%)[libc.so.6] (22 samples, 0.06%)OSSL_PARAM_locate (47 samples, 0.12%)EVP_CIPHER_CTX_get_iv_length (68 samples, 0.18%)[libcrypto.so.3] (52 samples, 0.13%)[libc.so.6] (27 samples, 0.07%)OSSL_PARAM_locate (49 samples, 0.13%)strcmp@plt (7 samples, 0.02%)EVP_CIPHER_CTX_set_padding (86 samples, 0.22%)[libcrypto.so.3] (66 samples, 0.17%)OSSL_PARAM_locate_const (6 samples, 0.02%)EVP_CIPHER_free (15 samples, 0.04%)EVP_CipherInit_ex (252 samples, 0.65%)[libcrypto.so.3] (252 samples, 0.65%)EVP_CIPHER_up_ref (51 samples, 0.13%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_dec (270 samples, 0.70%)zssp::crypto_impl::openssl::CipherCtx::cipher_init (254 samples, 0.65%)core::slice::index::<impl core::ops::index::IndexMut<I> for [T]>::index_mut (7 samples, 0.02%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::index_mut (7 samples, 0.02%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::get_unchecked_mut (4 samples, 0.01%)<core::ops::range::Range<usize> as core::slice::index::SliceIndex<[T]>>::get_unchecked_mut (4 samples, 0.01%)arrayvec::arrayvec::ArrayVec<T,_>::remaining_capacity (5 samples, 0.01%)arrayvec::arrayvec::ArrayVec<T,_>::try_extend_from_slice (240 samples, 0.62%)core::intrinsics::copy_nonoverlapping (238 samples, 0.61%)[libc.so.6] (238 samples, 0.61%)std::io::impls::<impl std::io::Write for &mut W>::write (247 samples, 0.64%)<arrayvec::arrayvec::ArrayVec<u8,_> as std::io::Write>::write (247 samples, 0.64%)zssp::zeta::receive_payload_in_place (5,500 samples, 14.16%)zssp::zeta::receive_pa..zssp::antireplay::Window<_,_>::update (8 samples, 0.02%)core::sync::atomic::AtomicU64::fetch_max (8 samples, 0.02%)core::sync::atomic::atomic_umax (8 samples, 0.02%)zssp::zssp::Context<Crypto>::receive (8,391 samples, 21.61%)zssp::zssp::Context<Crypto>::recei..zssp::zssp::parse_fragment_header (101 samples, 0.26%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (73 samples, 0.19%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (73 samples, 0.19%)std::sys::unix::locks::futex_mutex::Mutex::unlock (70 samples, 0.18%)core::sync::atomic::AtomicU32::swap (66 samples, 0.17%)core::sync::atomic::atomic_swap (66 samples, 0.17%)std::sync::mutex::Mutex<T>::lock (77 samples, 0.20%)std::sys::unix::locks::futex_mutex::Mutex::lock (76 samples, 0.20%)core::sync::atomic::AtomicU32::compare_exchange (73 samples, 0.19%)core::sync::atomic::atomic_compare_exchange (73 samples, 0.19%)<zssp::crypto_impl::openssl::Aes256OpenSSLEnc as zssp::crypto::aes::Aes256Enc>::encrypt_in_place (321 samples, 0.83%)zssp::crypto_impl::openssl::CipherCtx::update (162 samples, 0.42%)EVP_EncryptUpdate (158 samples, 0.41%)[libcrypto.so.3] (139 samples, 0.36%)[libcrypto.so.3] (108 samples, 0.28%)[libcrypto.so.3] (99 samples, 0.25%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)<std::sync::mutex::MutexGuard<T> as core::ops::deref::Deref>::deref (10 samples, 0.03%)[libcrypto.so.3] (174 samples, 0.45%)CRYPTO_gcm128_encrypt (280 samples, 0.72%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)CRYPTO_gcm128_encrypt_ctr32 (476 samples, 1.23%)[libcrypto.so.3] (345 samples, 0.89%)[libcrypto.so.3] (31 samples, 0.08%)CRYPTO_gcm128_setiv (18 samples, 0.05%)[libcrypto.so.3] (15 samples, 0.04%)perf_ctx_enable (5 samples, 0.01%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::encrypt (3,908 samples, 10.06%)<zssp::crypto_i..zssp::crypto_impl::openssl::CipherCtx::update (3,898 samples, 10.04%)zssp::crypto_im..EVP_EncryptUpdate (3,895 samples, 10.03%)EVP_EncryptUpda..[libcrypto.so.3] (3,844 samples, 9.90%)[libcrypto.so...[libcrypto.so.3] (3,838 samples, 9.88%)[libcrypto.so...[libcrypto.so.3] (3,819 samples, 9.83%)[libcrypto.so...[libcrypto.so.3] (3,007 samples, 7.74%)[libcrypto...[libcrypto.so.3] (2,753 samples, 7.09%)[libcrypto..asm_sysvec_reschedule_ipi (10 samples, 0.03%)sysvec_reschedule_ipi (10 samples, 0.03%)irqentry_exit (10 samples, 0.03%)irqentry_exit_to_user_mode (10 samples, 0.03%)exit_to_user_mode_prepare (10 samples, 0.03%)exit_to_user_mode_loop (10 samples, 0.03%)schedule (10 samples, 0.03%)__schedule (10 samples, 0.03%)finish_task_switch.isra.0 (10 samples, 0.03%)__perf_event_task_sched_in (10 samples, 0.03%)perf_pmu_nop_void (5 samples, 0.01%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc> (11 samples, 0.03%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (11 samples, 0.03%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (11 samples, 0.03%)std::sys::unix::locks::futex_mutex::Mutex::unlock (11 samples, 0.03%)core::sync::atomic::AtomicU32::swap (11 samples, 0.03%)core::sync::atomic::atomic_swap (11 samples, 0.03%)zssp::crypto_impl::openssl::CipherCtx::finalize (60 samples, 0.15%)EVP_EncryptFinal_ex (60 samples, 0.15%)[libcrypto.so.3] (53 samples, 0.14%)[libcrypto.so.3] (53 samples, 0.14%)[libcrypto.so.3] (48 samples, 0.12%)CRYPTO_gcm128_tag (48 samples, 0.12%)CRYPTO_gcm128_finish (40 samples, 0.10%)[libcrypto.so.3] (26 samples, 0.07%)[libc.so.6] (21 samples, 0.05%)OSSL_PARAM_locate (45 samples, 0.12%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::finish (153 samples, 0.39%)zssp::crypto_impl::openssl::CipherCtx::get_tag (80 samples, 0.21%)EVP_CIPHER_CTX_ctrl (79 samples, 0.20%)[libcrypto.so.3] (55 samples, 0.14%)OSSL_PARAM_set_octet_string (5 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (13 samples, 0.03%)std::sys::unix::locks::futex_mutex::Mutex::lock (13 samples, 0.03%)core::sync::atomic::AtomicU32::compare_exchange (13 samples, 0.03%)core::sync::atomic::atomic_compare_exchange (13 samples, 0.03%)[libcrypto.so.3] (40 samples, 0.10%)OSSL_PARAM_locate (36 samples, 0.09%)[libc.so.6] (20 samples, 0.05%)EVP_CIPHER_CTX_get_iv_length (67 samples, 0.17%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_pmu_nop_void (5 samples, 0.01%)[libc.so.6] (17 samples, 0.04%)OSSL_PARAM_locate (31 samples, 0.08%)strcmp@plt (5 samples, 0.01%)EVP_CIPHER_CTX_set_padding (67 samples, 0.17%)[libcrypto.so.3] (49 samples, 0.13%)OSSL_PARAM_locate_const (6 samples, 0.02%)EVP_CIPHER_free (21 samples, 0.05%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_enc (206 samples, 0.53%)zssp::crypto_impl::openssl::CipherCtx::cipher_init (193 samples, 0.50%)EVP_CipherInit_ex (193 samples, 0.50%)[libcrypto.so.3] (193 samples, 0.50%)EVP_CIPHER_up_ref (27 samples, 0.07%)__rdl_alloc (6 samples, 0.02%)std::sys::unix::alloc::<impl core::alloc::global::GlobalAlloc for std::alloc::System>::alloc (6 samples, 0.02%)[libc.so.6] (60 samples, 0.15%)[libc.so.6] (978 samples, 2.52%)[l..asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)__entry_text_start (4 samples, 0.01%)__get_user_nocheck_4 (14 samples, 0.04%)futex_q_lock (5 samples, 0.01%)futex_q_unlock (17 samples, 0.04%)futex_wait (45 samples, 0.12%)futex_wait_setup (42 samples, 0.11%)do_futex (46 samples, 0.12%)__x64_sys_futex (47 samples, 0.12%)entry_SYSCALL_64_after_hwframe (52 samples, 0.13%)do_syscall_64 (52 samples, 0.13%)syscall_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)__lll_lock_wait_private (74 samples, 0.19%)__entry_text_start (10 samples, 0.03%)futex_hash (7 samples, 0.02%)_raw_spin_lock (8 samples, 0.02%)native_queued_spin_lock_slowpath (8 samples, 0.02%)futex_wake_mark (10 samples, 0.03%)wake_q_add_safe (4 samples, 0.01%)__smp_call_single_queue (12 samples, 0.03%)native_send_call_func_single_ipi (8 samples, 0.02%)x2apic_send_IPI (7 samples, 0.02%)native_write_msr (5 samples, 0.01%)llist_add_batch (10 samples, 0.03%)try_to_wake_up (77 samples, 0.20%)ttwu_queue_wakelist (37 samples, 0.10%)futex_wake (145 samples, 0.37%)wake_up_q (85 samples, 0.22%)do_futex (158 samples, 0.41%)__x64_sys_futex (159 samples, 0.41%)exit_to_user_mode_prepare (6 samples, 0.02%)do_syscall_64 (172 samples, 0.44%)syscall_exit_to_user_mode (8 samples, 0.02%)entry_SYSCALL_64_after_hwframe (175 samples, 0.45%)alloc::vec::Vec<T,A>::with_capacity_in (1,972 samples, 5.08%)alloc:..alloc::raw_vec::RawVec<T,A>::with_capacity_in (1,972 samples, 5.08%)alloc:..alloc::raw_vec::RawVec<T,A>::allocate_in (1,972 samples, 5.08%)alloc:..<alloc::alloc::Global as core::alloc::Allocator>::allocate (1,970 samples, 5.07%)<alloc..alloc::alloc::Global::alloc_impl (1,970 samples, 5.07%)alloc:..alloc::alloc::alloc (1,970 samples, 5.07%)alloc:..malloc (1,962 samples, 5.05%)malloc__lll_lock_wake_private (192 samples, 0.49%)alloc::slice::<impl [T]>::to_vec (2,175 samples, 5.60%)alloc::..alloc::slice::<impl [T]>::to_vec_in (2,175 samples, 5.60%)alloc::..alloc::slice::hack::to_vec (2,175 samples, 5.60%)alloc::..<T as alloc::slice::hack::ConvertVec>::to_vec (2,175 samples, 5.60%)<T as a..core::ptr::const_ptr::<impl *const T>::copy_to_nonoverlapping (203 samples, 0.52%)core::intrinsics::copy_nonoverlapping (203 samples, 0.52%)[libc.so.6] (201 samples, 0.52%)core::sync::atomic::AtomicUsize::compare_exchange_weak (118 samples, 0.30%)core::sync::atomic::atomic_compare_exchange_weak (118 samples, 0.30%)std::sync::mpmc::array::Channel<T>::start_send (183 samples, 0.47%)core::sync::atomic::AtomicUsize::load (37 samples, 0.10%)core::sync::atomic::atomic_load (37 samples, 0.10%)core::ptr::mut_ptr::<impl *mut T>::write (7 samples, 0.02%)core::ptr::write (7 samples, 0.02%)benchmark::bob_main::_{{closure}} (3,947 samples, 10.16%)benchmark::bob_..std::sync::mpsc::SyncSender<T>::send (1,771 samples, 4.56%)std::..std::sync::mpmc::Sender<T>::send (1,764 samples, 4.54%)std::..std::sync::mpmc::array::Channel<T>::send (219 samples, 0.56%)std::sync::mpmc::array::Channel<T>::write (17 samples, 0.04%)std::sync::mpmc::waker::SyncWaker::notify (5 samples, 0.01%)core::mem::drop (38 samples, 0.10%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<zssp::zeta::MutableState<benchmark::TestApplication>>> (38 samples, 0.10%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (38 samples, 0.10%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (38 samples, 0.10%)core::sync::atomic::AtomicU32::fetch_sub (36 samples, 0.09%)core::sync::atomic::atomic_sub (36 samples, 0.09%)core::slice::<impl [T]>::copy_from_slice (10 samples, 0.03%)core::intrinsics::copy_nonoverlapping (10 samples, 0.03%)<core::ops::range::Range<usize> as core::slice::index::SliceIndex<[T]>>::index (6 samples, 0.02%)core::slice::index::<impl core::ops::index::Index<I> for [T]>::index (11 samples, 0.03%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::index (5 samples, 0.01%)core::slice::index::<impl core::ops::index::IndexMut<I> for [T]>::index_mut (5 samples, 0.01%)<core::ops::range::Range<usize> as core::slice::index::SliceIndex<[T]>>::index_mut (5 samples, 0.01%)core::sync::atomic::AtomicU32::compare_exchange_weak (34 samples, 0.09%)core::sync::atomic::atomic_compare_exchange_weak (34 samples, 0.09%)std::sync::rwlock::RwLock<T>::read (38 samples, 0.10%)std::sys::unix::locks::futex_rwlock::RwLock::read (38 samples, 0.10%)benchmark::bob_main (17,970 samples, 46.28%)benchmark::bob_mainzssp::zssp::Context<Crypto>::send (8,668 samples, 22.32%)zssp::zssp::Context<Crypto>::sendzssp::zeta::send_payload (8,667 samples, 22.32%)zssp::zeta::send_payloadzssp::zeta::get_counter (8 samples, 0.02%)core::sync::atomic::AtomicU64::fetch_add (7 samples, 0.02%)core::sync::atomic::atomic_add (7 samples, 0.02%)cfree (25 samples, 0.06%)clock_gettime (17 samples, 0.04%)core::hash::BuildHasher::hash_one (25 samples, 0.06%)core::hash::impls::<impl core::hash::Hash for &T>::hash (18 samples, 0.05%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::fragged::Fragged<alloc::vec::Vec<u8>,48_usize>>> (4 samples, 0.01%)core::result::Result<T,E>::unwrap (10 samples, 0.03%)malloc (11 samples, 0.03%)std::sync::mpmc::Receiver<T>::recv_timeout (25 samples, 0.06%)std::sync::mpmc::Receiver<T>::recv_deadline (10 samples, 0.03%)std::sync::mpmc::Sender<T>::send (13 samples, 0.03%)std::sync::mpmc::array::Channel<T>::recv (28 samples, 0.07%)std::sync::mpmc::array::Channel<T>::send (14 samples, 0.04%)std::sync::mpmc::array::Channel<T>::start_recv (21 samples, 0.05%)std::sync::mpmc::utils::Backoff::new (22 samples, 0.06%)std::sync::mpmc::waker::SyncWaker::notify (22 samples, 0.06%)core::sync::atomic::AtomicBool::load (5 samples, 0.01%)core::sync::atomic::atomic_load (5 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (9 samples, 0.02%)std::sys::unix::time::Timespec::sub_timespec (9 samples, 0.02%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (9 samples, 0.02%)std::time::SystemTime::checked_add (7 samples, 0.02%)zssp::fragged::Fragged<Fragment,_>::assemble (19 samples, 0.05%)zssp::zeta::from_nonce (8 samples, 0.02%)alloc::vec::Vec<T,A>::with_capacity_in (20 samples, 0.05%)alloc::raw_vec::RawVec<T,A>::with_capacity_in (20 samples, 0.05%)alloc::raw_vec::RawVec<T,A>::allocate_in (20 samples, 0.05%)<alloc::alloc::Global as core::alloc::Allocator>::allocate (20 samples, 0.05%)alloc::alloc::Global::alloc_impl (20 samples, 0.05%)alloc::alloc::alloc (20 samples, 0.05%)alloc::slice::<impl [T]>::to_vec (29 samples, 0.07%)alloc::slice::<impl [T]>::to_vec_in (29 samples, 0.07%)alloc::slice::hack::to_vec (29 samples, 0.07%)<T as alloc::slice::hack::ConvertVec>::to_vec (29 samples, 0.07%)core::ptr::const_ptr::<impl *const T>::copy_to_nonoverlapping (9 samples, 0.02%)core::intrinsics::copy_nonoverlapping (9 samples, 0.02%)zssp::zeta::send_payload (76 samples, 0.20%)benchmark::bob_main::_{{closure}} (34 samples, 0.09%)std::sync::mpsc::SyncSender<T>::send (5 samples, 0.01%)core::ptr::drop_in_place<arrayvec::arrayvec::ArrayVec<alloc::vec::Vec<u8>,48_usize>> (6 samples, 0.02%)<arrayvec::arrayvec::ArrayVec<T,_> as core::ops::drop::Drop>::drop (6 samples, 0.02%)arrayvec::arrayvec::ArrayVec<T,_>::clear (6 samples, 0.02%)arrayvec::arrayvec_impl::ArrayVecImpl::clear (6 samples, 0.02%)arrayvec::arrayvec_impl::ArrayVecImpl::truncate (6 samples, 0.02%)core::ptr::drop_in_place<[alloc::vec::Vec<u8>]> (6 samples, 0.02%)core::ptr::drop_in_place<alloc::vec::Vec<u8>> (6 samples, 0.02%)core::ptr::drop_in_place<alloc::raw_vec::RawVec<u8>> (6 samples, 0.02%)<alloc::raw_vec::RawVec<T,A> as core::ops::drop::Drop>::drop (6 samples, 0.02%)<alloc::alloc::Global as core::alloc::Allocator>::deallocate (6 samples, 0.02%)alloc::alloc::dealloc (6 samples, 0.02%)std::collections::hash::map::HashMap<K,V,S>::get (5 samples, 0.01%)hashbrown::map::HashMap<K,V,S,A>::get (5 samples, 0.01%)hashbrown::map::HashMap<K,V,S,A>::get_inner (5 samples, 0.01%)hashbrown::map::make_hash (5 samples, 0.01%)zssp::zssp::Context<Crypto>::receive (78 samples, 0.20%)std::sync::mpmc::array::Channel<T>::start_recv (8 samples, 0.02%)[unknown] (37,624 samples, 96.89%)[unknown]zssp::zssp::parse_fragment_header (7 samples, 0.02%)EVP_DecryptUpdate (24 samples, 0.06%)__bss_start (26 samples, 0.07%)__rust_probestack (6 samples, 0.02%)entry_SYSCALL_64_after_hwframe (7 samples, 0.02%)entry_SYSCALL_64_safe_stack (14 samples, 0.04%)ret_from_fork (10 samples, 0.03%)schedule_tail (10 samples, 0.03%)finish_task_switch.isra.0 (10 samples, 0.03%)__perf_event_task_sched_in (10 samples, 0.03%)perf_ctx_enable (10 samples, 0.03%)core::ptr::drop_in_place<arrayvec::arrayvec::ArrayVec<alloc::vec::Vec<u8>,48_usize>> (5 samples, 0.01%)<arrayvec::arrayvec::ArrayVec<T,_> as core::ops::drop::Drop>::drop (5 samples, 0.01%)arrayvec::arrayvec::ArrayVec<T,_>::clear (5 samples, 0.01%)arrayvec::arrayvec_impl::ArrayVecImpl::clear (5 samples, 0.01%)arrayvec::arrayvec_impl::ArrayVecImpl::truncate (5 samples, 0.01%)core::ptr::drop_in_place<[alloc::vec::Vec<u8>]> (5 samples, 0.01%)core::ptr::drop_in_place<alloc::vec::Vec<u8>> (5 samples, 0.01%)core::ptr::drop_in_place<alloc::raw_vec::RawVec<u8>> (5 samples, 0.01%)<alloc::raw_vec::RawVec<T,A> as core::ops::drop::Drop>::drop (5 samples, 0.01%)<alloc::alloc::Global as core::alloc::Allocator>::deallocate (5 samples, 0.01%)alloc::alloc::dealloc (5 samples, 0.01%)benchmark (38,828 samples, 99.99%)benchmarkzssp::zssp::Context<Crypto>::receive (58 samples, 0.15%)std::collections::hash::map::HashMap<K,V,S>::get (6 samples, 0.02%)hashbrown::map::HashMap<K,V,S,A>::get (6 samples, 0.02%)hashbrown::map::HashMap<K,V,S,A>::get_inner (6 samples, 0.02%)hashbrown::map::make_hash (6 samples, 0.02%)all (38,833 samples, 100%)perf-exec (5 samples, 0.01%)entry_SYSCALL_64_after_hwframe (5 samples, 0.01%)do_syscall_64 (5 samples, 0.01%)__x64_sys_execve (5 samples, 0.01%)do_execveat_common.isra.0 (5 samples, 0.01%)bprm_execve (5 samples, 0.01%)bprm_execve.part.0 (5 samples, 0.01%)exec_binprm (5 samples, 0.01%)search_binary_handler (5 samples, 0.01%)load_elf_binary (5 samples, 0.01%)begin_new_exec (5 samples, 0.01%)perf_event_exec (5 samples, 0.01%)perf_event_enable_on_exec (4 samples, 0.01%)ctx_resched (4 samples, 0.01%)perf_ctx_enable (4 samples, 0.01%) \ No newline at end of file diff --git a/src/crypto_impl/openssl.rs b/src/crypto_impl/openssl.rs index 596352f..ce2b25b 100644 --- a/src/crypto_impl/openssl.rs +++ b/src/crypto_impl/openssl.rs @@ -1,10 +1,9 @@ use std::{ ptr::{self, NonNull}, - sync::Mutex, + sync::{Mutex, MutexGuard}, }; use openssl_sys::*; -use zeroize::Zeroizing; use crate::crypto::*; @@ -150,8 +149,8 @@ impl Aes256Dec for Aes256OpenSSLDec { } } -pub struct AesGcmOpenSSLEnc(CipherCtx); -impl AesGcmEncContext for AesGcmOpenSSLEnc { +pub struct AesGcmOpenSSLEnc<'a>(MutexGuard<'a, CipherCtx>); +impl<'a> AesGcmEncContext for AesGcmOpenSSLEnc<'a> { fn encrypt(&mut self, input: &[u8], output: &mut [u8]) { unsafe { assert!(self.0.update::(input, output.as_mut_ptr())) }; } @@ -166,8 +165,8 @@ impl AesGcmEncContext for AesGcmOpenSSLEnc { } } -pub struct AesGcmOpenSSLDec(CipherCtx); -impl AesGcmDecContext for AesGcmOpenSSLDec { +pub struct AesGcmOpenSSLDec<'a>(MutexGuard<'a, CipherCtx>); +impl<'a> AesGcmDecContext for AesGcmOpenSSLDec<'a> { fn decrypt_in_place(&mut self, data: &mut [u8]) { let p = data.as_mut_ptr(); unsafe { assert!(self.0.update::(data, p)) }; @@ -179,39 +178,54 @@ impl AesGcmDecContext for AesGcmOpenSSLDec { } pub struct AesGcmOpenSSLPool { - enc_key: Zeroizing<[u8; AES_256_KEY_SIZE]>, - dec_key: Zeroizing<[u8; AES_256_KEY_SIZE]>, + enc: [Mutex; 8], + dec: [Mutex; 8], } -impl HighThroughputAesGcmPool for AesGcmOpenSSLPool { - type EncContext<'a> = AesGcmOpenSSLEnc; +unsafe impl Send for AesGcmOpenSSLPool {} +unsafe impl Sync for AesGcmOpenSSLPool {} - type DecContext<'a> = AesGcmOpenSSLDec; +impl HighThroughputAesGcmPool for AesGcmOpenSSLPool { + type EncContext<'a> = AesGcmOpenSSLEnc<'a>; + + type DecContext<'a> = AesGcmOpenSSLDec<'a>; fn new(encrypt_key: &[u8; AES_256_KEY_SIZE], decrypt_key: &[u8; AES_256_KEY_SIZE]) -> Self { - AesGcmOpenSSLPool { - enc_key: Zeroizing::new(*encrypt_key), - dec_key: Zeroizing::new(*decrypt_key), + unsafe { + AesGcmOpenSSLPool { + enc: std::array::from_fn(|_| { + let ctx = CipherCtx::new().unwrap(); + let t = openssl_sys::EVP_aes_256_gcm(); + assert!(ctx.cipher_init::(t, encrypt_key.as_ptr(), ptr::null())); + openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); + Mutex::new(ctx) + }), + dec: std::array::from_fn(|_| { + let ctx = CipherCtx::new().unwrap(); + let t = openssl_sys::EVP_aes_256_gcm(); + assert!(ctx.cipher_init::(t, decrypt_key.as_ptr(), ptr::null())); + openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); + Mutex::new(ctx) + }), + } } } fn start_enc<'a>(&'a self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> AesGcmOpenSSLEnc { - let ctx = CipherCtx::new().unwrap(); + let i = u64::from_be_bytes(nonce[4..].try_into().unwrap()); + let g = self.enc[(i as usize) % self.enc.len()].lock().unwrap(); unsafe { - let t = openssl_sys::EVP_aes_256_gcm(); - assert!(ctx.cipher_init::(t, self.enc_key.as_ptr(), nonce.as_ptr())); - openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); + assert!(g.cipher_init::(ptr::null(), ptr::null(), nonce.as_ptr())); } - AesGcmOpenSSLEnc(ctx) + AesGcmOpenSSLEnc(g) } fn start_dec<'a>(&'a self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> AesGcmOpenSSLDec { - let ctx = CipherCtx::new().unwrap(); + let i = u64::from_be_bytes(nonce[4..].try_into().unwrap()); + let g = self.dec[(i as usize) % self.enc.len()].lock().unwrap(); unsafe { - let t = openssl_sys::EVP_aes_256_gcm(); - assert!(ctx.cipher_init::(t, self.dec_key.as_ptr(), nonce.as_ptr())); - openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); + assert!(g.cipher_init::(ptr::null(), ptr::null(), nonce.as_ptr())); } - AesGcmOpenSSLDec(ctx) + AesGcmOpenSSLDec(g) } } From c8bee176a26d690f8c2814e3270c2f03ba48aa12 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 14 Aug 2023 17:52:18 -0400 Subject: [PATCH 40/50] removed flamegraph.svg --- .gitignore | 1 + flamegraph.svg | 491 ------------------------------------------------- 2 files changed, 1 insertion(+), 491 deletions(-) delete mode 100644 flamegraph.svg diff --git a/.gitignore b/.gitignore index 68e7d6c..c3a4a94 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target perf*.data perf*.old +.svg diff --git a/flamegraph.svg b/flamegraph.svg deleted file mode 100644 index a00c032..0000000 --- a/flamegraph.svg +++ /dev/null @@ -1,491 +0,0 @@ -Flame Graph Reset ZoomSearch <zssp::crypto_impl::openssl::Aes256OpenSSLDec as zssp::crypto::aes::Aes256Dec>::decrypt_in_place (13 samples, 0.03%)zssp::crypto_impl::openssl::CipherCtx::update (7 samples, 0.02%)<zssp::crypto_impl::openssl::Aes256OpenSSLEnc as zssp::crypto::aes::Aes256Enc>::encrypt_in_place (28 samples, 0.07%)zssp::crypto_impl::openssl::CipherCtx::update (4 samples, 0.01%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::decrypt_in_place (68 samples, 0.18%)zssp::crypto_impl::openssl::CipherCtx::finalize (4 samples, 0.01%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::finish (6 samples, 0.02%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::encrypt (102 samples, 0.26%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_dec (4 samples, 0.01%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_enc (5 samples, 0.01%)CRYPTO_gcm128_decrypt (8 samples, 0.02%)CRYPTO_gcm128_decrypt_ctr32 (22 samples, 0.06%)CRYPTO_gcm128_encrypt (17 samples, 0.04%)CRYPTO_gcm128_encrypt_ctr32 (15 samples, 0.04%)CRYPTO_gcm128_finish (7 samples, 0.02%)CRYPTO_gcm128_tag (4 samples, 0.01%)EVP_CIPHER_CTX_ctrl (9 samples, 0.02%)EVP_CIPHER_CTX_set_padding (14 samples, 0.04%)EVP_CIPHER_get_block_size (12 samples, 0.03%)EVP_CipherInit_ex (9 samples, 0.02%)EVP_DecryptUpdate (13 samples, 0.03%)EVP_EncryptFinal_ex (5 samples, 0.01%)OSSL_PARAM_locate (103 samples, 0.27%)[benchmark] (6 samples, 0.02%)EVP_DecryptUpdate (6 samples, 0.02%)[libc.so.6] (82 samples, 0.21%)[libcrypto.so.3] (124 samples, 0.32%)__lll_lock_wake_private (4 samples, 0.01%)malloc (13 samples, 0.03%)std::sync::mpmc::Sender<T>::send (5 samples, 0.01%)<std::sync::mpmc::select::Token as core::default::Default>::default (8 samples, 0.02%)std::sync::mpmc::array::Channel<T>::start_send (14 samples, 0.04%)std::sync::mpmc::array::Channel<T>::send (53 samples, 0.14%)std::sync::mpmc::array::Channel<T>::write (10 samples, 0.03%)std::sync::mpmc::array::Channel<T>::start_recv (8 samples, 0.02%)<std::sync::mpmc::select::Token as core::default::Default>::default (5 samples, 0.01%)std::sync::mpmc::array::Channel<T>::try_recv (24 samples, 0.06%)std::sync::mpmc::array::Channel<T>::read (8 samples, 0.02%)std::sync::mpmc::utils::Backoff::new (9 samples, 0.02%)std::sync::mpmc::waker::SyncWaker::notify (13 samples, 0.03%)core::sync::atomic::AtomicBool::load (8 samples, 0.02%)core::sync::atomic::atomic_load (8 samples, 0.02%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (7 samples, 0.02%)<std::time::Instant as core::ops::arith::Sub>::sub (4 samples, 0.01%)std::time::Instant::duration_since (4 samples, 0.01%)std::time::Instant::checked_duration_since (4 samples, 0.01%)std::sys::unix::time::inner::Instant::checked_sub_instant (4 samples, 0.01%)std::time::Instant::elapsed (21 samples, 0.05%)syscall (12 samples, 0.03%)__entry_text_start (10 samples, 0.03%)[anon] (912 samples, 2.35%)[..zssp::zeta::receive_payload_in_place (23 samples, 0.06%)std::io::impls::<impl std::io::Write for &mut W>::write (5 samples, 0.01%)<arrayvec::arrayvec::ArrayVec<u8,_> as std::io::Write>::write (5 samples, 0.01%)arrayvec::arrayvec::ArrayVec<T,_>::try_extend_from_slice (5 samples, 0.01%)core::intrinsics::copy_nonoverlapping (5 samples, 0.01%)[libc.so.6] (6 samples, 0.02%)__rust_probestack (5 samples, 0.01%)[benchmark] (38 samples, 0.10%)zssp::zssp::Context<Crypto>::receive (22 samples, 0.06%)CRYPTO_gcm128_finish (11 samples, 0.03%)[libcrypto.so.3] (93 samples, 0.24%)std::sync::mpmc::utils::Backoff::new (5 samples, 0.01%)[libcrypto.so.3] (123 samples, 0.32%)<core::num::nonzero::NonZeroU32 as core::hash::Hash>::hash (20 samples, 0.05%)<std::sync::mpmc::zero::ZeroToken as core::default::Default>::default (7 samples, 0.02%)<zssp::crypto_impl::openssl::Aes256OpenSSLDec as zssp::crypto::aes::Aes256Dec>::decrypt_in_place (17 samples, 0.04%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::decrypt_in_place (14 samples, 0.04%)zssp::crypto_impl::openssl::CipherCtx::update (14 samples, 0.04%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::encrypt (5 samples, 0.01%)zssp::crypto_impl::openssl::CipherCtx::update (5 samples, 0.01%)CRYPTO_gcm128_decrypt (6 samples, 0.02%)CRYPTO_gcm128_decrypt_ctr32 (25 samples, 0.06%)CRYPTO_gcm128_encrypt (18 samples, 0.05%)CRYPTO_gcm128_encrypt_ctr32 (33 samples, 0.08%)CRYPTO_gcm128_finish (4 samples, 0.01%)CRYPTO_gcm128_setiv (4 samples, 0.01%)EVP_CIPHER_CTX_ctrl (16 samples, 0.04%)EVP_CIPHER_CTX_get_iv_length (11 samples, 0.03%)EVP_CIPHER_get_block_size (4 samples, 0.01%)EVP_CipherInit_ex (4 samples, 0.01%)EVP_DecryptUpdate (46 samples, 0.12%)EVP_EncryptUpdate (45 samples, 0.12%)OSSL_PARAM_locate (33 samples, 0.08%)[[vdso]] (16 samples, 0.04%)[benchmark] (9 samples, 0.02%)EVP_DecryptUpdate (9 samples, 0.02%)[libc.so.6] (73 samples, 0.19%)[libcrypto.so.3] (329 samples, 0.85%)__bss_start (20 samples, 0.05%)[libcrypto.so.3] (20 samples, 0.05%)__lll_lock_wait_private (4 samples, 0.01%)__entry_text_start (35 samples, 0.09%)crng_make_state (5 samples, 0.01%)_copy_to_iter (38 samples, 0.10%)copyout (28 samples, 0.07%)copyout (5 samples, 0.01%)__memcpy (14 samples, 0.04%)chacha_block_generic (267 samples, 0.69%)chacha_permute (242 samples, 0.62%)__x64_sys_getrandom (440 samples, 1.13%)get_random_bytes_user (428 samples, 1.10%)crng_make_state (343 samples, 0.88%)crng_fast_key_erasure (302 samples, 0.78%)exit_to_user_mode_prepare (19 samples, 0.05%)do_syscall_64 (474 samples, 1.22%)syscall_exit_to_user_mode (24 samples, 0.06%)entry_SYSCALL_64_after_hwframe (494 samples, 1.27%)syscall_exit_to_user_mode (4 samples, 0.01%)<rand_core::os::OsRng as rand_core::RngCore>::next_u64 (593 samples, 1.53%)rand_core::impls::next_u64_via_fill (593 samples, 1.53%)<rand_core::os::OsRng as rand_core::RngCore>::fill_bytes (593 samples, 1.53%)<rand_core::os::OsRng as rand_core::RngCore>::try_fill_bytes (587 samples, 1.51%)getrandom::getrandom (587 samples, 1.51%)getrandom::getrandom_uninit (587 samples, 1.51%)getrandom::imp::getrandom_inner (586 samples, 1.51%)getrandom::util_libc::sys_fill_exact (583 samples, 1.50%)getrandom::imp::getrandom_inner::_{{closure}} (577 samples, 1.49%)getrandom::imp::getrandom (577 samples, 1.49%)syscall (575 samples, 1.48%)syscall_return_via_sysret (9 samples, 0.02%)[libc.so.6] (20 samples, 0.05%)arrayvec::arrayvec::ArrayVec<T,_>::clear (7 samples, 0.02%)arrayvec::arrayvec_impl::ArrayVecImpl::clear (7 samples, 0.02%)arrayvec::arrayvec_impl::ArrayVecImpl::truncate (7 samples, 0.02%)core::ptr::drop_in_place<alloc::sync::Arc<zssp::zeta::Session<benchmark::TestApplication>>> (12 samples, 0.03%)<alloc::sync::Arc<T> as core::ops::drop::Drop>::drop (12 samples, 0.03%)core::sync::atomic::AtomicUsize::fetch_sub (12 samples, 0.03%)core::sync::atomic::atomic_sub (12 samples, 0.03%)<std::sync::mpmc::select::Token as core::default::Default>::default (5 samples, 0.01%)_ZN3std4sync4mpmc5array16Channel$LT$T$GT$10start_recv17h7800ca29c64cb868E.llvm.12455019271255371362 (9 samples, 0.02%)core::mem::maybe_uninit::MaybeUninit<T>::assume_init (20 samples, 0.05%)std::sync::mpmc::array::Channel<T>::read (25 samples, 0.06%)std::sync::mpmc::waker::SyncWaker::notify (5 samples, 0.01%)core::sync::atomic::AtomicUsize::compare_exchange_weak (44 samples, 0.11%)core::sync::atomic::atomic_compare_exchange_weak (44 samples, 0.11%)core::sync::atomic::AtomicUsize::load (177 samples, 0.46%)core::sync::atomic::atomic_load (177 samples, 0.46%)core::sync::atomic::fence (10 samples, 0.03%)std::sync::mpsc::Receiver<T>::try_recv (337 samples, 0.87%)std::sync::mpmc::Receiver<T>::try_recv (337 samples, 0.87%)std::sync::mpmc::array::Channel<T>::try_recv (326 samples, 0.84%)std::sync::mpmc::array::Channel<T>::start_recv (270 samples, 0.70%)<std::time::Instant as core::ops::arith::Sub>::sub (8 samples, 0.02%)std::time::Instant::duration_since (8 samples, 0.02%)std::time::Instant::checked_duration_since (8 samples, 0.02%)std::sys::unix::time::inner::Instant::checked_sub_instant (8 samples, 0.02%)std::sys::unix::time::Timespec::sub_timespec (8 samples, 0.02%)[[vdso]] (33 samples, 0.08%)[[vdso]] (19 samples, 0.05%)std::time::Instant::elapsed (44 samples, 0.11%)std::time::Instant::now (36 samples, 0.09%)std::sys::unix::time::inner::Instant::now (36 samples, 0.09%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (35 samples, 0.09%)clock_gettime (35 samples, 0.09%)<T as core::convert::TryInto<U>>::try_into (401 samples, 1.03%)core::array::_<impl core::convert::TryFrom<&[T]> for [T: N]>::try_from (401 samples, 1.03%)core::result::Result<T,E>::map (401 samples, 1.03%)<core::result::Result<T,E> as core::ops::try_trait::Try>::branch (4 samples, 0.01%)<zssp::crypto_impl::openssl::Aes256OpenSSLDec as zssp::crypto::aes::Aes256Dec>::decrypt_in_place (5 samples, 0.01%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (71 samples, 0.18%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (71 samples, 0.18%)std::sys::unix::locks::futex_mutex::Mutex::unlock (64 samples, 0.16%)core::sync::atomic::AtomicU32::swap (60 samples, 0.15%)core::sync::atomic::atomic_swap (60 samples, 0.15%)std::sync::mutex::MutexGuard<T>::new (5 samples, 0.01%)std::sync::poison::Flag::guard (5 samples, 0.01%)std::thread::panicking (5 samples, 0.01%)std::panicking::panicking (5 samples, 0.01%)std::panicking::panic_count::count_is_zero (5 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (77 samples, 0.20%)std::sys::unix::locks::futex_mutex::Mutex::lock (72 samples, 0.19%)core::sync::atomic::AtomicU32::compare_exchange (70 samples, 0.18%)core::sync::atomic::atomic_compare_exchange (70 samples, 0.18%)EVP_CIPHER_CTX_get_block_size (5 samples, 0.01%)<zssp::crypto_impl::openssl::Aes256OpenSSLDec as zssp::crypto::aes::Aes256Dec>::decrypt_in_place (394 samples, 1.01%)zssp::crypto_impl::openssl::CipherCtx::update (243 samples, 0.63%)EVP_DecryptUpdate (242 samples, 0.62%)[libcrypto.so.3] (204 samples, 0.53%)[libcrypto.so.3] (152 samples, 0.39%)[libcrypto.so.3] (144 samples, 0.37%)__rust_probestack (9 samples, 0.02%)core::cmp::Ord::max (4 samples, 0.01%)zssp::zssp::Context<Crypto>::receive (4 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)core::num::nonzero::NonZeroU32::new (13 samples, 0.03%)asm_sysvec_reschedule_ipi (10 samples, 0.03%)sysvec_reschedule_ipi (10 samples, 0.03%)irqentry_exit (10 samples, 0.03%)irqentry_exit_to_user_mode (10 samples, 0.03%)exit_to_user_mode_prepare (10 samples, 0.03%)exit_to_user_mode_loop (10 samples, 0.03%)schedule (10 samples, 0.03%)__schedule (10 samples, 0.03%)finish_task_switch.isra.0 (10 samples, 0.03%)__perf_event_task_sched_in (10 samples, 0.03%)perf_pmu_nop_void (5 samples, 0.01%)alloc::sync::Weak<T>::upgrade::_{{closure}} (9 samples, 0.02%)core::sync::atomic::AtomicUsize::compare_exchange_weak (61 samples, 0.16%)core::sync::atomic::atomic_compare_exchange_weak (61 samples, 0.16%)alloc::sync::Weak<T>::upgrade (81 samples, 0.21%)core::sync::atomic::AtomicUsize::fetch_update (78 samples, 0.20%)core::sync::atomic::AtomicUsize::load (4 samples, 0.01%)core::sync::atomic::atomic_load (4 samples, 0.01%)core::option::Option<T>::map (82 samples, 0.21%)zssp::zssp::Context<Crypto>::receive::_{{closure}} (82 samples, 0.21%)core::ptr::drop_in_place<alloc::sync::Arc<zssp::zeta::Session<benchmark::TestApplication>>> (55 samples, 0.14%)<alloc::sync::Arc<T> as core::ops::drop::Drop>::drop (55 samples, 0.14%)core::sync::atomic::AtomicUsize::fetch_sub (53 samples, 0.14%)core::sync::atomic::atomic_sub (53 samples, 0.14%)__rdl_dealloc (4 samples, 0.01%)std::sys::unix::alloc::<impl core::alloc::global::GlobalAlloc for std::alloc::System>::dealloc (4 samples, 0.01%)__rust_dealloc (5 samples, 0.01%)[libc.so.6] (48 samples, 0.12%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_pmu_nop_void (5 samples, 0.01%)__entry_text_start (14 samples, 0.04%)futex_unqueue (10 samples, 0.03%)update_curr (11 samples, 0.03%)cpuacct_charge (5 samples, 0.01%)update_load_avg (8 samples, 0.02%)dequeue_entity (28 samples, 0.07%)dequeue_task (37 samples, 0.10%)dequeue_task_fair (36 samples, 0.09%)__perf_event_task_sched_in (8 samples, 0.02%)perf_ctx_enable (8 samples, 0.02%)finish_task_switch.isra.0 (18 samples, 0.05%)raw_spin_rq_unlock (9 samples, 0.02%)pick_next_task_fair (4 samples, 0.01%)newidle_balance (4 samples, 0.01%)pick_next_task (11 samples, 0.03%)prepare_task_switch (14 samples, 0.04%)__perf_event_task_sched_out (5 samples, 0.01%)psi_group_change (26 samples, 0.07%)psi_task_switch (44 samples, 0.11%)__schedule (149 samples, 0.38%)futex_wait_queue (165 samples, 0.42%)schedule (159 samples, 0.41%)__get_user_nocheck_4 (16 samples, 0.04%)_raw_spin_lock (5 samples, 0.01%)futex_q_lock (4 samples, 0.01%)futex_q_unlock (7 samples, 0.02%)futex_wait_setup (41 samples, 0.11%)futex_wait (230 samples, 0.59%)do_futex (236 samples, 0.61%)__x64_sys_futex (247 samples, 0.64%)__rseq_handle_notify_resume (7 samples, 0.02%)exit_to_user_mode_loop (21 samples, 0.05%)exit_to_user_mode_prepare (27 samples, 0.07%)do_syscall_64 (283 samples, 0.73%)syscall_exit_to_user_mode (31 samples, 0.08%)[libc.so.6] (785 samples, 2.02%)[..__lll_lock_wait_private (346 samples, 0.89%)entry_SYSCALL_64_after_hwframe (288 samples, 0.74%)__entry_text_start (13 samples, 0.03%)futex_hash (7 samples, 0.02%)_raw_spin_lock (25 samples, 0.06%)native_queued_spin_lock_slowpath (25 samples, 0.06%)do_futex (66 samples, 0.17%)futex_wake (54 samples, 0.14%)__x64_sys_futex (72 samples, 0.19%)exit_to_user_mode_prepare (10 samples, 0.03%)do_syscall_64 (90 samples, 0.23%)syscall_exit_to_user_mode (16 samples, 0.04%)entry_SYSCALL_64_after_hwframe (98 samples, 0.25%)alloc::alloc::dealloc (965 samples, 2.48%)al..cfree (956 samples, 2.46%)cf..__lll_lock_wake_private (119 samples, 0.31%)<alloc::alloc::Global as core::alloc::Allocator>::deallocate (970 samples, 2.50%)<a..zssp::zssp::Context<Crypto>::receive (5 samples, 0.01%)core::ptr::drop_in_place<arrayvec::arrayvec::ArrayVec<alloc::vec::Vec<u8>,48_usize>> (986 samples, 2.54%)co..<arrayvec::arrayvec::ArrayVec<T,_> as core::ops::drop::Drop>::drop (986 samples, 2.54%)<a..arrayvec::arrayvec::ArrayVec<T,_>::clear (986 samples, 2.54%)ar..arrayvec::arrayvec_impl::ArrayVecImpl::clear (986 samples, 2.54%)ar..arrayvec::arrayvec_impl::ArrayVecImpl::truncate (986 samples, 2.54%)ar..core::ptr::drop_in_place<[alloc::vec::Vec<u8>]> (984 samples, 2.53%)co..core::ptr::drop_in_place<alloc::vec::Vec<u8>> (981 samples, 2.53%)co..core::ptr::drop_in_place<alloc::raw_vec::RawVec<u8>> (981 samples, 2.53%)co..<alloc::raw_vec::RawVec<T,A> as core::ops::drop::Drop>::drop (981 samples, 2.53%)<a..alloc::raw_vec::RawVec<T,A>::current_memory (8 samples, 0.02%)core::alloc::layout::Layout::array (6 samples, 0.02%)core::alloc::layout::Layout::array::inner (6 samples, 0.02%)std::sync::poison::Flag::done (10 samples, 0.03%)std::thread::panicking (10 samples, 0.03%)std::panicking::panicking (10 samples, 0.03%)std::panicking::panic_count::count_is_zero (10 samples, 0.03%)core::sync::atomic::AtomicUsize::load (9 samples, 0.02%)core::sync::atomic::atomic_load (9 samples, 0.02%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (78 samples, 0.20%)std::sys::unix::locks::futex_mutex::Mutex::unlock (68 samples, 0.18%)core::sync::atomic::AtomicU32::swap (66 samples, 0.17%)core::sync::atomic::atomic_swap (66 samples, 0.17%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::fragged::Fragged<alloc::vec::Vec<u8>,48_usize>>> (88 samples, 0.23%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::fragged::Fragged<alloc::vec::Vec<u8>,48_usize>>> (5 samples, 0.01%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<std::collections::hash::map::HashMap<core::num::nonzero::NonZeroU32,alloc::sync::Weak<zssp::zeta::Session<benchmark::TestApplication>>>>> (85 samples, 0.22%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (85 samples, 0.22%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (85 samples, 0.22%)core::sync::atomic::AtomicU32::fetch_sub (76 samples, 0.20%)core::sync::atomic::atomic_sub (76 samples, 0.20%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<zssp::zeta::MutableState<benchmark::TestApplication>>> (66 samples, 0.17%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (66 samples, 0.17%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (66 samples, 0.17%)core::sync::atomic::AtomicU32::fetch_sub (57 samples, 0.15%)core::sync::atomic::atomic_sub (57 samples, 0.15%)core::num::<impl u64>::rotate_left (4 samples, 0.01%)<core::hash::sip::Sip13Rounds as core::hash::sip::Sip>::c_rounds (20 samples, 0.05%)core::num::<impl u64>::wrapping_add (4 samples, 0.01%)core::num::<impl u64>::rotate_left (7 samples, 0.02%)<std::collections::hash::map::DefaultHasher as core::hash::Hasher>::finish (62 samples, 0.16%)<core::hash::sip::SipHasher13 as core::hash::Hasher>::finish (62 samples, 0.16%)<core::hash::sip::Hasher<S> as core::hash::Hasher>::finish (62 samples, 0.16%)<core::hash::sip::Sip13Rounds as core::hash::sip::Sip>::d_rounds (32 samples, 0.08%)core::num::<impl u64>::wrapping_add (14 samples, 0.04%)<std::collections::hash::map::RandomState as core::hash::BuildHasher>::build_hasher (17 samples, 0.04%)core::hash::sip::SipHasher13::new_with_keys (8 samples, 0.02%)core::hash::sip::Hasher<S>::new_with_keys (8 samples, 0.02%)core::hash::sip::Hasher<S>::reset (8 samples, 0.02%)hashbrown::map::make_hash (104 samples, 0.27%)core::hash::BuildHasher::hash_one (104 samples, 0.27%)core::hash::impls::<impl core::hash::Hash for &T>::hash (23 samples, 0.06%)<core::num::nonzero::NonZeroU32 as core::hash::Hash>::hash (23 samples, 0.06%)core::hash::impls::<impl core::hash::Hash for u32>::hash (23 samples, 0.06%)core::hash::Hasher::write_u32 (23 samples, 0.06%)<std::collections::hash::map::DefaultHasher as core::hash::Hasher>::write (23 samples, 0.06%)<core::hash::sip::SipHasher13 as core::hash::Hasher>::write (23 samples, 0.06%)<core::hash::sip::Hasher<S> as core::hash::Hasher>::write (23 samples, 0.06%)core::hash::sip::u8to64_le (15 samples, 0.04%)hashbrown::raw::RawTable<T,A>::find::_{{closure}} (5 samples, 0.01%)hashbrown::raw::h2 (8 samples, 0.02%)hashbrown::raw::sse2::Group::load (29 samples, 0.07%)core::core_arch::x86::sse2::_mm_loadu_si128 (29 samples, 0.07%)core::intrinsics::copy_nonoverlapping (29 samples, 0.07%)hashbrown::map::HashMap<K,V,S,A>::get_inner (163 samples, 0.42%)hashbrown::raw::RawTable<T,A>::get (59 samples, 0.15%)hashbrown::raw::RawTable<T,A>::find (59 samples, 0.15%)hashbrown::raw::RawTableInner<A>::find_inner (59 samples, 0.15%)hashbrown::raw::sse2::Group::match_byte (7 samples, 0.02%)core::core_arch::x86::sse2::_mm_movemask_epi8 (7 samples, 0.02%)std::collections::hash::map::HashMap<K,V,S>::get (164 samples, 0.42%)hashbrown::map::HashMap<K,V,S,A>::get (164 samples, 0.42%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (42 samples, 0.11%)std::sys::unix::locks::futex_mutex::Mutex::lock (38 samples, 0.10%)core::sync::atomic::AtomicU32::compare_exchange (30 samples, 0.08%)core::sync::atomic::atomic_compare_exchange (30 samples, 0.08%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)core::sync::atomic::AtomicU32::compare_exchange_weak (128 samples, 0.33%)core::sync::atomic::atomic_compare_exchange_weak (128 samples, 0.33%)std::sync::rwlock::RwLock<T>::read (162 samples, 0.42%)std::sys::unix::locks::futex_rwlock::RwLock::read (162 samples, 0.42%)std::sys::unix::locks::futex_rwlock::is_read_lockable (12 samples, 0.03%)core::num::<impl u64>::wrapping_add (5 samples, 0.01%)zssp::antireplay::Window<_,_>::check (13 samples, 0.03%)core::sync::atomic::AtomicU64::load (7 samples, 0.02%)core::sync::atomic::atomic_load (7 samples, 0.02%)arrayvec::arrayvec::ArrayVec<T,_>::push (20 samples, 0.05%)arrayvec::arrayvec_impl::ArrayVecImpl::push (20 samples, 0.05%)arrayvec::arrayvec_impl::ArrayVecImpl::try_push (20 samples, 0.05%)arrayvec::arrayvec_impl::ArrayVecImpl::push_unchecked (13 samples, 0.03%)core::ptr::write (12 samples, 0.03%)core::array::equality::_<impl core::cmp::PartialEq<[B: N]> for [A: N]>::ne (5 samples, 0.01%)<T as core::array::equality::SpecArrayEq<U,_>>::spec_ne (5 samples, 0.01%)<T as core::array::equality::SpecArrayEq<U,_>>::spec_eq (5 samples, 0.01%)core::mem::maybe_uninit::MaybeUninit<T>::assume_init_read (7 samples, 0.02%)core::ptr::const_ptr::<impl *const T>::read (7 samples, 0.02%)core::ptr::read (7 samples, 0.02%)core::mem::maybe_uninit::MaybeUninit<T>::assume_init (7 samples, 0.02%)core::num::<impl u64>::wrapping_shl (6 samples, 0.02%)core::num::<impl u64>::unchecked_shl (6 samples, 0.02%)zssp::fragged::Fragged<Fragment,_>::assemble (98 samples, 0.25%)zssp::fragged::Fragged<Fragment,_>::drop_in_place (5 samples, 0.01%)<T as core::convert::TryInto<U>>::try_into (26 samples, 0.07%)core::array::_<impl core::convert::TryFrom<&[T]> for [T: N]>::try_from (26 samples, 0.07%)core::result::Result<T,E>::map (26 samples, 0.07%)zssp::zeta::from_nonce (32 samples, 0.08%)core::num::<impl u64>::from_be_bytes (6 samples, 0.02%)core::num::<impl u64>::from_be (6 samples, 0.02%)core::num::<impl u64>::swap_bytes (6 samples, 0.02%)<alloc::vec::Vec<T,A> as core::convert::AsMut<[T]>>::as_mut (4 samples, 0.01%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (69 samples, 0.18%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (69 samples, 0.18%)core::sync::atomic::AtomicU32::fetch_sub (68 samples, 0.18%)core::sync::atomic::atomic_sub (68 samples, 0.18%)CRYPTO_gcm128_decrypt (249 samples, 0.64%)[libcrypto.so.3] (120 samples, 0.31%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)CRYPTO_gcm128_decrypt_ctr32 (478 samples, 1.23%)[libcrypto.so.3] (360 samples, 0.93%)[libcrypto.so.3] (22 samples, 0.06%)CRYPTO_gcm128_setiv (23 samples, 0.06%)[libcrypto.so.3] (20 samples, 0.05%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::decrypt_in_place (4,848 samples, 12.48%)<zssp::crypto_impl:..zssp::crypto_impl::openssl::CipherCtx::update (4,846 samples, 12.48%)zssp::crypto_impl::..EVP_DecryptUpdate (4,846 samples, 12.48%)EVP_DecryptUpdate[libcrypto.so.3] (4,745 samples, 12.22%)[libcrypto.so.3][libcrypto.so.3] (4,741 samples, 12.21%)[libcrypto.so.3][libcrypto.so.3] (4,719 samples, 12.15%)[libcrypto.so.3][libcrypto.so.3] (3,937 samples, 10.14%)[libcrypto.so.3][libcrypto.so.3] (3,769 samples, 9.71%)[libcrypto.so...asm_sysvec_apic_timer_interrupt (6 samples, 0.02%)sysvec_apic_timer_interrupt (6 samples, 0.02%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (4 samples, 0.01%)__schedule (4 samples, 0.01%)finish_task_switch.isra.0 (4 samples, 0.01%)__perf_event_task_sched_in (4 samples, 0.01%)perf_ctx_enable (4 samples, 0.01%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::AesGcmOpenSSLDec> (13 samples, 0.03%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (13 samples, 0.03%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (13 samples, 0.03%)std::sys::unix::locks::futex_mutex::Mutex::unlock (12 samples, 0.03%)core::sync::atomic::AtomicU32::swap (12 samples, 0.03%)core::sync::atomic::atomic_swap (12 samples, 0.03%)[libc.so.6] (6 samples, 0.02%)[libcrypto.so.3] (20 samples, 0.05%)CRYPTO_gcm128_finish (40 samples, 0.10%)zssp::crypto_impl::openssl::CipherCtx::finalize (59 samples, 0.15%)EVP_DecryptFinal_ex (59 samples, 0.15%)[libcrypto.so.3] (50 samples, 0.13%)[libcrypto.so.3] (50 samples, 0.13%)[libcrypto.so.3] (46 samples, 0.12%)OSSL_PARAM_get_octet_string (5 samples, 0.01%)[libcrypto.so.3] (4 samples, 0.01%)OSSL_PARAM_locate (27 samples, 0.07%)[libc.so.6] (16 samples, 0.04%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::finish (145 samples, 0.37%)zssp::crypto_impl::openssl::CipherCtx::set_tag (70 samples, 0.18%)EVP_CIPHER_CTX_ctrl (70 samples, 0.18%)[libcrypto.so.3] (40 samples, 0.10%)std::sync::mutex::Mutex<T>::lock (18 samples, 0.05%)std::sys::unix::locks::futex_mutex::Mutex::lock (18 samples, 0.05%)core::sync::atomic::AtomicU32::compare_exchange (17 samples, 0.04%)core::sync::atomic::atomic_compare_exchange (17 samples, 0.04%)[libc.so.6] (14 samples, 0.04%)OSSL_PARAM_locate (37 samples, 0.10%)strcmp@plt (5 samples, 0.01%)EVP_CIPHER_CTX_get_iv_length (70 samples, 0.18%)[libcrypto.so.3] (47 samples, 0.12%)[libc.so.6] (22 samples, 0.06%)OSSL_PARAM_locate (50 samples, 0.13%)strcmp@plt (5 samples, 0.01%)EVP_CIPHER_CTX_set_padding (92 samples, 0.24%)[libcrypto.so.3] (72 samples, 0.19%)OSSL_PARAM_locate_const (5 samples, 0.01%)EVP_CIPHER_free (13 samples, 0.03%)EVP_CIPHER_up_ref (46 samples, 0.12%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_dec (292 samples, 0.75%)zssp::crypto_impl::openssl::CipherCtx::cipher_init (273 samples, 0.70%)EVP_CipherInit_ex (272 samples, 0.70%)[libcrypto.so.3] (272 samples, 0.70%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::index_mut (6 samples, 0.02%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::get_unchecked_mut (4 samples, 0.01%)<core::ops::range::Range<usize> as core::slice::index::SliceIndex<[T]>>::get_unchecked_mut (4 samples, 0.01%)core::slice::index::<impl core::ops::index::IndexMut<I> for [T]>::index_mut (7 samples, 0.02%)arrayvec::arrayvec::ArrayVec<T,_>::get_unchecked_ptr (6 samples, 0.02%)core::ptr::mut_ptr::<impl *mut T>::add (6 samples, 0.02%)core::ptr::mut_ptr::<impl *mut T>::offset (6 samples, 0.02%)std::io::impls::<impl std::io::Write for &mut W>::write (253 samples, 0.65%)<arrayvec::arrayvec::ArrayVec<u8,_> as std::io::Write>::write (253 samples, 0.65%)arrayvec::arrayvec::ArrayVec<T,_>::try_extend_from_slice (250 samples, 0.64%)core::intrinsics::copy_nonoverlapping (244 samples, 0.63%)[libc.so.6] (242 samples, 0.62%)zssp::antireplay::Window<_,_>::update (15 samples, 0.04%)core::sync::atomic::AtomicU64::fetch_max (15 samples, 0.04%)core::sync::atomic::atomic_umax (15 samples, 0.04%)zssp::zeta::receive_payload_in_place (5,655 samples, 14.56%)zssp::zeta::receive_pa..zssp::zssp::Context<Crypto>::receive (8,525 samples, 21.95%)zssp::zssp::Context<Crypto>::receivezssp::zssp::parse_fragment_header (74 samples, 0.19%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (60 samples, 0.15%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (60 samples, 0.15%)std::sys::unix::locks::futex_mutex::Mutex::unlock (59 samples, 0.15%)core::sync::atomic::AtomicU32::swap (54 samples, 0.14%)core::sync::atomic::atomic_swap (54 samples, 0.14%)std::sync::mutex::Mutex<T>::lock (79 samples, 0.20%)std::sys::unix::locks::futex_mutex::Mutex::lock (77 samples, 0.20%)core::sync::atomic::AtomicU32::compare_exchange (74 samples, 0.19%)core::sync::atomic::atomic_compare_exchange (74 samples, 0.19%)<zssp::crypto_impl::openssl::Aes256OpenSSLEnc as zssp::crypto::aes::Aes256Enc>::encrypt_in_place (325 samples, 0.84%)zssp::crypto_impl::openssl::CipherCtx::update (180 samples, 0.46%)EVP_EncryptUpdate (180 samples, 0.46%)[libcrypto.so.3] (154 samples, 0.40%)[libcrypto.so.3] (123 samples, 0.32%)[libcrypto.so.3] (115 samples, 0.30%)<std::sync::mutex::MutexGuard<T> as core::ops::deref::Deref>::deref (5 samples, 0.01%)CRYPTO_gcm128_encrypt (264 samples, 0.68%)[libcrypto.so.3] (144 samples, 0.37%)[libcrypto.so.3] (20 samples, 0.05%)CRYPTO_gcm128_encrypt_ctr32 (501 samples, 1.29%)[libcrypto.so.3] (349 samples, 0.90%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)CRYPTO_gcm128_setiv (23 samples, 0.06%)[libcrypto.so.3] (20 samples, 0.05%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::encrypt (3,870 samples, 9.97%)<zssp::crypto_..zssp::crypto_impl::openssl::CipherCtx::update (3,865 samples, 9.95%)zssp::crypto_i..EVP_EncryptUpdate (3,859 samples, 9.94%)EVP_EncryptUpd..[libcrypto.so.3] (3,809 samples, 9.81%)[libcrypto.so...[libcrypto.so.3] (3,805 samples, 9.80%)[libcrypto.so...[libcrypto.so.3] (3,789 samples, 9.76%)[libcrypto.so...[libcrypto.so.3] (2,964 samples, 7.63%)[libcrypto..[libcrypto.so.3] (2,713 samples, 6.99%)[libcrypt..asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_pmu_nop_void (5 samples, 0.01%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc> (7 samples, 0.02%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (7 samples, 0.02%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (7 samples, 0.02%)std::sys::unix::locks::futex_mutex::Mutex::unlock (6 samples, 0.02%)core::sync::atomic::AtomicU32::swap (6 samples, 0.02%)core::sync::atomic::atomic_swap (6 samples, 0.02%)[libcrypto.so.3] (31 samples, 0.08%)zssp::crypto_impl::openssl::CipherCtx::finalize (75 samples, 0.19%)EVP_EncryptFinal_ex (75 samples, 0.19%)[libcrypto.so.3] (61 samples, 0.16%)[libcrypto.so.3] (59 samples, 0.15%)[libcrypto.so.3] (56 samples, 0.14%)CRYPTO_gcm128_tag (55 samples, 0.14%)CRYPTO_gcm128_finish (42 samples, 0.11%)[libc.so.6] (29 samples, 0.07%)OSSL_PARAM_locate (60 samples, 0.15%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::finish (177 samples, 0.46%)zssp::crypto_impl::openssl::CipherCtx::get_tag (94 samples, 0.24%)EVP_CIPHER_CTX_ctrl (94 samples, 0.24%)[libcrypto.so.3] (69 samples, 0.18%)OSSL_PARAM_set_octet_string (5 samples, 0.01%)[libc.so.6] (5 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (15 samples, 0.04%)std::sys::unix::locks::futex_mutex::Mutex::lock (13 samples, 0.03%)core::sync::atomic::AtomicU32::compare_exchange (11 samples, 0.03%)core::sync::atomic::atomic_compare_exchange (11 samples, 0.03%)[libc.so.6] (25 samples, 0.06%)EVP_CIPHER_CTX_get_iv_length (76 samples, 0.20%)[libcrypto.so.3] (53 samples, 0.14%)OSSL_PARAM_locate (46 samples, 0.12%)[libc.so.6] (37 samples, 0.10%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)OSSL_PARAM_locate (49 samples, 0.13%)strcmp@plt (5 samples, 0.01%)EVP_CIPHER_CTX_set_padding (95 samples, 0.24%)[libcrypto.so.3] (62 samples, 0.16%)OSSL_PARAM_locate_const (5 samples, 0.01%)EVP_CIPHER_free (14 samples, 0.04%)EVP_CIPHER_up_ref (28 samples, 0.07%)EVP_CipherInit_ex (245 samples, 0.63%)[libcrypto.so.3] (245 samples, 0.63%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_enc (262 samples, 0.67%)zssp::crypto_impl::openssl::CipherCtx::cipher_init (247 samples, 0.64%)__rdl_alloc (10 samples, 0.03%)std::sys::unix::alloc::<impl core::alloc::global::GlobalAlloc for std::alloc::System>::alloc (10 samples, 0.03%)__rust_alloc (5 samples, 0.01%)[libc.so.6] (74 samples, 0.19%)asm_exc_page_fault (5 samples, 0.01%)exc_page_fault (5 samples, 0.01%)do_user_addr_fault (5 samples, 0.01%)handle_mm_fault (5 samples, 0.01%)__handle_mm_fault (5 samples, 0.01%)handle_pte_fault (5 samples, 0.01%)do_anonymous_page (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)finish_task_switch.isra.0 (13 samples, 0.03%)__perf_event_task_sched_in (13 samples, 0.03%)perf_pmu_nop_void (8 samples, 0.02%)[libc.so.6] (1,111 samples, 2.86%)[l..asm_sysvec_reschedule_ipi (16 samples, 0.04%)sysvec_reschedule_ipi (16 samples, 0.04%)irqentry_exit (16 samples, 0.04%)irqentry_exit_to_user_mode (16 samples, 0.04%)exit_to_user_mode_prepare (16 samples, 0.04%)exit_to_user_mode_loop (16 samples, 0.04%)schedule (14 samples, 0.04%)__schedule (14 samples, 0.04%)__entry_text_start (10 samples, 0.03%)__get_user_nocheck_4 (9 samples, 0.02%)_raw_spin_lock (6 samples, 0.02%)futex_q_lock (10 samples, 0.03%)futex_q_unlock (12 samples, 0.03%)futex_wait_setup (44 samples, 0.11%)get_futex_key (4 samples, 0.01%)futex_wait (52 samples, 0.13%)__x64_sys_futex (57 samples, 0.15%)do_futex (54 samples, 0.14%)entry_SYSCALL_64_after_hwframe (62 samples, 0.16%)do_syscall_64 (60 samples, 0.15%)__lll_lock_wait_private (101 samples, 0.26%)__entry_text_start (7 samples, 0.02%)_raw_spin_lock (9 samples, 0.02%)_raw_spin_lock (12 samples, 0.03%)native_queued_spin_lock_slowpath (12 samples, 0.03%)futex_wake_mark (24 samples, 0.06%)__futex_unqueue (4 samples, 0.01%)__smp_call_single_queue (20 samples, 0.05%)native_send_call_func_single_ipi (17 samples, 0.04%)x2apic_send_IPI (17 samples, 0.04%)native_write_msr (15 samples, 0.04%)llist_add_batch (17 samples, 0.04%)try_to_wake_up (89 samples, 0.23%)ttwu_queue_wakelist (55 samples, 0.14%)futex_wake (196 samples, 0.50%)wake_up_q (96 samples, 0.25%)do_futex (210 samples, 0.54%)__x64_sys_futex (214 samples, 0.55%)entry_SYSCALL_64_after_hwframe (224 samples, 0.58%)do_syscall_64 (222 samples, 0.57%)__lll_lock_wake_private (239 samples, 0.62%)alloc::vec::Vec<T,A>::with_capacity_in (2,224 samples, 5.73%)alloc::..alloc::raw_vec::RawVec<T,A>::with_capacity_in (2,224 samples, 5.73%)alloc::..alloc::raw_vec::RawVec<T,A>::allocate_in (2,224 samples, 5.73%)alloc::..<alloc::alloc::Global as core::alloc::Allocator>::allocate (2,221 samples, 5.72%)<alloc:..alloc::alloc::Global::alloc_impl (2,221 samples, 5.72%)alloc::..alloc::alloc::alloc (2,221 samples, 5.72%)alloc::..malloc (2,206 samples, 5.68%)mallocalloc::slice::<impl [T]>::to_vec (2,451 samples, 6.31%)alloc::s..alloc::slice::<impl [T]>::to_vec_in (2,451 samples, 6.31%)alloc::s..alloc::slice::hack::to_vec (2,451 samples, 6.31%)alloc::s..<T as alloc::slice::hack::ConvertVec>::to_vec (2,451 samples, 6.31%)<T as al..core::ptr::const_ptr::<impl *const T>::copy_to_nonoverlapping (227 samples, 0.58%)core::intrinsics::copy_nonoverlapping (227 samples, 0.58%)[libc.so.6] (222 samples, 0.57%)<std::sync::mpmc::select::Token as core::default::Default>::default (5 samples, 0.01%)std::sync::mpmc::array::Channel<T>::send (4 samples, 0.01%)core::sync::atomic::AtomicUsize::compare_exchange_weak (80 samples, 0.21%)core::sync::atomic::atomic_compare_exchange_weak (80 samples, 0.21%)std::sync::mpmc::array::Channel<T>::start_send (113 samples, 0.29%)core::sync::atomic::AtomicUsize::load (7 samples, 0.02%)core::sync::atomic::atomic_load (7 samples, 0.02%)core::sync::atomic::AtomicU32::swap (6 samples, 0.02%)core::sync::atomic::atomic_swap (6 samples, 0.02%)futex_wake_mark (7 samples, 0.02%)__smp_call_single_queue (6 samples, 0.02%)__x64_sys_futex (57 samples, 0.15%)do_futex (57 samples, 0.15%)futex_wake (55 samples, 0.14%)wake_up_q (36 samples, 0.09%)try_to_wake_up (35 samples, 0.09%)ttwu_queue_wakelist (14 samples, 0.04%)std::sync::mpmc::array::Channel<T>::write (91 samples, 0.23%)std::sync::mpmc::waker::SyncWaker::notify (85 samples, 0.22%)std::sync::mpmc::waker::Waker::try_select (74 samples, 0.19%)<core::slice::iter::Iter<T> as core::iter::traits::iterator::Iterator>::position (74 samples, 0.19%)std::sync::mpmc::waker::Waker::try_select::_{{closure}} (74 samples, 0.19%)std::sync::mpmc::context::Context::unpark (72 samples, 0.19%)std::thread::Thread::unpark (72 samples, 0.19%)std::sys_common::thread_parking::futex::Parker::unpark (72 samples, 0.19%)std::sys::unix::futex::futex_wake (66 samples, 0.17%)syscall (66 samples, 0.17%)entry_SYSCALL_64_after_hwframe (61 samples, 0.16%)do_syscall_64 (61 samples, 0.16%)std::sync::mpmc::context::Context::wait_until (4 samples, 0.01%)std::thread::park (4 samples, 0.01%)std::sys_common::thread_parking::futex::Parker::park (4 samples, 0.01%)std::sys::unix::futex::futex_wait (4 samples, 0.01%)syscall (4 samples, 0.01%)entry_SYSCALL_64_after_hwframe (4 samples, 0.01%)do_syscall_64 (4 samples, 0.01%)std::sync::mpmc::context::Context::with (5 samples, 0.01%)std::thread::local::LocalKey<T>::try_with (5 samples, 0.01%)std::sync::mpmc::context::Context::with::_{{closure}} (5 samples, 0.01%)std::sync::mpmc::context::Context::with::_{{closure}} (5 samples, 0.01%)std::sync::mpmc::array::Channel<T>::send::_{{closure}} (5 samples, 0.01%)benchmark::bob_main::_{{closure}} (4,067 samples, 10.47%)benchmark::bob_..std::sync::mpsc::SyncSender<T>::send (1,615 samples, 4.16%)std::..std::sync::mpmc::Sender<T>::send (1,613 samples, 4.15%)std::..std::sync::mpmc::array::Channel<T>::send (265 samples, 0.68%)std::sync::mpmc::utils::Backoff::spin_light (25 samples, 0.06%)core::hint::spin_loop (25 samples, 0.06%)core::core_arch::x86::sse2::_mm_pause (25 samples, 0.06%)core::mem::drop (16 samples, 0.04%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<zssp::zeta::MutableState<benchmark::TestApplication>>> (16 samples, 0.04%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (16 samples, 0.04%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (16 samples, 0.04%)core::sync::atomic::AtomicU32::fetch_sub (15 samples, 0.04%)core::sync::atomic::atomic_sub (15 samples, 0.04%)core::slice::<impl [T]>::copy_from_slice (6 samples, 0.02%)core::intrinsics::copy_nonoverlapping (6 samples, 0.02%)core::slice::index::<impl core::ops::index::IndexMut<I> for [T]>::index_mut (4 samples, 0.01%)<core::ops::range::Range<usize> as core::slice::index::SliceIndex<[T]>>::index_mut (4 samples, 0.01%)std::sync::rwlock::RwLock<T>::read (23 samples, 0.06%)std::sys::unix::locks::futex_rwlock::RwLock::read (23 samples, 0.06%)core::sync::atomic::AtomicU32::compare_exchange_weak (21 samples, 0.05%)core::sync::atomic::atomic_compare_exchange_weak (21 samples, 0.05%)benchmark::alice_main (18,418 samples, 47.43%)benchmark::alice_mainzssp::zssp::Context<Crypto>::send (8,789 samples, 22.63%)zssp::zssp::Context<Crypto>::sendzssp::zeta::send_payload (8,789 samples, 22.63%)zssp::zeta::send_payloadzssp::zeta::get_counter (18 samples, 0.05%)core::sync::atomic::AtomicU64::fetch_add (16 samples, 0.04%)core::sync::atomic::atomic_add (16 samples, 0.04%)[libc.so.6] (15 samples, 0.04%)core::ptr::drop_in_place<alloc::sync::Arc<zssp::zeta::Session<benchmark::TestApplication>>> (15 samples, 0.04%)<alloc::sync::Arc<T> as core::ops::drop::Drop>::drop (15 samples, 0.04%)core::sync::atomic::AtomicUsize::fetch_sub (14 samples, 0.04%)core::sync::atomic::atomic_sub (14 samples, 0.04%)core::time::Duration::as_millis (8 samples, 0.02%)core::result::Result<T,E>::map_err (4 samples, 0.01%)std::sync::mpmc::array::Channel<T>::read (7 samples, 0.02%)std::sync::mpmc::waker::SyncWaker::notify (6 samples, 0.02%)core::slice::<impl [T]>::get_unchecked (6 samples, 0.02%)<usize as core::slice::index::SliceIndex<[T]>>::get_unchecked (6 samples, 0.02%)core::ptr::const_ptr::<impl *const T>::add (6 samples, 0.02%)core::ptr::const_ptr::<impl *const T>::offset (6 samples, 0.02%)core::sync::atomic::AtomicUsize::compare_exchange_weak (90 samples, 0.23%)core::sync::atomic::atomic_compare_exchange_weak (90 samples, 0.23%)core::sync::atomic::AtomicUsize::load (63 samples, 0.16%)core::sync::atomic::atomic_load (63 samples, 0.16%)std::sync::mpmc::array::Channel<T>::start_recv (200 samples, 0.52%)core::sync::atomic::AtomicUsize::load (5 samples, 0.01%)core::sync::atomic::atomic_load (5 samples, 0.01%)core::sync::atomic::AtomicU32::swap (4 samples, 0.01%)core::sync::atomic::atomic_swap (4 samples, 0.01%)core::option::Option<T>::and_then (7 samples, 0.02%)std::sys::unix::futex::futex_wait::_{{closure}} (7 samples, 0.02%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (7 samples, 0.02%)clock_gettime (7 samples, 0.02%)[[vdso]] (7 samples, 0.02%)[[vdso]] (4 samples, 0.01%)futex_unqueue (7 samples, 0.02%)enqueue_hrtimer (5 samples, 0.01%)hrtimer_sleeper_start_expires (6 samples, 0.02%)hrtimer_start_range_ns (6 samples, 0.02%)__hrtimer_start_range_ns (6 samples, 0.02%)update_curr (5 samples, 0.01%)dequeue_task (14 samples, 0.04%)dequeue_task_fair (14 samples, 0.04%)dequeue_entity (14 samples, 0.04%)update_load_avg (5 samples, 0.01%)__perf_event_task_sched_in (11 samples, 0.03%)perf_ctx_enable (11 samples, 0.03%)finish_task_switch.isra.0 (16 samples, 0.04%)raw_spin_rq_unlock (4 samples, 0.01%)pick_next_task (4 samples, 0.01%)prepare_task_switch (4 samples, 0.01%)psi_group_change (8 samples, 0.02%)psi_task_switch (13 samples, 0.03%)futex_wait_queue (78 samples, 0.20%)schedule (68 samples, 0.18%)__schedule (66 samples, 0.17%)__get_user_nocheck_4 (4 samples, 0.01%)futex_q_lock (10 samples, 0.03%)futex_wait_setup (18 samples, 0.05%)hrtimer_cancel (7 samples, 0.02%)hrtimer_try_to_cancel (6 samples, 0.02%)futex_wait (117 samples, 0.30%)do_futex (120 samples, 0.31%)__x64_sys_futex (130 samples, 0.33%)get_timespec64 (4 samples, 0.01%)_copy_from_user (4 samples, 0.01%)exit_to_user_mode_prepare (8 samples, 0.02%)exit_to_user_mode_loop (8 samples, 0.02%)__rseq_handle_notify_resume (5 samples, 0.01%)std::sys_common::thread_parking::futex::Parker::park_timeout (157 samples, 0.40%)std::sys::unix::futex::futex_wait (152 samples, 0.39%)syscall (145 samples, 0.37%)entry_SYSCALL_64_after_hwframe (140 samples, 0.36%)do_syscall_64 (140 samples, 0.36%)syscall_exit_to_user_mode (9 samples, 0.02%)std::thread::park_timeout (159 samples, 0.41%)std::sync::mpmc::context::Context::wait_until (168 samples, 0.43%)core::sync::atomic::AtomicBool::store (6 samples, 0.02%)core::sync::atomic::atomic_store (6 samples, 0.02%)std::sync::mpmc::context::Context::with (187 samples, 0.48%)std::thread::local::LocalKey<T>::try_with (187 samples, 0.48%)std::sync::mpmc::context::Context::with::_{{closure}} (187 samples, 0.48%)std::sync::mpmc::context::Context::with::_{{closure}} (187 samples, 0.48%)std::sync::mpmc::array::Channel<T>::recv::_{{closure}} (185 samples, 0.48%)std::sync::mpmc::waker::SyncWaker::register (15 samples, 0.04%)std::sync::mutex::Mutex<T>::lock (6 samples, 0.02%)std::sys::unix::locks::futex_mutex::Mutex::lock (6 samples, 0.02%)core::sync::atomic::AtomicU32::compare_exchange (6 samples, 0.02%)core::sync::atomic::atomic_compare_exchange (6 samples, 0.02%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (4 samples, 0.01%)clock_gettime (4 samples, 0.01%)[[vdso]] (4 samples, 0.01%)std::sync::mpmc::Receiver<T>::recv_deadline (418 samples, 1.08%)std::sync::mpmc::array::Channel<T>::recv (416 samples, 1.07%)[[vdso]] (164 samples, 0.42%)[[vdso]] (114 samples, 0.29%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (175 samples, 0.45%)clock_gettime (173 samples, 0.45%)__vdso_clock_gettime (6 samples, 0.02%)std::sync::mpsc::Receiver<T>::recv_timeout (634 samples, 1.63%)std::sync::mpmc::Receiver<T>::recv_timeout (628 samples, 1.62%)std::time::SystemTime::checked_add (17 samples, 0.04%)std::sys::unix::time::SystemTime::checked_add_duration (16 samples, 0.04%)std::sys::unix::time::Timespec::checked_add_duration (16 samples, 0.04%)core::option::Option<T>::and_then (8 samples, 0.02%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)core::cmp::impls::<impl core::cmp::PartialOrd<&B> for &A>::ge (21 samples, 0.05%)core::cmp::PartialOrd::ge (21 samples, 0.05%)<std::sys::unix::time::Timespec as core::cmp::PartialOrd>::partial_cmp (15 samples, 0.04%)std::time::Instant::duration_since (45 samples, 0.12%)std::time::Instant::checked_duration_since (45 samples, 0.12%)std::sys::unix::time::inner::Instant::checked_sub_instant (45 samples, 0.12%)std::sys::unix::time::Timespec::sub_timespec (45 samples, 0.12%)core::time::Duration::new (5 samples, 0.01%)<std::time::Instant as core::ops::arith::Sub>::sub (48 samples, 0.12%)[[vdso]] (111 samples, 0.29%)[[vdso]] (145 samples, 0.37%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (4 samples, 0.01%)perf_ctx_enable (4 samples, 0.01%)std::time::Instant::elapsed (208 samples, 0.54%)std::time::Instant::now (158 samples, 0.41%)std::sys::unix::time::inner::Instant::now (158 samples, 0.41%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (155 samples, 0.40%)clock_gettime (151 samples, 0.39%)__vdso_clock_gettime (4 samples, 0.01%)<T as core::convert::TryInto<U>>::try_into (317 samples, 0.82%)core::array::_<impl core::convert::TryFrom<&[T]> for [T: N]>::try_from (317 samples, 0.82%)core::result::Result<T,E>::map (317 samples, 0.82%)<core::result::Result<T,E> as core::ops::try_trait::Try>::branch (6 samples, 0.02%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (76 samples, 0.20%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (76 samples, 0.20%)std::sys::unix::locks::futex_mutex::Mutex::unlock (75 samples, 0.19%)core::sync::atomic::AtomicU32::swap (71 samples, 0.18%)core::sync::atomic::atomic_swap (71 samples, 0.18%)std::sync::mutex::MutexGuard<T>::new (6 samples, 0.02%)std::sync::poison::Flag::guard (6 samples, 0.02%)std::thread::panicking (6 samples, 0.02%)std::panicking::panicking (6 samples, 0.02%)std::panicking::panic_count::count_is_zero (6 samples, 0.02%)std::sync::mutex::Mutex<T>::lock (73 samples, 0.19%)std::sys::unix::locks::futex_mutex::Mutex::lock (67 samples, 0.17%)core::sync::atomic::AtomicU32::compare_exchange (66 samples, 0.17%)core::sync::atomic::atomic_compare_exchange (66 samples, 0.17%)<zssp::crypto_impl::openssl::Aes256OpenSSLDec as zssp::crypto::aes::Aes256Dec>::decrypt_in_place (355 samples, 0.91%)zssp::crypto_impl::openssl::CipherCtx::update (206 samples, 0.53%)EVP_DecryptUpdate (202 samples, 0.52%)[libcrypto.so.3] (165 samples, 0.42%)[libcrypto.so.3] (125 samples, 0.32%)[libcrypto.so.3] (116 samples, 0.30%)__rust_probestack (6 samples, 0.02%)core::cmp::Ord::max (5 samples, 0.01%)zssp::zssp::Context<Crypto>::receive (5 samples, 0.01%)alloc::sync::Weak<T>::upgrade::_{{closure}} (6 samples, 0.02%)core::sync::atomic::AtomicUsize::compare_exchange_weak (73 samples, 0.19%)core::sync::atomic::atomic_compare_exchange_weak (73 samples, 0.19%)alloc::sync::Weak<T>::upgrade (92 samples, 0.24%)core::sync::atomic::AtomicUsize::fetch_update (88 samples, 0.23%)core::sync::atomic::AtomicUsize::load (6 samples, 0.02%)core::sync::atomic::atomic_load (6 samples, 0.02%)core::option::Option<T>::map (99 samples, 0.25%)zssp::zssp::Context<Crypto>::receive::_{{closure}} (99 samples, 0.25%)zssp::zssp::Context<Crypto>::receive (7 samples, 0.02%)core::ptr::drop_in_place<alloc::sync::Arc<zssp::zeta::Session<benchmark::TestApplication>>> (45 samples, 0.12%)<alloc::sync::Arc<T> as core::ops::drop::Drop>::drop (45 samples, 0.12%)core::sync::atomic::AtomicUsize::fetch_sub (43 samples, 0.11%)core::sync::atomic::atomic_sub (43 samples, 0.11%)__rdl_dealloc (4 samples, 0.01%)std::sys::unix::alloc::<impl core::alloc::global::GlobalAlloc for std::alloc::System>::dealloc (4 samples, 0.01%)__rust_dealloc (9 samples, 0.02%)[libc.so.6] (76 samples, 0.20%)__entry_text_start (13 samples, 0.03%)futex_unqueue (17 samples, 0.04%)_raw_spin_lock (4 samples, 0.01%)cpuacct_charge (10 samples, 0.03%)update_curr (18 samples, 0.05%)dequeue_entity (32 samples, 0.08%)update_load_avg (5 samples, 0.01%)dequeue_task (42 samples, 0.11%)dequeue_task_fair (42 samples, 0.11%)__perf_event_task_sched_in (9 samples, 0.02%)perf_ctx_enable (9 samples, 0.02%)x86_pmu_enable (9 samples, 0.02%)intel_pmu_enable_all (9 samples, 0.02%)native_write_msr (9 samples, 0.02%)finish_task_switch.isra.0 (29 samples, 0.07%)raw_spin_rq_unlock (12 samples, 0.03%)pick_next_task_fair (4 samples, 0.01%)pick_next_task (10 samples, 0.03%)put_prev_task_fair (6 samples, 0.02%)prepare_task_switch (15 samples, 0.04%)__perf_event_task_sched_out (6 samples, 0.02%)psi_group_change (38 samples, 0.10%)psi_task_switch (52 samples, 0.13%)__schedule (179 samples, 0.46%)update_rq_clock (5 samples, 0.01%)sched_clock_cpu (5 samples, 0.01%)sched_clock (4 samples, 0.01%)native_sched_clock (4 samples, 0.01%)futex_wait_queue (199 samples, 0.51%)schedule (187 samples, 0.48%)__get_user_nocheck_4 (26 samples, 0.07%)_raw_spin_lock (4 samples, 0.01%)futex_hash (5 samples, 0.01%)futex_q_lock (5 samples, 0.01%)futex_q_unlock (5 samples, 0.01%)futex_wait (278 samples, 0.72%)futex_wait_setup (52 samples, 0.13%)do_futex (282 samples, 0.73%)__x64_sys_futex (292 samples, 0.75%)__get_user_8 (5 samples, 0.01%)rseq_ip_fixup (11 samples, 0.03%)exit_to_user_mode_loop (24 samples, 0.06%)__rseq_handle_notify_resume (15 samples, 0.04%)exit_to_user_mode_prepare (31 samples, 0.08%)do_syscall_64 (331 samples, 0.85%)syscall_exit_to_user_mode (36 samples, 0.09%)__lll_lock_wait_private (397 samples, 1.02%)entry_SYSCALL_64_after_hwframe (335 samples, 0.86%)[libc.so.6] (911 samples, 2.35%)[..__entry_text_start (15 samples, 0.04%)do_syscall_64 (4 samples, 0.01%)_raw_spin_lock (5 samples, 0.01%)futex_hash (8 samples, 0.02%)_raw_spin_lock (25 samples, 0.06%)native_queued_spin_lock_slowpath (25 samples, 0.06%)_raw_spin_unlock (4 samples, 0.01%)preempt_schedule_thunk (4 samples, 0.01%)preempt_schedule (4 samples, 0.01%)__schedule (4 samples, 0.01%)finish_task_switch.isra.0 (4 samples, 0.01%)__perf_event_task_sched_in (4 samples, 0.01%)perf_ctx_enable (4 samples, 0.01%)futex_wake (77 samples, 0.20%)do_futex (97 samples, 0.25%)__x64_sys_futex (101 samples, 0.26%)do_syscall_64 (113 samples, 0.29%)syscall_exit_to_user_mode (6 samples, 0.02%)exit_to_user_mode_prepare (5 samples, 0.01%)alloc::alloc::dealloc (1,146 samples, 2.95%)all..cfree (1,133 samples, 2.92%)cf..__lll_lock_wake_private (146 samples, 0.38%)entry_SYSCALL_64_after_hwframe (120 samples, 0.31%)<alloc::alloc::Global as core::alloc::Allocator>::deallocate (1,151 samples, 2.96%)<al..zssp::zssp::Context<Crypto>::receive (5 samples, 0.01%)core::ptr::drop_in_place<arrayvec::arrayvec::ArrayVec<alloc::vec::Vec<u8>,48_usize>> (1,169 samples, 3.01%)cor..<arrayvec::arrayvec::ArrayVec<T,_> as core::ops::drop::Drop>::drop (1,169 samples, 3.01%)<ar..arrayvec::arrayvec::ArrayVec<T,_>::clear (1,169 samples, 3.01%)arr..arrayvec::arrayvec_impl::ArrayVecImpl::clear (1,169 samples, 3.01%)arr..arrayvec::arrayvec_impl::ArrayVecImpl::truncate (1,169 samples, 3.01%)arr..core::ptr::drop_in_place<[alloc::vec::Vec<u8>]> (1,162 samples, 2.99%)cor..core::ptr::drop_in_place<alloc::vec::Vec<u8>> (1,161 samples, 2.99%)cor..core::ptr::drop_in_place<alloc::raw_vec::RawVec<u8>> (1,161 samples, 2.99%)cor..<alloc::raw_vec::RawVec<T,A> as core::ops::drop::Drop>::drop (1,161 samples, 2.99%)<al..alloc::raw_vec::RawVec<T,A>::current_memory (9 samples, 0.02%)std::sync::poison::Flag::done (4 samples, 0.01%)std::thread::panicking (4 samples, 0.01%)std::panicking::panicking (4 samples, 0.01%)std::panicking::panic_count::count_is_zero (4 samples, 0.01%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (83 samples, 0.21%)std::sys::unix::locks::futex_mutex::Mutex::unlock (79 samples, 0.20%)core::sync::atomic::AtomicU32::swap (76 samples, 0.20%)core::sync::atomic::atomic_swap (76 samples, 0.20%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::fragged::Fragged<alloc::vec::Vec<u8>,48_usize>>> (87 samples, 0.22%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::fragged::Fragged<alloc::vec::Vec<u8>,48_usize>>> (4 samples, 0.01%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<std::collections::hash::map::HashMap<core::num::nonzero::NonZeroU32,alloc::sync::Weak<zssp::zeta::Session<benchmark::TestApplication>>>>> (88 samples, 0.23%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (88 samples, 0.23%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (88 samples, 0.23%)core::sync::atomic::AtomicU32::fetch_sub (80 samples, 0.21%)core::sync::atomic::atomic_sub (80 samples, 0.21%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<zssp::zeta::MutableState<benchmark::TestApplication>>> (59 samples, 0.15%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (59 samples, 0.15%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (59 samples, 0.15%)core::sync::atomic::AtomicU32::fetch_sub (54 samples, 0.14%)core::sync::atomic::atomic_sub (54 samples, 0.14%)core::slice::index::<impl core::ops::index::IndexMut<I> for [T]>::index_mut (6 samples, 0.02%)<core::ops::range::Range<usize> as core::slice::index::SliceIndex<[T]>>::index_mut (6 samples, 0.02%)<core::ops::range::Range<usize> as core::slice::index::SliceIndex<[T]>>::get_unchecked_mut (6 samples, 0.02%)core::ptr::mut_ptr::<impl *mut T>::add (6 samples, 0.02%)core::ptr::mut_ptr::<impl *mut T>::offset (6 samples, 0.02%)core::num::<impl u64>::rotate_left (9 samples, 0.02%)<core::hash::sip::Sip13Rounds as core::hash::sip::Sip>::c_rounds (22 samples, 0.06%)core::num::<impl u64>::wrapping_add (6 samples, 0.02%)core::num::<impl u64>::rotate_left (15 samples, 0.04%)<std::collections::hash::map::DefaultHasher as core::hash::Hasher>::finish (78 samples, 0.20%)<core::hash::sip::SipHasher13 as core::hash::Hasher>::finish (78 samples, 0.20%)<core::hash::sip::Hasher<S> as core::hash::Hasher>::finish (78 samples, 0.20%)<core::hash::sip::Sip13Rounds as core::hash::sip::Sip>::d_rounds (44 samples, 0.11%)core::num::<impl u64>::wrapping_add (20 samples, 0.05%)hashbrown::map::make_hash (98 samples, 0.25%)core::hash::BuildHasher::hash_one (98 samples, 0.25%)core::hash::impls::<impl core::hash::Hash for &T>::hash (15 samples, 0.04%)<core::num::nonzero::NonZeroU32 as core::hash::Hash>::hash (15 samples, 0.04%)core::hash::impls::<impl core::hash::Hash for u32>::hash (15 samples, 0.04%)core::hash::Hasher::write_u32 (15 samples, 0.04%)<std::collections::hash::map::DefaultHasher as core::hash::Hasher>::write (15 samples, 0.04%)<core::hash::sip::SipHasher13 as core::hash::Hasher>::write (15 samples, 0.04%)<core::hash::sip::Hasher<S> as core::hash::Hasher>::write (15 samples, 0.04%)core::hash::sip::u8to64_le (10 samples, 0.03%)<hashbrown::raw::bitmask::BitMaskIter as core::iter::traits::iterator::Iterator>::next (6 samples, 0.02%)hashbrown::map::equivalent_key::_{{closure}} (10 samples, 0.03%)<core::num::nonzero::NonZeroU32 as core::cmp::PartialEq>::eq (10 samples, 0.03%)hashbrown::raw::RawTable<T,A>::find::_{{closure}} (17 samples, 0.04%)hashbrown::raw::Bucket<T>::as_ref (7 samples, 0.02%)hashbrown::raw::Bucket<T>::as_ptr (7 samples, 0.02%)core::ptr::mut_ptr::<impl *mut T>::sub (7 samples, 0.02%)core::ptr::mut_ptr::<impl *mut T>::offset (7 samples, 0.02%)hashbrown::raw::h2 (14 samples, 0.04%)hashbrown::map::HashMap<K,V,S,A>::get_inner (149 samples, 0.38%)hashbrown::raw::RawTable<T,A>::get (51 samples, 0.13%)hashbrown::raw::RawTable<T,A>::find (51 samples, 0.13%)hashbrown::raw::RawTableInner<A>::find_inner (51 samples, 0.13%)hashbrown::raw::sse2::Group::match_byte (4 samples, 0.01%)core::core_arch::x86::sse2::_mm_movemask_epi8 (4 samples, 0.01%)std::collections::hash::map::HashMap<K,V,S>::get (153 samples, 0.39%)hashbrown::map::HashMap<K,V,S,A>::get (153 samples, 0.39%)zssp::zssp::Context<Crypto>::receive (4 samples, 0.01%)std::sync::mutex::MutexGuard<T>::new (5 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (50 samples, 0.13%)std::sys::unix::locks::futex_mutex::Mutex::lock (44 samples, 0.11%)core::sync::atomic::AtomicU32::compare_exchange (42 samples, 0.11%)core::sync::atomic::atomic_compare_exchange (42 samples, 0.11%)core::sync::atomic::AtomicU32::compare_exchange_weak (135 samples, 0.35%)core::sync::atomic::atomic_compare_exchange_weak (135 samples, 0.35%)std::sync::rwlock::RwLock<T>::read (158 samples, 0.41%)std::sys::unix::locks::futex_rwlock::RwLock::read (158 samples, 0.41%)std::sys::unix::locks::futex_rwlock::is_read_lockable (8 samples, 0.02%)zssp::antireplay::Window<_,_>::check (10 samples, 0.03%)core::sync::atomic::AtomicU64::load (6 samples, 0.02%)core::sync::atomic::atomic_load (6 samples, 0.02%)arrayvec::arrayvec::ArrayVec<T,_>::push (19 samples, 0.05%)arrayvec::arrayvec_impl::ArrayVecImpl::push (19 samples, 0.05%)arrayvec::arrayvec_impl::ArrayVecImpl::try_push (19 samples, 0.05%)arrayvec::arrayvec_impl::ArrayVecImpl::push_unchecked (11 samples, 0.03%)core::ptr::write (11 samples, 0.03%)zssp::fragged::Fragged<Fragment,_>::assemble (59 samples, 0.15%)<T as core::convert::TryInto<U>>::try_into (39 samples, 0.10%)core::array::_<impl core::convert::TryFrom<&[T]> for [T: N]>::try_from (39 samples, 0.10%)core::result::Result<T,E>::map (39 samples, 0.10%)zssp::zeta::from_nonce (44 samples, 0.11%)<core::slice::iter::IterMut<T> as core::iter::traits::iterator::Iterator>::next (4 samples, 0.01%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (60 samples, 0.15%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (60 samples, 0.15%)core::sync::atomic::AtomicU32::fetch_sub (60 samples, 0.15%)core::sync::atomic::atomic_sub (60 samples, 0.15%)EVP_CIPHER_CTX_get_block_size (5 samples, 0.01%)CRYPTO_gcm128_decrypt (268 samples, 0.69%)[libcrypto.so.3] (104 samples, 0.27%)CRYPTO_gcm128_decrypt_ctr32 (512 samples, 1.32%)[libcrypto.so.3] (396 samples, 1.02%)[libcrypto.so.3] (25 samples, 0.06%)CRYPTO_gcm128_setiv (25 samples, 0.06%)[libcrypto.so.3] (22 samples, 0.06%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::decrypt_in_place (4,728 samples, 12.18%)<zssp::crypto_impl..zssp::crypto_impl::openssl::CipherCtx::update (4,725 samples, 12.17%)zssp::crypto_impl:..EVP_DecryptUpdate (4,725 samples, 12.17%)EVP_DecryptUpdate[libcrypto.so.3] (4,634 samples, 11.93%)[libcrypto.so.3][libcrypto.so.3] (4,632 samples, 11.93%)[libcrypto.so.3][libcrypto.so.3] (4,611 samples, 11.87%)[libcrypto.so.3][libcrypto.so.3] (3,756 samples, 9.67%)[libcrypto.so...[libcrypto.so.3] (3,594 samples, 9.26%)[libcrypto.so..core::ptr::drop_in_place<zssp::crypto_impl::openssl::AesGcmOpenSSLDec> (7 samples, 0.02%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (7 samples, 0.02%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (7 samples, 0.02%)std::sys::unix::locks::futex_mutex::Mutex::unlock (6 samples, 0.02%)core::sync::atomic::AtomicU32::swap (6 samples, 0.02%)core::sync::atomic::atomic_swap (6 samples, 0.02%)[libc.so.6] (4 samples, 0.01%)CRYPTO_gcm128_finish (47 samples, 0.12%)[libcrypto.so.3] (28 samples, 0.07%)zssp::crypto_impl::openssl::CipherCtx::finalize (63 samples, 0.16%)EVP_DecryptFinal_ex (63 samples, 0.16%)[libcrypto.so.3] (58 samples, 0.15%)[libcrypto.so.3] (56 samples, 0.14%)[libcrypto.so.3] (51 samples, 0.13%)OSSL_PARAM_locate (27 samples, 0.07%)[libc.so.6] (12 samples, 0.03%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::finish (152 samples, 0.39%)zssp::crypto_impl::openssl::CipherCtx::set_tag (82 samples, 0.21%)EVP_CIPHER_CTX_ctrl (82 samples, 0.21%)[libcrypto.so.3] (41 samples, 0.11%)std::sync::mutex::Mutex<T>::lock (16 samples, 0.04%)std::sys::unix::locks::futex_mutex::Mutex::lock (16 samples, 0.04%)core::sync::atomic::AtomicU32::compare_exchange (15 samples, 0.04%)core::sync::atomic::atomic_compare_exchange (15 samples, 0.04%)[libc.so.6] (22 samples, 0.06%)OSSL_PARAM_locate (47 samples, 0.12%)EVP_CIPHER_CTX_get_iv_length (68 samples, 0.18%)[libcrypto.so.3] (52 samples, 0.13%)[libc.so.6] (27 samples, 0.07%)OSSL_PARAM_locate (49 samples, 0.13%)strcmp@plt (7 samples, 0.02%)EVP_CIPHER_CTX_set_padding (86 samples, 0.22%)[libcrypto.so.3] (66 samples, 0.17%)OSSL_PARAM_locate_const (6 samples, 0.02%)EVP_CIPHER_free (15 samples, 0.04%)EVP_CipherInit_ex (252 samples, 0.65%)[libcrypto.so.3] (252 samples, 0.65%)EVP_CIPHER_up_ref (51 samples, 0.13%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_dec (270 samples, 0.70%)zssp::crypto_impl::openssl::CipherCtx::cipher_init (254 samples, 0.65%)core::slice::index::<impl core::ops::index::IndexMut<I> for [T]>::index_mut (7 samples, 0.02%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::index_mut (7 samples, 0.02%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::get_unchecked_mut (4 samples, 0.01%)<core::ops::range::Range<usize> as core::slice::index::SliceIndex<[T]>>::get_unchecked_mut (4 samples, 0.01%)arrayvec::arrayvec::ArrayVec<T,_>::remaining_capacity (5 samples, 0.01%)arrayvec::arrayvec::ArrayVec<T,_>::try_extend_from_slice (240 samples, 0.62%)core::intrinsics::copy_nonoverlapping (238 samples, 0.61%)[libc.so.6] (238 samples, 0.61%)std::io::impls::<impl std::io::Write for &mut W>::write (247 samples, 0.64%)<arrayvec::arrayvec::ArrayVec<u8,_> as std::io::Write>::write (247 samples, 0.64%)zssp::zeta::receive_payload_in_place (5,500 samples, 14.16%)zssp::zeta::receive_pa..zssp::antireplay::Window<_,_>::update (8 samples, 0.02%)core::sync::atomic::AtomicU64::fetch_max (8 samples, 0.02%)core::sync::atomic::atomic_umax (8 samples, 0.02%)zssp::zssp::Context<Crypto>::receive (8,391 samples, 21.61%)zssp::zssp::Context<Crypto>::recei..zssp::zssp::parse_fragment_header (101 samples, 0.26%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (73 samples, 0.19%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (73 samples, 0.19%)std::sys::unix::locks::futex_mutex::Mutex::unlock (70 samples, 0.18%)core::sync::atomic::AtomicU32::swap (66 samples, 0.17%)core::sync::atomic::atomic_swap (66 samples, 0.17%)std::sync::mutex::Mutex<T>::lock (77 samples, 0.20%)std::sys::unix::locks::futex_mutex::Mutex::lock (76 samples, 0.20%)core::sync::atomic::AtomicU32::compare_exchange (73 samples, 0.19%)core::sync::atomic::atomic_compare_exchange (73 samples, 0.19%)<zssp::crypto_impl::openssl::Aes256OpenSSLEnc as zssp::crypto::aes::Aes256Enc>::encrypt_in_place (321 samples, 0.83%)zssp::crypto_impl::openssl::CipherCtx::update (162 samples, 0.42%)EVP_EncryptUpdate (158 samples, 0.41%)[libcrypto.so.3] (139 samples, 0.36%)[libcrypto.so.3] (108 samples, 0.28%)[libcrypto.so.3] (99 samples, 0.25%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)<std::sync::mutex::MutexGuard<T> as core::ops::deref::Deref>::deref (10 samples, 0.03%)[libcrypto.so.3] (174 samples, 0.45%)CRYPTO_gcm128_encrypt (280 samples, 0.72%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)CRYPTO_gcm128_encrypt_ctr32 (476 samples, 1.23%)[libcrypto.so.3] (345 samples, 0.89%)[libcrypto.so.3] (31 samples, 0.08%)CRYPTO_gcm128_setiv (18 samples, 0.05%)[libcrypto.so.3] (15 samples, 0.04%)perf_ctx_enable (5 samples, 0.01%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::encrypt (3,908 samples, 10.06%)<zssp::crypto_i..zssp::crypto_impl::openssl::CipherCtx::update (3,898 samples, 10.04%)zssp::crypto_im..EVP_EncryptUpdate (3,895 samples, 10.03%)EVP_EncryptUpda..[libcrypto.so.3] (3,844 samples, 9.90%)[libcrypto.so...[libcrypto.so.3] (3,838 samples, 9.88%)[libcrypto.so...[libcrypto.so.3] (3,819 samples, 9.83%)[libcrypto.so...[libcrypto.so.3] (3,007 samples, 7.74%)[libcrypto...[libcrypto.so.3] (2,753 samples, 7.09%)[libcrypto..asm_sysvec_reschedule_ipi (10 samples, 0.03%)sysvec_reschedule_ipi (10 samples, 0.03%)irqentry_exit (10 samples, 0.03%)irqentry_exit_to_user_mode (10 samples, 0.03%)exit_to_user_mode_prepare (10 samples, 0.03%)exit_to_user_mode_loop (10 samples, 0.03%)schedule (10 samples, 0.03%)__schedule (10 samples, 0.03%)finish_task_switch.isra.0 (10 samples, 0.03%)__perf_event_task_sched_in (10 samples, 0.03%)perf_pmu_nop_void (5 samples, 0.01%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc> (11 samples, 0.03%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (11 samples, 0.03%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (11 samples, 0.03%)std::sys::unix::locks::futex_mutex::Mutex::unlock (11 samples, 0.03%)core::sync::atomic::AtomicU32::swap (11 samples, 0.03%)core::sync::atomic::atomic_swap (11 samples, 0.03%)zssp::crypto_impl::openssl::CipherCtx::finalize (60 samples, 0.15%)EVP_EncryptFinal_ex (60 samples, 0.15%)[libcrypto.so.3] (53 samples, 0.14%)[libcrypto.so.3] (53 samples, 0.14%)[libcrypto.so.3] (48 samples, 0.12%)CRYPTO_gcm128_tag (48 samples, 0.12%)CRYPTO_gcm128_finish (40 samples, 0.10%)[libcrypto.so.3] (26 samples, 0.07%)[libc.so.6] (21 samples, 0.05%)OSSL_PARAM_locate (45 samples, 0.12%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::finish (153 samples, 0.39%)zssp::crypto_impl::openssl::CipherCtx::get_tag (80 samples, 0.21%)EVP_CIPHER_CTX_ctrl (79 samples, 0.20%)[libcrypto.so.3] (55 samples, 0.14%)OSSL_PARAM_set_octet_string (5 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (13 samples, 0.03%)std::sys::unix::locks::futex_mutex::Mutex::lock (13 samples, 0.03%)core::sync::atomic::AtomicU32::compare_exchange (13 samples, 0.03%)core::sync::atomic::atomic_compare_exchange (13 samples, 0.03%)[libcrypto.so.3] (40 samples, 0.10%)OSSL_PARAM_locate (36 samples, 0.09%)[libc.so.6] (20 samples, 0.05%)EVP_CIPHER_CTX_get_iv_length (67 samples, 0.17%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_pmu_nop_void (5 samples, 0.01%)[libc.so.6] (17 samples, 0.04%)OSSL_PARAM_locate (31 samples, 0.08%)strcmp@plt (5 samples, 0.01%)EVP_CIPHER_CTX_set_padding (67 samples, 0.17%)[libcrypto.so.3] (49 samples, 0.13%)OSSL_PARAM_locate_const (6 samples, 0.02%)EVP_CIPHER_free (21 samples, 0.05%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_enc (206 samples, 0.53%)zssp::crypto_impl::openssl::CipherCtx::cipher_init (193 samples, 0.50%)EVP_CipherInit_ex (193 samples, 0.50%)[libcrypto.so.3] (193 samples, 0.50%)EVP_CIPHER_up_ref (27 samples, 0.07%)__rdl_alloc (6 samples, 0.02%)std::sys::unix::alloc::<impl core::alloc::global::GlobalAlloc for std::alloc::System>::alloc (6 samples, 0.02%)[libc.so.6] (60 samples, 0.15%)[libc.so.6] (978 samples, 2.52%)[l..asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)__entry_text_start (4 samples, 0.01%)__get_user_nocheck_4 (14 samples, 0.04%)futex_q_lock (5 samples, 0.01%)futex_q_unlock (17 samples, 0.04%)futex_wait (45 samples, 0.12%)futex_wait_setup (42 samples, 0.11%)do_futex (46 samples, 0.12%)__x64_sys_futex (47 samples, 0.12%)entry_SYSCALL_64_after_hwframe (52 samples, 0.13%)do_syscall_64 (52 samples, 0.13%)syscall_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)__lll_lock_wait_private (74 samples, 0.19%)__entry_text_start (10 samples, 0.03%)futex_hash (7 samples, 0.02%)_raw_spin_lock (8 samples, 0.02%)native_queued_spin_lock_slowpath (8 samples, 0.02%)futex_wake_mark (10 samples, 0.03%)wake_q_add_safe (4 samples, 0.01%)__smp_call_single_queue (12 samples, 0.03%)native_send_call_func_single_ipi (8 samples, 0.02%)x2apic_send_IPI (7 samples, 0.02%)native_write_msr (5 samples, 0.01%)llist_add_batch (10 samples, 0.03%)try_to_wake_up (77 samples, 0.20%)ttwu_queue_wakelist (37 samples, 0.10%)futex_wake (145 samples, 0.37%)wake_up_q (85 samples, 0.22%)do_futex (158 samples, 0.41%)__x64_sys_futex (159 samples, 0.41%)exit_to_user_mode_prepare (6 samples, 0.02%)do_syscall_64 (172 samples, 0.44%)syscall_exit_to_user_mode (8 samples, 0.02%)entry_SYSCALL_64_after_hwframe (175 samples, 0.45%)alloc::vec::Vec<T,A>::with_capacity_in (1,972 samples, 5.08%)alloc:..alloc::raw_vec::RawVec<T,A>::with_capacity_in (1,972 samples, 5.08%)alloc:..alloc::raw_vec::RawVec<T,A>::allocate_in (1,972 samples, 5.08%)alloc:..<alloc::alloc::Global as core::alloc::Allocator>::allocate (1,970 samples, 5.07%)<alloc..alloc::alloc::Global::alloc_impl (1,970 samples, 5.07%)alloc:..alloc::alloc::alloc (1,970 samples, 5.07%)alloc:..malloc (1,962 samples, 5.05%)malloc__lll_lock_wake_private (192 samples, 0.49%)alloc::slice::<impl [T]>::to_vec (2,175 samples, 5.60%)alloc::..alloc::slice::<impl [T]>::to_vec_in (2,175 samples, 5.60%)alloc::..alloc::slice::hack::to_vec (2,175 samples, 5.60%)alloc::..<T as alloc::slice::hack::ConvertVec>::to_vec (2,175 samples, 5.60%)<T as a..core::ptr::const_ptr::<impl *const T>::copy_to_nonoverlapping (203 samples, 0.52%)core::intrinsics::copy_nonoverlapping (203 samples, 0.52%)[libc.so.6] (201 samples, 0.52%)core::sync::atomic::AtomicUsize::compare_exchange_weak (118 samples, 0.30%)core::sync::atomic::atomic_compare_exchange_weak (118 samples, 0.30%)std::sync::mpmc::array::Channel<T>::start_send (183 samples, 0.47%)core::sync::atomic::AtomicUsize::load (37 samples, 0.10%)core::sync::atomic::atomic_load (37 samples, 0.10%)core::ptr::mut_ptr::<impl *mut T>::write (7 samples, 0.02%)core::ptr::write (7 samples, 0.02%)benchmark::bob_main::_{{closure}} (3,947 samples, 10.16%)benchmark::bob_..std::sync::mpsc::SyncSender<T>::send (1,771 samples, 4.56%)std::..std::sync::mpmc::Sender<T>::send (1,764 samples, 4.54%)std::..std::sync::mpmc::array::Channel<T>::send (219 samples, 0.56%)std::sync::mpmc::array::Channel<T>::write (17 samples, 0.04%)std::sync::mpmc::waker::SyncWaker::notify (5 samples, 0.01%)core::mem::drop (38 samples, 0.10%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<zssp::zeta::MutableState<benchmark::TestApplication>>> (38 samples, 0.10%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (38 samples, 0.10%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (38 samples, 0.10%)core::sync::atomic::AtomicU32::fetch_sub (36 samples, 0.09%)core::sync::atomic::atomic_sub (36 samples, 0.09%)core::slice::<impl [T]>::copy_from_slice (10 samples, 0.03%)core::intrinsics::copy_nonoverlapping (10 samples, 0.03%)<core::ops::range::Range<usize> as core::slice::index::SliceIndex<[T]>>::index (6 samples, 0.02%)core::slice::index::<impl core::ops::index::Index<I> for [T]>::index (11 samples, 0.03%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::index (5 samples, 0.01%)core::slice::index::<impl core::ops::index::IndexMut<I> for [T]>::index_mut (5 samples, 0.01%)<core::ops::range::Range<usize> as core::slice::index::SliceIndex<[T]>>::index_mut (5 samples, 0.01%)core::sync::atomic::AtomicU32::compare_exchange_weak (34 samples, 0.09%)core::sync::atomic::atomic_compare_exchange_weak (34 samples, 0.09%)std::sync::rwlock::RwLock<T>::read (38 samples, 0.10%)std::sys::unix::locks::futex_rwlock::RwLock::read (38 samples, 0.10%)benchmark::bob_main (17,970 samples, 46.28%)benchmark::bob_mainzssp::zssp::Context<Crypto>::send (8,668 samples, 22.32%)zssp::zssp::Context<Crypto>::sendzssp::zeta::send_payload (8,667 samples, 22.32%)zssp::zeta::send_payloadzssp::zeta::get_counter (8 samples, 0.02%)core::sync::atomic::AtomicU64::fetch_add (7 samples, 0.02%)core::sync::atomic::atomic_add (7 samples, 0.02%)cfree (25 samples, 0.06%)clock_gettime (17 samples, 0.04%)core::hash::BuildHasher::hash_one (25 samples, 0.06%)core::hash::impls::<impl core::hash::Hash for &T>::hash (18 samples, 0.05%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::fragged::Fragged<alloc::vec::Vec<u8>,48_usize>>> (4 samples, 0.01%)core::result::Result<T,E>::unwrap (10 samples, 0.03%)malloc (11 samples, 0.03%)std::sync::mpmc::Receiver<T>::recv_timeout (25 samples, 0.06%)std::sync::mpmc::Receiver<T>::recv_deadline (10 samples, 0.03%)std::sync::mpmc::Sender<T>::send (13 samples, 0.03%)std::sync::mpmc::array::Channel<T>::recv (28 samples, 0.07%)std::sync::mpmc::array::Channel<T>::send (14 samples, 0.04%)std::sync::mpmc::array::Channel<T>::start_recv (21 samples, 0.05%)std::sync::mpmc::utils::Backoff::new (22 samples, 0.06%)std::sync::mpmc::waker::SyncWaker::notify (22 samples, 0.06%)core::sync::atomic::AtomicBool::load (5 samples, 0.01%)core::sync::atomic::atomic_load (5 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (9 samples, 0.02%)std::sys::unix::time::Timespec::sub_timespec (9 samples, 0.02%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (9 samples, 0.02%)std::time::SystemTime::checked_add (7 samples, 0.02%)zssp::fragged::Fragged<Fragment,_>::assemble (19 samples, 0.05%)zssp::zeta::from_nonce (8 samples, 0.02%)alloc::vec::Vec<T,A>::with_capacity_in (20 samples, 0.05%)alloc::raw_vec::RawVec<T,A>::with_capacity_in (20 samples, 0.05%)alloc::raw_vec::RawVec<T,A>::allocate_in (20 samples, 0.05%)<alloc::alloc::Global as core::alloc::Allocator>::allocate (20 samples, 0.05%)alloc::alloc::Global::alloc_impl (20 samples, 0.05%)alloc::alloc::alloc (20 samples, 0.05%)alloc::slice::<impl [T]>::to_vec (29 samples, 0.07%)alloc::slice::<impl [T]>::to_vec_in (29 samples, 0.07%)alloc::slice::hack::to_vec (29 samples, 0.07%)<T as alloc::slice::hack::ConvertVec>::to_vec (29 samples, 0.07%)core::ptr::const_ptr::<impl *const T>::copy_to_nonoverlapping (9 samples, 0.02%)core::intrinsics::copy_nonoverlapping (9 samples, 0.02%)zssp::zeta::send_payload (76 samples, 0.20%)benchmark::bob_main::_{{closure}} (34 samples, 0.09%)std::sync::mpsc::SyncSender<T>::send (5 samples, 0.01%)core::ptr::drop_in_place<arrayvec::arrayvec::ArrayVec<alloc::vec::Vec<u8>,48_usize>> (6 samples, 0.02%)<arrayvec::arrayvec::ArrayVec<T,_> as core::ops::drop::Drop>::drop (6 samples, 0.02%)arrayvec::arrayvec::ArrayVec<T,_>::clear (6 samples, 0.02%)arrayvec::arrayvec_impl::ArrayVecImpl::clear (6 samples, 0.02%)arrayvec::arrayvec_impl::ArrayVecImpl::truncate (6 samples, 0.02%)core::ptr::drop_in_place<[alloc::vec::Vec<u8>]> (6 samples, 0.02%)core::ptr::drop_in_place<alloc::vec::Vec<u8>> (6 samples, 0.02%)core::ptr::drop_in_place<alloc::raw_vec::RawVec<u8>> (6 samples, 0.02%)<alloc::raw_vec::RawVec<T,A> as core::ops::drop::Drop>::drop (6 samples, 0.02%)<alloc::alloc::Global as core::alloc::Allocator>::deallocate (6 samples, 0.02%)alloc::alloc::dealloc (6 samples, 0.02%)std::collections::hash::map::HashMap<K,V,S>::get (5 samples, 0.01%)hashbrown::map::HashMap<K,V,S,A>::get (5 samples, 0.01%)hashbrown::map::HashMap<K,V,S,A>::get_inner (5 samples, 0.01%)hashbrown::map::make_hash (5 samples, 0.01%)zssp::zssp::Context<Crypto>::receive (78 samples, 0.20%)std::sync::mpmc::array::Channel<T>::start_recv (8 samples, 0.02%)[unknown] (37,624 samples, 96.89%)[unknown]zssp::zssp::parse_fragment_header (7 samples, 0.02%)EVP_DecryptUpdate (24 samples, 0.06%)__bss_start (26 samples, 0.07%)__rust_probestack (6 samples, 0.02%)entry_SYSCALL_64_after_hwframe (7 samples, 0.02%)entry_SYSCALL_64_safe_stack (14 samples, 0.04%)ret_from_fork (10 samples, 0.03%)schedule_tail (10 samples, 0.03%)finish_task_switch.isra.0 (10 samples, 0.03%)__perf_event_task_sched_in (10 samples, 0.03%)perf_ctx_enable (10 samples, 0.03%)core::ptr::drop_in_place<arrayvec::arrayvec::ArrayVec<alloc::vec::Vec<u8>,48_usize>> (5 samples, 0.01%)<arrayvec::arrayvec::ArrayVec<T,_> as core::ops::drop::Drop>::drop (5 samples, 0.01%)arrayvec::arrayvec::ArrayVec<T,_>::clear (5 samples, 0.01%)arrayvec::arrayvec_impl::ArrayVecImpl::clear (5 samples, 0.01%)arrayvec::arrayvec_impl::ArrayVecImpl::truncate (5 samples, 0.01%)core::ptr::drop_in_place<[alloc::vec::Vec<u8>]> (5 samples, 0.01%)core::ptr::drop_in_place<alloc::vec::Vec<u8>> (5 samples, 0.01%)core::ptr::drop_in_place<alloc::raw_vec::RawVec<u8>> (5 samples, 0.01%)<alloc::raw_vec::RawVec<T,A> as core::ops::drop::Drop>::drop (5 samples, 0.01%)<alloc::alloc::Global as core::alloc::Allocator>::deallocate (5 samples, 0.01%)alloc::alloc::dealloc (5 samples, 0.01%)benchmark (38,828 samples, 99.99%)benchmarkzssp::zssp::Context<Crypto>::receive (58 samples, 0.15%)std::collections::hash::map::HashMap<K,V,S>::get (6 samples, 0.02%)hashbrown::map::HashMap<K,V,S,A>::get (6 samples, 0.02%)hashbrown::map::HashMap<K,V,S,A>::get_inner (6 samples, 0.02%)hashbrown::map::make_hash (6 samples, 0.02%)all (38,833 samples, 100%)perf-exec (5 samples, 0.01%)entry_SYSCALL_64_after_hwframe (5 samples, 0.01%)do_syscall_64 (5 samples, 0.01%)__x64_sys_execve (5 samples, 0.01%)do_execveat_common.isra.0 (5 samples, 0.01%)bprm_execve (5 samples, 0.01%)bprm_execve.part.0 (5 samples, 0.01%)exec_binprm (5 samples, 0.01%)search_binary_handler (5 samples, 0.01%)load_elf_binary (5 samples, 0.01%)begin_new_exec (5 samples, 0.01%)perf_event_exec (5 samples, 0.01%)perf_event_enable_on_exec (4 samples, 0.01%)ctx_resched (4 samples, 0.01%)perf_ctx_enable (4 samples, 0.01%) \ No newline at end of file From 609c352957c003ad3b2fff9ef7877434643c16bf Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 14 Aug 2023 17:59:14 -0400 Subject: [PATCH 41/50] got rid of extra lines --- src/zeta.rs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/zeta.rs b/src/zeta.rs index cd4ce9a..8af5e23 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -1376,13 +1376,8 @@ pub(crate) fn received_k1_trans Date: Tue, 15 Aug 2023 12:11:59 -0400 Subject: [PATCH 42/50] made cipherctx pub --- src/crypto_impl/openssl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crypto_impl/openssl.rs b/src/crypto_impl/openssl.rs index ce2b25b..43125ae 100644 --- a/src/crypto_impl/openssl.rs +++ b/src/crypto_impl/openssl.rs @@ -7,7 +7,7 @@ use openssl_sys::*; use crate::crypto::*; -struct CipherCtx(NonNull); +pub struct CipherCtx(NonNull); impl Drop for CipherCtx { fn drop(&mut self) { unsafe { From ddab1cf216db1d3c853198f3e99381f04cbc4f2f Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 15 Aug 2023 17:15:29 -0400 Subject: [PATCH 43/50] added kyber update --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- src/crypto_impl/kyber1024.rs | 4 +++- src/frag_cache.rs | 4 ++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 50f595c..e1e0058 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -215,9 +215,9 @@ checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "pqc_kyber" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c578a7eab95d8649f115dd6f90894d167766e82a6bcf66ff25f60e7dfc857e" +checksum = "1b5dd33c0b42d244b01ab4f6cabaeb03c3b875017780fb3903b53b9c91fb6663" dependencies = [ "rand_core", ] diff --git a/Cargo.toml b/Cargo.toml index 80513a0..0078064 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ rand_core = { version = "0.6.4" } zeroize = { version = "1.6.0" } arrayvec = { version = "0.7.4", default-features = false, features = ["std", "zeroize"] } -pqc_kyber = { version = "0.6.0", default-features = false, features = ["kyber1024", "std"], optional = true } +pqc_kyber = { version = "0.7.0", default-features = false, features = ["kyber1024", "std"], 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 } diff --git a/src/crypto_impl/kyber1024.rs b/src/crypto_impl/kyber1024.rs index feffd5f..4a70105 100644 --- a/src/crypto_impl/kyber1024.rs +++ b/src/crypto_impl/kyber1024.rs @@ -8,7 +8,9 @@ use crate::crypto::*; 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); + // According to the source code this can only fail if the RNG fails. + // Idk why rust allows RNG to fail. + let keypair = pqc_kyber::keypair(rng).unwrap(); (Zeroizing::new(keypair.secret), keypair.public) } diff --git a/src/frag_cache.rs b/src/frag_cache.rs index e9f15b9..5fdd804 100644 --- a/src/frag_cache.rs +++ b/src/frag_cache.rs @@ -117,7 +117,7 @@ impl UnassociatedFragCache { if self.map[idx].key == 0 { // This is a new entry so initialize it. if (fragment_count as usize) <= self.frags_unused_size { - let mut entry = &mut self.map[idx]; + let entry = &mut self.map[idx]; entry.key = key; entry.frags_idx = self.frags_first_unused as u32; entry.fragment_have = 0; @@ -135,7 +135,7 @@ impl UnassociatedFragCache { return; } } - let mut entry = &mut self.map[idx]; + let entry = &mut self.map[idx]; let new_size = entry.packet_size + fragment_size as u32; let got = 1u64.wrapping_shl(fragment_no as u32); From 49bc88d0e6bc23ad26e3aacbe8b87a153451fa8d Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 16 Aug 2023 11:16:47 -0400 Subject: [PATCH 44/50] added a pool --- examples/benchmark.rs | 79 +++++++++++++++++++++++++++++-------------- 1 file changed, 53 insertions(+), 26 deletions(-) diff --git a/examples/benchmark.rs b/examples/benchmark.rs index b2ec3fb..b3be688 100644 --- a/examples/benchmark.rs +++ b/examples/benchmark.rs @@ -1,5 +1,5 @@ use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{mpsc, Arc}; +use std::sync::{mpsc, Arc, Mutex}; use std::thread; use std::time::{Duration, Instant}; @@ -13,7 +13,6 @@ use zssp::application::{ }; use zssp::crypto::P384KeyPair; use zssp::crypto_impl::*; -use zssp::result::ReceiveError; use zssp::Session; const TEST_MTU: usize = 1500; @@ -22,6 +21,34 @@ struct TestApplication { time: Instant, } +struct PooledVec(Vec); +static POOL: Mutex>> = Mutex::new(Vec::new()); +fn alloc(b: &[u8]) -> PooledVec { + let mut p = POOL.lock().unwrap(); + let mut v = p.pop().unwrap_or_default(); + v.extend(b); + PooledVec(v) +} +impl Drop for PooledVec { + fn drop(&mut self) { + let mut p = POOL.lock().unwrap(); + let mut v = Vec::new(); + std::mem::swap(&mut self.0, &mut v); + v.clear(); + p.push(v); + } +} +impl AsMut<[u8]> for PooledVec { + fn as_mut(&mut self) -> &mut [u8] { + self.0.as_mut() + } +} +impl AsRef<[u8]> for PooledVec { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + #[allow(unused)] impl CryptoLayer for TestApplication { type Rng = OsRng; @@ -37,7 +64,7 @@ impl CryptoLayer for TestApplication { type SessionData = (); - type IncomingPacketBuffer = Vec; + type IncomingPacketBuffer = PooledVec; } #[allow(unused)] impl ApplicationLayer for &TestApplication { @@ -97,8 +124,8 @@ impl ApplicationLayer for &TestApplication { fn alice_main( run: &AtomicBool, alice_app: &TestApplication, - alice_out: mpsc::SyncSender>, - alice_in: mpsc::Receiver>, + alice_out: mpsc::SyncSender, + alice_in: mpsc::Receiver, alice_keypair: P384CrateKeyPair, bob_pubkey: P384CratePublicKey, ) { @@ -113,7 +140,7 @@ fn alice_main( context .open( alice_app, - |b| alice_out.send(b.to_vec()).is_ok(), + |b| alice_out.send(alloc(b)).is_ok(), TEST_MTU, bob_pubkey.clone(), (), @@ -132,9 +159,9 @@ fn alice_main( output_data.clear(); match context.receive( alice_app, - |b| alice_out.send(b.to_vec()).is_ok(), + |b| alice_out.send(alloc(b)).is_ok(), TEST_MTU, - |_| Some((|b: &mut [u8]| alice_out.send(b.to_vec()).is_ok(), TEST_MTU)), + |_| Some((|b: &mut [u8]| alice_out.send(alloc(b)).is_ok(), TEST_MTU)), &0, pkt, &mut output_data, @@ -154,10 +181,10 @@ fn alice_main( _ => panic!(), }, Err(e) => { - println!("[alice] ERROR {:?}", e); - if let ReceiveError::ByzantineFault { unnatural, .. } = e { - assert!(!unnatural) - } + //println!("[alice] ERROR {:?}", e); + //if let ReceiveError::ByzantineFault { unnatural, .. } = e { + // assert!(!unnatural) + //} } } } else { @@ -169,7 +196,7 @@ fn alice_main( context .send( alice_session.as_ref().unwrap(), - |b| alice_out.send(b.to_vec()).is_ok(), + |b| alice_out.send(alloc(b)).is_ok(), &mut [0u8; TEST_MTU], &test_data[..1400 + ((OsRng.next_u64() as usize) % (test_data.len() - 1400))], ) @@ -181,7 +208,7 @@ fn alice_main( if current_time >= next_service { next_service = current_time + context.service(alice_app, |_| { - Some((|b: &mut [u8]| alice_out.send(b.to_vec()).is_ok(), TEST_MTU)) + Some((|b: &mut [u8]| alice_out.send(alloc(b)).is_ok(), TEST_MTU)) }); } } @@ -191,8 +218,8 @@ fn alice_main( fn bob_main( run: &AtomicBool, bob_app: &TestApplication, - bob_out: mpsc::SyncSender>, - bob_in: mpsc::Receiver>, + bob_out: mpsc::SyncSender, + bob_in: mpsc::Receiver, bob_keypair: P384CrateKeyPair, ) { let startup_time = std::time::Instant::now(); @@ -214,9 +241,9 @@ fn bob_main( output_data.clear(); match context.receive( bob_app, - |b| bob_out.send(b.to_vec()).is_ok(), + |b| bob_out.send(alloc(b)).is_ok(), TEST_MTU, - |_| Some((|b: &mut [u8]| bob_out.send(b.to_vec()).is_ok(), TEST_MTU)), + |_| Some((|b: &mut [u8]| bob_out.send(alloc(b)).is_ok(), TEST_MTU)), &0, pkt, &mut output_data, @@ -234,7 +261,7 @@ fn bob_main( context .send( &s, - |b| bob_out.send(b.to_vec()).is_ok(), + |b| bob_out.send(alloc(b)).is_ok(), &mut [0u8; TEST_MTU], &output_data, ) @@ -244,10 +271,10 @@ fn bob_main( _ => panic!(), }, Err(e) => { - println!("[bob] ERROR {:?}", e); - if let ReceiveError::ByzantineFault { unnatural, .. } = e { - assert!(!unnatural) - } + //println!("[bob] ERROR {:?}", e); + //if let ReceiveError::ByzantineFault { unnatural, .. } = e { + // assert!(!unnatural) + //} } } } @@ -265,7 +292,7 @@ fn bob_main( if current_time >= next_service { next_service = current_time + context.service(bob_app, |_| { - Some((|b: &mut [u8]| bob_out.send(b.to_vec()).is_ok(), TEST_MTU)) + Some((|b: &mut [u8]| bob_out.send(alloc(b)).is_ok(), TEST_MTU)) }); } } @@ -280,8 +307,8 @@ fn core(time: u64) { let bob_pubkey = bob_keypair.public_key(); let bob_app = TestApplication { time: Instant::now() }; - let (alice_out, bob_in) = mpsc::sync_channel::>(256); - let (bob_out, alice_in) = mpsc::sync_channel::>(256); + let (alice_out, bob_in) = mpsc::sync_channel::(256); + let (bob_out, alice_in) = mpsc::sync_channel::(256); thread::scope(|ts| { { From fea819601af64964196f94194fbbc28a234e52a9 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 16 Aug 2023 11:35:46 -0400 Subject: [PATCH 45/50] added docs for benchmark fix --- examples/benchmark.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/examples/benchmark.rs b/examples/benchmark.rs index b3be688..58e5534 100644 --- a/examples/benchmark.rs +++ b/examples/benchmark.rs @@ -14,6 +14,7 @@ use zssp::application::{ use zssp::crypto::P384KeyPair; use zssp::crypto_impl::*; use zssp::Session; +use zssp::result::ReceiveError; const TEST_MTU: usize = 1500; @@ -21,6 +22,8 @@ struct TestApplication { time: Instant, } +/// We have to pool allocations or else variations in the speed of the memory allocator will bias +/// our performance stats. struct PooledVec(Vec); static POOL: Mutex>> = Mutex::new(Vec::new()); fn alloc(b: &[u8]) -> PooledVec { @@ -181,10 +184,10 @@ fn alice_main( _ => panic!(), }, Err(e) => { - //println!("[alice] ERROR {:?}", e); - //if let ReceiveError::ByzantineFault { unnatural, .. } = e { - // assert!(!unnatural) - //} + println!("[alice] ERROR {:?}", e); + if let ReceiveError::ByzantineFault { unnatural, .. } = e { + assert!(!unnatural) + } } } } else { @@ -271,10 +274,10 @@ fn bob_main( _ => panic!(), }, Err(e) => { - //println!("[bob] ERROR {:?}", e); - //if let ReceiveError::ByzantineFault { unnatural, .. } = e { - // assert!(!unnatural) - //} + println!("[bob] ERROR {:?}", e); + if let ReceiveError::ByzantineFault { unnatural, .. } = e { + assert!(!unnatural) + } } } } From 4c3861624e728d7a116540cc2402f4c8e004420a Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 16 Aug 2023 11:52:55 -0400 Subject: [PATCH 46/50] updated docs --- src/zssp.rs | 31 ++++++------------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/src/zssp.rs b/src/zssp.rs index 27b24a6..91f8e66 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -131,12 +131,10 @@ impl Context { /// * `send` - Function to be called to send one or more initial packets to the remote being /// contacted /// * `mtu` - MTU for initial packets - /// * `remote_static_key` - Remote side's static public NIST P-384 key - /// * `application_data` - Arbitrary data meaningful to the application to include with session + /// * `static_remote_key` - Remote side's static public NIST P-384 key + /// * `session_data` - Arbitrary data meaningful to the application to include with session /// object - /// * `ratchet_state` - The last saved and confirmed ratchet state associated with this remote - /// peer, or None if we do not have one. - /// * `local_identity_blob` - Payload to be sent to Bob that contains the information necessary + /// * `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, @@ -176,25 +174,12 @@ impl Context { /// session and will always result in a new session ReceiveOk being returned. /// /// * `app` - Interface to application using ZSSP - /// * `check_allow_incoming_session` - Function to call to check whether an unidentified new - /// session should be accepted - /// * `check_accept_session` - Function to accept sessions after final negotiation. - /// The second argument is the identity blob that the remote peer sent us. The application - /// must verify this identity is associated with the remote peer's static key. - /// The third argument is the ratchet chain length, or ratchet count. - /// To prevent desync, if this function returns (Some(_), _), no other open session with the - /// same remote peer must exist. /// * `send_unassociated_reply` - Function to send reply packets directly when no session exists /// * `send_unassociated_mtu` - MTU for unassociated replies /// * `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 - /// * `data_buf` - Buffer to receive decrypted and authenticated object data (an error is - /// returned if too small) - /// * `incoming_physical_packet_buf` - Buffer containing incoming wire packet - /// (receive() takes ownership) - /// * `current_time` - Current time in milliseconds. Does not have to be monotonic, nor synced - /// with the remote peer. Used to check the state of local offers we may currently have or want - /// to put in-flight. + /// * `incoming_fragment_buf` - Buffer containing incoming wire packet (the context takes ownership) + /// * `output_buffer` - Buffer to receive decrypted and authenticated object data pub fn receive<'a, App: ApplicationLayer, SendFn: FnMut(&mut [u8]) -> bool>( &self, mut app: App, @@ -605,7 +590,6 @@ impl Context { /// slice of `data` /// * `mtu_sized_buffer` - A writable work buffer whose size equals the MTU /// * `data` - Data to send - /// * `current_time` - Current time in milliseconds pub fn send( &self, session: &Arc>, @@ -622,11 +606,8 @@ impl Context { /// try to satisfy this but small variations in timing of up to +/- a second or two are not /// a problem. /// + /// * `app` - Interface to application using ZSSP /// * `send_to` - Function to get a sender and an MTU to send something over an active session - /// * `current_time` - 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 - /// should rekey. pub fn service, SendFn: FnMut(&mut [u8]) -> bool>( &self, mut app: App, From 9604f8268abd3b8bdf8ff3b7d9b63698002aa405 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 16 Aug 2023 11:53:57 -0400 Subject: [PATCH 47/50] fixed .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c3a4a94..b3dabfd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ /target perf*.data perf*.old -.svg +*.svg From 5739911ddd827001119333033297ce5340c065ff Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 16 Aug 2023 12:05:51 -0400 Subject: [PATCH 48/50] added default crypto trait --- Cargo.toml | 6 ++---- examples/basic_test.rs | 2 +- examples/benchmark.rs | 16 ++-------------- src/crypto_impl/kyber1024.rs | 4 ++-- src/crypto_impl/mod.rs | 22 ++++++++++++++++++++++ 5 files changed, 29 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0078064..c7ec4f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,10 +25,8 @@ hmac = { version = "0.12.1", default-features = false, optional = true } openssl-sys = { version = "0.9.91", default-features = false, optional = true } [features] -default = ["debug", "p384", "sha2", "pqc_kyber", "openssl-sys"] +default = ["debug", "default-crypto"] +default-crypto = ["p384", "sha2", "pqc_kyber", "openssl-sys", "rand_core/getrandom"] sha2 = ["dep:sha2", "dep:hmac"] logging = [] debug = ["logging"] - -[dev-dependencies] -rand_core = { version = "0.6.4", features = ["getrandom"] } diff --git a/examples/basic_test.rs b/examples/basic_test.rs index 11e5d0b..58a8ed5 100644 --- a/examples/basic_test.rs +++ b/examples/basic_test.rs @@ -57,7 +57,7 @@ impl CryptoLayer for TestApplication { type Hmac = HmacSha512Crate; type PublicKey = P384CratePublicKey; type KeyPair = P384CrateKeyPair; - type Kem = RustKyber1024PrivateKey; + type Kem = Kyber1024CratePrivateKey; type SessionData = u128; diff --git a/examples/benchmark.rs b/examples/benchmark.rs index 58e5534..cc30885 100644 --- a/examples/benchmark.rs +++ b/examples/benchmark.rs @@ -8,7 +8,7 @@ use rand_core::OsRng; use rand_core::RngCore; use zssp::application::{ - AcceptAction, ApplicationLayer, CryptoLayer, IncomingSessionAction, RatchetState, RatchetStates, RatchetUpdate, + AcceptAction, ApplicationLayer, IncomingSessionAction, RatchetState, RatchetStates, RatchetUpdate, RATCHET_SIZE, }; use zssp::crypto::P384KeyPair; @@ -53,20 +53,8 @@ impl AsRef<[u8]> for PooledVec { } #[allow(unused)] -impl CryptoLayer for TestApplication { - type Rng = OsRng; - type PrpEnc = Aes256OpenSSLEnc; - type PrpDec = Aes256OpenSSLDec; - type Aead = AesGcmOpenSSL; - type AeadPool = AesGcmOpenSSLPool; - type Hash = Sha512Crate; - type Hmac = HmacSha512Crate; - type PublicKey = P384CratePublicKey; - type KeyPair = P384CrateKeyPair; - type Kem = RustKyber1024PrivateKey; - +impl DefaultCrypto for TestApplication { type SessionData = (); - type IncomingPacketBuffer = PooledVec; } #[allow(unused)] diff --git a/src/crypto_impl/kyber1024.rs b/src/crypto_impl/kyber1024.rs index 4a70105..dd50978 100644 --- a/src/crypto_impl/kyber1024.rs +++ b/src/crypto_impl/kyber1024.rs @@ -5,8 +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 RustKyber1024PrivateKey = Zeroizing<[u8; pqc_kyber::KYBER_SECRETKEYBYTES]>; -impl Kyber1024PrivateKey for RustKyber1024PrivateKey { +pub type Kyber1024CratePrivateKey = Zeroizing<[u8; pqc_kyber::KYBER_SECRETKEYBYTES]>; +impl Kyber1024PrivateKey for Kyber1024CratePrivateKey { fn generate(rng: &mut Rng) -> (Self, [u8; KYBER_PUBLIC_KEY_SIZE]) { // According to the source code this can only fail if the RNG fails. // Idk why rust allows RNG to fail. diff --git a/src/crypto_impl/mod.rs b/src/crypto_impl/mod.rs index d481ff4..e08234b 100644 --- a/src/crypto_impl/mod.rs +++ b/src/crypto_impl/mod.rs @@ -27,3 +27,25 @@ mod openssl; pub use openssl::*; #[cfg(feature = "openssl-sys")] pub use openssl_sys; + +#[cfg(feature = "default-crypto")] +pub trait DefaultCrypto { + type SessionData; + type IncomingPacketBuffer: AsMut<[u8]> + AsRef<[u8]>; +} +#[cfg(feature = "default-crypto")] +impl crate::application::CryptoLayer for C { + type Rng = rand_core::OsRng; + type PrpEnc = Aes256OpenSSLEnc; + type PrpDec = Aes256OpenSSLDec; + type Aead = AesGcmOpenSSL; + type AeadPool = AesGcmOpenSSLPool; + type Hash = Sha512Crate; + type Hmac = HmacSha512Crate; + type PublicKey = P384CratePublicKey; + type KeyPair = P384CrateKeyPair; + type Kem = Kyber1024CratePrivateKey; + + type SessionData = C::SessionData; + type IncomingPacketBuffer = C::IncomingPacketBuffer; +} From 5a9296c2cae5aaadc31d183afa98146406cd22d8 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 16 Aug 2023 12:10:14 -0400 Subject: [PATCH 49/50] renamed crypto impls --- examples/basic_test.rs | 34 ++++++++-------- examples/benchmark.rs | 16 ++++---- src/crypto_impl/kyber1024.rs | 4 +- src/crypto_impl/mod.rs | 18 ++++----- src/crypto_impl/openssl.rs | 76 ++++++++++++++++++------------------ src/crypto_impl/p384_impl.rs | 8 ++-- src/crypto_impl/sha512.rs | 10 ++--- 7 files changed, 83 insertions(+), 83 deletions(-) diff --git a/examples/basic_test.rs b/examples/basic_test.rs index 58a8ed5..1c5534c 100644 --- a/examples/basic_test.rs +++ b/examples/basic_test.rs @@ -49,15 +49,15 @@ impl CryptoLayer for TestApplication { }; type Rng = OsRng; - type PrpEnc = Aes256OpenSSLEnc; - type PrpDec = Aes256OpenSSLDec; - type Aead = AesGcmOpenSSL; - type AeadPool = AesGcmOpenSSLPool; - type Hash = Sha512Crate; - type Hmac = HmacSha512Crate; - type PublicKey = P384CratePublicKey; - type KeyPair = P384CrateKeyPair; - type Kem = Kyber1024CratePrivateKey; + type PrpEnc = OpenSSLAes256Enc; + type PrpDec = OpenSSLAes256Dec; + type Aead = OpenSSLAesGcm; + type AeadPool = OpenSSLAesGcmPool; + type Hash = CrateSha512; + type Hmac = CrateHmacSha512; + type PublicKey = CrateP384PublicKey; + type KeyPair = CrateP384KeyPair; + type Kem = CrateKyber1024PrivateKey; type SessionData = u128; @@ -81,7 +81,7 @@ impl ApplicationLayer for &TestApplication { fn check_accept_session( &mut self, - remote_static_key: &P384CratePublicKey, + remote_static_key: &CrateP384PublicKey, identity: &[u8], ) -> AcceptAction { AcceptAction { @@ -98,7 +98,7 @@ impl ApplicationLayer for &TestApplication { fn restore_by_identity( &mut self, - remote_static_key: &P384CratePublicKey, + remote_static_key: &CrateP384PublicKey, session_data: &u128, ) -> Result, ()> { let ratchets = self.ratchets.lock().unwrap(); @@ -107,7 +107,7 @@ impl ApplicationLayer for &TestApplication { fn save_ratchet_state( &mut self, - remote_static_key: &P384CratePublicKey, + remote_static_key: &CrateP384PublicKey, session_data: &u128, update_data: RatchetUpdate<'_>, ) -> Result<(), ()> { @@ -144,8 +144,8 @@ fn alice_main( alice_out: mpsc::SyncSender>, alice_in: mpsc::Receiver>, recursive_out: mpsc::SyncSender>, - alice_keypair: P384CrateKeyPair, - bob_pubkey: P384CratePublicKey, + alice_keypair: CrateP384KeyPair, + bob_pubkey: CrateP384PublicKey, ) { let startup_time = std::time::Instant::now(); let context = zssp::Context::::new(alice_keypair, OsRng); @@ -251,7 +251,7 @@ fn bob_main( bob_out: mpsc::SyncSender>, bob_in: mpsc::Receiver>, recursive_out: mpsc::SyncSender>, - bob_keypair: P384CrateKeyPair, + bob_keypair: CrateP384KeyPair, ) { let startup_time = std::time::Instant::now(); let context = zssp::Context::::new(bob_keypair, OsRng); @@ -335,13 +335,13 @@ fn bob_main( fn core(time: u64, packet_success_rate: u32) { let run = &AtomicBool::new(true); - let alice_keypair = P384CrateKeyPair::generate(&mut OsRng); + let alice_keypair = CrateP384KeyPair::generate(&mut OsRng); let alice_app = TestApplication { time: Instant::now(), name: "alice", ratchets: Mutex::new(Ratchets::new()), }; - let bob_keypair = P384CrateKeyPair::generate(&mut OsRng); + let bob_keypair = CrateP384KeyPair::generate(&mut OsRng); let bob_pubkey = bob_keypair.public_key(); let bob_app = TestApplication { time: Instant::now(), diff --git a/examples/benchmark.rs b/examples/benchmark.rs index cc30885..30778ff 100644 --- a/examples/benchmark.rs +++ b/examples/benchmark.rs @@ -75,7 +75,7 @@ impl ApplicationLayer for &TestApplication { fn check_accept_session( &mut self, - remote_static_key: &P384CratePublicKey, + remote_static_key: &CrateP384PublicKey, identity: &[u8], ) -> AcceptAction { AcceptAction { @@ -91,7 +91,7 @@ impl ApplicationLayer for &TestApplication { fn restore_by_identity( &mut self, - remote_static_key: &P384CratePublicKey, + remote_static_key: &CrateP384PublicKey, session_data: &(), ) -> Result, ()> { Ok(None) @@ -99,7 +99,7 @@ impl ApplicationLayer for &TestApplication { fn save_ratchet_state( &mut self, - remote_static_key: &P384CratePublicKey, + remote_static_key: &CrateP384PublicKey, session_data: &(), update_data: RatchetUpdate<'_>, ) -> Result<(), ()> { @@ -117,8 +117,8 @@ fn alice_main( alice_app: &TestApplication, alice_out: mpsc::SyncSender, alice_in: mpsc::Receiver, - alice_keypair: P384CrateKeyPair, - bob_pubkey: P384CratePublicKey, + alice_keypair: CrateP384KeyPair, + bob_pubkey: CrateP384PublicKey, ) { let startup_time = std::time::Instant::now(); let context = zssp::Context::::new(alice_keypair, OsRng); @@ -211,7 +211,7 @@ fn bob_main( bob_app: &TestApplication, bob_out: mpsc::SyncSender, bob_in: mpsc::Receiver, - bob_keypair: P384CrateKeyPair, + bob_keypair: CrateP384KeyPair, ) { let startup_time = std::time::Instant::now(); let context = zssp::Context::::new(bob_keypair, OsRng); @@ -292,9 +292,9 @@ fn bob_main( fn core(time: u64) { let run = &AtomicBool::new(true); - let alice_keypair = P384CrateKeyPair::generate(&mut OsRng); + let alice_keypair = CrateP384KeyPair::generate(&mut OsRng); let alice_app = TestApplication { time: Instant::now() }; - let bob_keypair = P384CrateKeyPair::generate(&mut OsRng); + let bob_keypair = CrateP384KeyPair::generate(&mut OsRng); let bob_pubkey = bob_keypair.public_key(); let bob_app = TestApplication { time: Instant::now() }; diff --git a/src/crypto_impl/kyber1024.rs b/src/crypto_impl/kyber1024.rs index dd50978..ef553c2 100644 --- a/src/crypto_impl/kyber1024.rs +++ b/src/crypto_impl/kyber1024.rs @@ -5,8 +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 Kyber1024CratePrivateKey = Zeroizing<[u8; pqc_kyber::KYBER_SECRETKEYBYTES]>; -impl Kyber1024PrivateKey for Kyber1024CratePrivateKey { +pub type CrateKyber1024PrivateKey = Zeroizing<[u8; pqc_kyber::KYBER_SECRETKEYBYTES]>; +impl Kyber1024PrivateKey for CrateKyber1024PrivateKey { fn generate(rng: &mut Rng) -> (Self, [u8; KYBER_PUBLIC_KEY_SIZE]) { // According to the source code this can only fail if the RNG fails. // Idk why rust allows RNG to fail. diff --git a/src/crypto_impl/mod.rs b/src/crypto_impl/mod.rs index e08234b..8aa0ceb 100644 --- a/src/crypto_impl/mod.rs +++ b/src/crypto_impl/mod.rs @@ -36,15 +36,15 @@ pub trait DefaultCrypto { #[cfg(feature = "default-crypto")] impl crate::application::CryptoLayer for C { type Rng = rand_core::OsRng; - type PrpEnc = Aes256OpenSSLEnc; - type PrpDec = Aes256OpenSSLDec; - type Aead = AesGcmOpenSSL; - type AeadPool = AesGcmOpenSSLPool; - type Hash = Sha512Crate; - type Hmac = HmacSha512Crate; - type PublicKey = P384CratePublicKey; - type KeyPair = P384CrateKeyPair; - type Kem = Kyber1024CratePrivateKey; + type PrpEnc = OpenSSLAes256Enc; + type PrpDec = OpenSSLAes256Dec; + type Aead = OpenSSLAesGcm; + type AeadPool = OpenSSLAesGcmPool; + type Hash = CrateSha512; + type Hmac = CrateHmacSha512; + type PublicKey = CrateP384PublicKey; + type KeyPair = CrateP384KeyPair; + type Kem = CrateKyber1024PrivateKey; type SessionData = C::SessionData; type IncomingPacketBuffer = C::IncomingPacketBuffer; diff --git a/src/crypto_impl/openssl.rs b/src/crypto_impl/openssl.rs index 43125ae..8a73e92 100644 --- a/src/crypto_impl/openssl.rs +++ b/src/crypto_impl/openssl.rs @@ -7,18 +7,18 @@ use openssl_sys::*; use crate::crypto::*; -pub struct CipherCtx(NonNull); -impl Drop for CipherCtx { +pub struct OpenSSLCtx(NonNull); +impl Drop for OpenSSLCtx { fn drop(&mut self) { unsafe { EVP_CIPHER_CTX_free(self.0.as_ptr()); } } } -impl CipherCtx { +impl OpenSSLCtx { /// Creates a new context. pub fn new() -> Option { - unsafe { Some(CipherCtx(NonNull::new(EVP_CIPHER_CTX_new())?)) } + unsafe { Some(OpenSSLCtx(NonNull::new(EVP_CIPHER_CTX_new())?)) } } pub unsafe fn cipher_init( @@ -88,13 +88,13 @@ impl CipherCtx { } } -pub struct Aes256OpenSSLEnc(Mutex); -unsafe impl Send for Aes256OpenSSLEnc {} -unsafe impl Sync for Aes256OpenSSLEnc {} +pub struct OpenSSLAes256Enc(Mutex); +unsafe impl Send for OpenSSLAes256Enc {} +unsafe impl Sync for OpenSSLAes256Enc {} -impl Aes256Enc for Aes256OpenSSLEnc { +impl Aes256Enc for OpenSSLAes256Enc { fn new(key: &[u8; AES_256_KEY_SIZE]) -> Self { - let ctx = CipherCtx::new().unwrap(); + let ctx = OpenSSLCtx::new().unwrap(); unsafe { let t = openssl_sys::EVP_aes_256_ecb(); assert!(ctx.cipher_init::(t, key.as_ptr(), ptr::null())); @@ -118,13 +118,13 @@ impl Aes256Enc for Aes256OpenSSLEnc { unsafe { assert!(ctx.update::(block, ptr)) } } } -pub struct Aes256OpenSSLDec(Mutex); -unsafe impl Send for Aes256OpenSSLDec {} -unsafe impl Sync for Aes256OpenSSLDec {} +pub struct OpenSSLAes256Dec(Mutex); +unsafe impl Send for OpenSSLAes256Dec {} +unsafe impl Sync for OpenSSLAes256Dec {} -impl Aes256Dec for Aes256OpenSSLDec { +impl Aes256Dec for OpenSSLAes256Dec { fn new(key: &[u8; AES_256_KEY_SIZE]) -> Self { - let ctx = CipherCtx::new().unwrap(); + let ctx = OpenSSLCtx::new().unwrap(); unsafe { let t = openssl_sys::EVP_aes_256_ecb(); assert!(ctx.cipher_init::(t, key.as_ptr(), ptr::null())); @@ -149,8 +149,8 @@ impl Aes256Dec for Aes256OpenSSLDec { } } -pub struct AesGcmOpenSSLEnc<'a>(MutexGuard<'a, CipherCtx>); -impl<'a> AesGcmEncContext for AesGcmOpenSSLEnc<'a> { +pub struct OpenSSLAesGcmEnc<'a>(MutexGuard<'a, OpenSSLCtx>); +impl<'a> AesGcmEncContext for OpenSSLAesGcmEnc<'a> { fn encrypt(&mut self, input: &[u8], output: &mut [u8]) { unsafe { assert!(self.0.update::(input, output.as_mut_ptr())) }; } @@ -165,8 +165,8 @@ impl<'a> AesGcmEncContext for AesGcmOpenSSLEnc<'a> { } } -pub struct AesGcmOpenSSLDec<'a>(MutexGuard<'a, CipherCtx>); -impl<'a> AesGcmDecContext for AesGcmOpenSSLDec<'a> { +pub struct OpenSSLAesGcmDec<'a>(MutexGuard<'a, OpenSSLCtx>); +impl<'a> AesGcmDecContext for OpenSSLAesGcmDec<'a> { fn decrypt_in_place(&mut self, data: &mut [u8]) { let p = data.as_mut_ptr(); unsafe { assert!(self.0.update::(data, p)) }; @@ -177,30 +177,30 @@ impl<'a> AesGcmDecContext for AesGcmOpenSSLDec<'a> { } } -pub struct AesGcmOpenSSLPool { - enc: [Mutex; 8], - dec: [Mutex; 8], +pub struct OpenSSLAesGcmPool { + enc: [Mutex; 8], + dec: [Mutex; 8], } -unsafe impl Send for AesGcmOpenSSLPool {} -unsafe impl Sync for AesGcmOpenSSLPool {} +unsafe impl Send for OpenSSLAesGcmPool {} +unsafe impl Sync for OpenSSLAesGcmPool {} -impl HighThroughputAesGcmPool for AesGcmOpenSSLPool { - type EncContext<'a> = AesGcmOpenSSLEnc<'a>; +impl HighThroughputAesGcmPool for OpenSSLAesGcmPool { + type EncContext<'a> = OpenSSLAesGcmEnc<'a>; - type DecContext<'a> = AesGcmOpenSSLDec<'a>; + type DecContext<'a> = OpenSSLAesGcmDec<'a>; fn new(encrypt_key: &[u8; AES_256_KEY_SIZE], decrypt_key: &[u8; AES_256_KEY_SIZE]) -> Self { unsafe { - AesGcmOpenSSLPool { + OpenSSLAesGcmPool { enc: std::array::from_fn(|_| { - let ctx = CipherCtx::new().unwrap(); + let ctx = OpenSSLCtx::new().unwrap(); let t = openssl_sys::EVP_aes_256_gcm(); assert!(ctx.cipher_init::(t, encrypt_key.as_ptr(), ptr::null())); openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); Mutex::new(ctx) }), dec: std::array::from_fn(|_| { - let ctx = CipherCtx::new().unwrap(); + let ctx = OpenSSLCtx::new().unwrap(); let t = openssl_sys::EVP_aes_256_gcm(); assert!(ctx.cipher_init::(t, decrypt_key.as_ptr(), ptr::null())); openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); @@ -210,27 +210,27 @@ impl HighThroughputAesGcmPool for AesGcmOpenSSLPool { } } - fn start_enc<'a>(&'a self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> AesGcmOpenSSLEnc { + fn start_enc<'a>(&'a self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> OpenSSLAesGcmEnc { let i = u64::from_be_bytes(nonce[4..].try_into().unwrap()); let g = self.enc[(i as usize) % self.enc.len()].lock().unwrap(); unsafe { assert!(g.cipher_init::(ptr::null(), ptr::null(), nonce.as_ptr())); } - AesGcmOpenSSLEnc(g) + OpenSSLAesGcmEnc(g) } - fn start_dec<'a>(&'a self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> AesGcmOpenSSLDec { + fn start_dec<'a>(&'a self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> OpenSSLAesGcmDec { let i = u64::from_be_bytes(nonce[4..].try_into().unwrap()); let g = self.dec[(i as usize) % self.enc.len()].lock().unwrap(); unsafe { assert!(g.cipher_init::(ptr::null(), ptr::null(), nonce.as_ptr())); } - AesGcmOpenSSLDec(g) + OpenSSLAesGcmDec(g) } } -pub struct AesGcmOpenSSL; -impl LowThroughputAesGcm for AesGcmOpenSSL { +pub struct OpenSSLAesGcm; +impl LowThroughputAesGcm for OpenSSLAesGcm { fn encrypt_in_place( key: &[u8; AES_256_KEY_SIZE], nonce: &[u8; AES_GCM_NONCE_SIZE], @@ -238,7 +238,7 @@ impl LowThroughputAesGcm for AesGcmOpenSSL { data: &mut [u8], ) -> [u8; AES_GCM_TAG_SIZE] { let mut output = [0u8; AES_GCM_TAG_SIZE]; - let ctx = CipherCtx::new().unwrap(); + let ctx = OpenSSLCtx::new().unwrap(); unsafe { let t = openssl_sys::EVP_aes_256_gcm(); assert!(ctx.cipher_init::(t, key.as_ptr(), nonce.as_ptr())); @@ -261,7 +261,7 @@ impl LowThroughputAesGcm for AesGcmOpenSSL { data: &mut [u8], tag: &[u8; AES_GCM_TAG_SIZE], ) -> bool { - let ctx = CipherCtx::new().unwrap(); + let ctx = OpenSSLCtx::new().unwrap(); unsafe { let t = openssl_sys::EVP_aes_256_gcm(); assert!(ctx.cipher_init::(t, key.as_ptr(), nonce.as_ptr())); @@ -282,7 +282,7 @@ mod test { #[test] fn aes_128_ecb() { let key = [1u8; 16]; - let ctx = CipherCtx::new().unwrap(); + let ctx = OpenSSLCtx::new().unwrap(); unsafe { assert!(ctx.cipher_init::(openssl_sys::EVP_aes_128_ecb(), key.as_ptr(), ptr::null())); openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); diff --git a/src/crypto_impl/p384_impl.rs b/src/crypto_impl/p384_impl.rs index 77c0938..17bfb1b 100644 --- a/src/crypto_impl/p384_impl.rs +++ b/src/crypto_impl/p384_impl.rs @@ -3,8 +3,8 @@ use rand_core::{CryptoRng, RngCore}; use crate::crypto::*; -pub type P384CratePublicKey = PublicKey; -impl P384PublicKey for P384CratePublicKey { +pub type CrateP384PublicKey = PublicKey; +impl P384PublicKey for CrateP384PublicKey { fn from_bytes(raw_key: &[u8; P384_PUBLIC_KEY_SIZE]) -> Option { PublicKey::from_sec1_bytes(raw_key).ok() } @@ -15,8 +15,8 @@ impl P384PublicKey for P384CratePublicKey { } } -pub type P384CrateKeyPair = EphemeralSecret; -impl P384KeyPair for P384CrateKeyPair { +pub type CrateP384KeyPair = EphemeralSecret; +impl P384KeyPair for CrateP384KeyPair { type PublicKey = PublicKey; fn generate(rng: &mut Rng) -> Self { diff --git a/src/crypto_impl/sha512.rs b/src/crypto_impl/sha512.rs index ba932f8..1a1bd4d 100644 --- a/src/crypto_impl/sha512.rs +++ b/src/crypto_impl/sha512.rs @@ -3,8 +3,8 @@ use sha2::{Digest, Sha512}; use crate::crypto::*; -pub type Sha512Crate = Sha512; -impl Sha512Hash for Sha512Crate { +pub type CrateSha512 = Sha512; +impl Sha512Hash for CrateSha512 { fn new() -> Self { Digest::new() } @@ -20,10 +20,10 @@ impl Sha512Hash for Sha512Crate { } } -pub struct HmacSha512Crate; -impl Sha512Hmac for HmacSha512Crate { +pub struct CrateHmacSha512; +impl Sha512Hmac for CrateHmacSha512 { fn new() -> Self { - HmacSha512Crate + CrateHmacSha512 } fn hash(&mut self, key: &[u8], full_input: &[u8], output: &mut [u8; SHA512_HASH_SIZE]) { From 81d12879a4ec395e79aaf3be658d7a7b4e53e18f Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 16 Aug 2023 12:38:17 -0400 Subject: [PATCH 50/50] cargo fmt --- examples/benchmark.rs | 5 ++--- src/proto.rs | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/examples/benchmark.rs b/examples/benchmark.rs index 30778ff..c570df9 100644 --- a/examples/benchmark.rs +++ b/examples/benchmark.rs @@ -8,13 +8,12 @@ use rand_core::OsRng; use rand_core::RngCore; use zssp::application::{ - AcceptAction, ApplicationLayer, IncomingSessionAction, RatchetState, RatchetStates, RatchetUpdate, - RATCHET_SIZE, + AcceptAction, ApplicationLayer, IncomingSessionAction, RatchetState, RatchetStates, RatchetUpdate, RATCHET_SIZE, }; use zssp::crypto::P384KeyPair; use zssp::crypto_impl::*; -use zssp::Session; use zssp::result::ReceiveError; +use zssp::Session; const TEST_MTU: usize = 1500; diff --git a/src/proto.rs b/src/proto.rs index e5a9249..da39de7 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -88,6 +88,7 @@ pub(crate) const LABEL_HEADER_KEY: &[u8; 4] = b"ASKH"; pub(crate) const LABEL_KEX_KEY: &[u8; 4] = b"ASKK"; pub(crate) const EXPIRE_AFTER_USES: u64 = 1 << 32 - 1; +pub(crate) const THREAD_SAFE_COUNTER_HARD_EXPIRE: u64 = u64::MAX - 1 << 16; /// 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. @@ -101,7 +102,6 @@ pub(crate) const COUNTER_WINDOW_MAX_SKIP_AHEAD: u64 = 1 << 24; /// 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; -pub(crate) const THREAD_SAFE_COUNTER_HARD_EXPIRE: u64 = u64::MAX - 1 << 16; /* Packet constants */