Add automatic retransmission in the earliest stages of session init.

This commit is contained in:
Adam Ierymenko 2023-02-27 17:52:49 -05:00
commit 5143aa6a4e
4 changed files with 332 additions and 205 deletions

View file

@ -49,6 +49,12 @@ pub trait ApplicationLayer: Sized {
/// over very long distances. /// over very long distances.
const INCOMING_SESSION_NEGOTIATION_TIMEOUT_MS: i64 = 2000; const INCOMING_SESSION_NEGOTIATION_TIMEOUT_MS: i64 = 2000;
/// Retry interval for outgoing connection initiation or rekey attempts.
///
/// Retry attepmpts 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.
const RETRY_INTERVAL: i64 = 500;
/// Type for arbitrary opaque object for use by the application that is attached to each session. /// Type for arbitrary opaque object for use by the application that is attached to each session.
type Data; type Data;

View file

@ -15,6 +15,6 @@ mod zssp;
pub use crate::applicationlayer::ApplicationLayer; pub use crate::applicationlayer::ApplicationLayer;
pub use crate::error::Error; pub use crate::error::Error;
pub use crate::proto::{MAX_METADATA_SIZE, MIN_PACKET_SIZE, MIN_TRANSPORT_MTU}; pub use crate::proto::{MAX_INIT_PAYLOAD_SIZE, MIN_PACKET_SIZE, MIN_TRANSPORT_MTU};
pub use crate::sessionid::SessionId; pub use crate::sessionid::SessionId;
pub use crate::zssp::{Context, ReceiveResult, Session}; pub use crate::zssp::{Context, ReceiveResult, Session};

View file

@ -22,8 +22,8 @@ pub const MIN_PACKET_SIZE: usize = HEADER_SIZE + AES_GCM_TAG_SIZE;
/// Minimum physical MTU for ZSSP to function. /// Minimum physical MTU for ZSSP to function.
pub const MIN_TRANSPORT_MTU: usize = 128; pub const MIN_TRANSPORT_MTU: usize = 128;
/// Maximum size of init meta-data objects. /// Maximum combined size of static public blob and metadata.
pub const MAX_METADATA_SIZE: usize = 256; pub const MAX_INIT_PAYLOAD_SIZE: usize = MAX_NOISE_HANDSHAKE_SIZE - ALICE_NOISE_XK_ACK_MIN_SIZE;
pub(crate) const SESSION_PROTOCOL_VERSION: u8 = 0x00; pub(crate) const SESSION_PROTOCOL_VERSION: u8 = 0x00;

View file

@ -11,11 +11,11 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::num::NonZeroU64; use std::num::NonZeroU64;
use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::atomic::{AtomicI64, AtomicU64, Ordering};
use std::sync::{Arc, Mutex, RwLock, Weak}; use std::sync::{Arc, Mutex, RwLock, Weak};
use zerotier_crypto::aes::{Aes, AesCtr, AesGcm}; use zerotier_crypto::aes::{Aes, AesCtr, AesGcm};
use zerotier_crypto::hash::{hmac_sha512, HMACSHA384, HMAC_SHA384_SIZE, SHA384}; use zerotier_crypto::hash::{hmac_sha512, HMACSHA384, HMAC_SHA384_SIZE, SHA384, SHA384_HASH_SIZE};
use zerotier_crypto::p384::{P384KeyPair, P384PublicKey, P384_ECDH_SHARED_SECRET_SIZE, P384_PUBLIC_KEY_SIZE}; use zerotier_crypto::p384::{P384KeyPair, P384PublicKey, P384_ECDH_SHARED_SECRET_SIZE, P384_PUBLIC_KEY_SIZE};
use zerotier_crypto::secret::Secret; use zerotier_crypto::secret::Secret;
use zerotier_crypto::{random, secure_eq}; use zerotier_crypto::{random, secure_eq};
@ -25,7 +25,7 @@ use zerotier_utils::gatherarray::GatherArray;
use zerotier_utils::memory; use zerotier_utils::memory;
use zerotier_utils::ringbuffermap::RingBufferMap; use zerotier_utils::ringbuffermap::RingBufferMap;
use pqc_kyber::{KYBER_SECRETKEYBYTES, KYBER_SSBYTES}; use pqc_kyber::{KYBER_CIPHERTEXTBYTES, KYBER_SECRETKEYBYTES, KYBER_SSBYTES};
use crate::applicationlayer::ApplicationLayer; use crate::applicationlayer::ApplicationLayer;
use crate::error::Error; use crate::error::Error;
@ -43,6 +43,15 @@ pub struct Context<Application: ApplicationLayer> {
sessions: RwLock<SessionMaps<Application>>, sessions: RwLock<SessionMaps<Application>>,
} }
/// Lookup maps for sessions within a session context.
struct SessionMaps<Application: ApplicationLayer> {
// Active sessions, automatically closed if the application no longer holds their Arc<>.
active: HashMap<SessionId, Weak<Session<Application>>>,
// Incomplete sessions in the middle of three-phase Noise_XK negotiation, expired after timeout.
incomplete: HashMap<SessionId, Arc<IncompleteIncomingSession>>,
}
/// Result generated by the context packet receive function, with possible payloads. /// Result generated by the context packet receive function, with possible payloads.
pub enum ReceiveResult<'b, Application: ApplicationLayer> { pub enum ReceiveResult<'b, Application: ApplicationLayer> {
/// Packet was valid, but no action needs to be taken. /// Packet was valid, but no action needs to be taken.
@ -76,45 +85,45 @@ pub struct Session<Application: ApplicationLayer> {
defrag: Mutex<RingBufferMap<u64, GatherArray<Application::IncomingPacketBuffer, MAX_FRAGMENTS>, 16, 16>>, defrag: Mutex<RingBufferMap<u64, GatherArray<Application::IncomingPacketBuffer, MAX_FRAGMENTS>, 16, 16>>,
} }
struct SessionMaps<Application: ApplicationLayer> { /// Most of the mutable parts of a session state.
// Active sessions, automatically closed if the application no longer holds their Arc<>. struct State {
active: HashMap<SessionId, Weak<Session<Application>>>, remote_session_id: Option<SessionId>,
keys: [Option<SessionKey>; 2],
// Incomplete sessions in the middle of three-phase Noise_XK negotiation, expired after timeout. current_key: usize,
incomplete: HashMap<SessionId, Arc<NoiseXKIncoming>>, current_offer: Offer,
} }
struct NoiseXKIncoming { /// State related to an incoming session not yet fully established.
struct IncompleteIncomingSession {
timestamp: i64, timestamp: i64,
request_hash: [u8; SHA384_HASH_SIZE],
alice_session_id: SessionId, alice_session_id: SessionId,
bob_session_id: SessionId, bob_session_id: SessionId,
noise_es_ee: Secret<BASE_KEY_SIZE>, noise_es_ee: Secret<BASE_KEY_SIZE>,
bob_hk_ciphertext: [u8; KYBER_CIPHERTEXTBYTES],
hk: Secret<KYBER_SSBYTES>, hk: Secret<KYBER_SSBYTES>,
header_protection_key: Secret<AES_HEADER_PROTECTION_KEY_SIZE>, header_protection_key: Secret<AES_HEADER_PROTECTION_KEY_SIZE>,
bob_noise_e_secret: P384KeyPair, bob_noise_e_secret: P384KeyPair,
} }
struct NoiseXKOutgoing { /// State related to an outgoing session attempt.
timestamp: i64, struct OutgoingSessionInit {
last_retry_time: AtomicI64,
alice_noise_e_secret: P384KeyPair, alice_noise_e_secret: P384KeyPair,
noise_es: Secret<P384_ECDH_SHARED_SECRET_SIZE>, noise_es: Secret<P384_ECDH_SHARED_SECRET_SIZE>,
alice_hk_secret: Secret<KYBER_SECRETKEYBYTES>, alice_hk_secret: Secret<KYBER_SECRETKEYBYTES>,
metadata: Option<ArrayVec<u8, MAX_METADATA_SIZE>>, metadata: Option<ArrayVec<u8, MAX_INIT_PAYLOAD_SIZE>>,
init_packet: [u8; AliceNoiseXKInit::SIZE],
} }
enum EphemeralOffer { /// Latest outgoing offer, either an outgoing attempt or a rekey attempt.
enum Offer {
None, None,
NoiseXKInit(Box<NoiseXKOutgoing>), NoiseXKInit(Box<OutgoingSessionInit>),
RekeyInit(P384KeyPair), RekeyInit(P384KeyPair, [u8; AliceRekeyInit::SIZE], AtomicI64),
}
struct State {
remote_session_id: Option<SessionId>,
keys: [Option<SessionKey>; 2],
current_key: usize,
offer: EphemeralOffer,
} }
/// An ephemeral session key with expiration info.
struct SessionKey { struct SessionKey {
ratchet_key: Secret<BASE_KEY_SIZE>, // Key used in derivation of the next session key ratchet_key: Secret<BASE_KEY_SIZE>, // Key used in derivation of the next session key
receive_key: Secret<AES_KEY_SIZE>, // Receive side AES-GCM key receive_key: Secret<AES_KEY_SIZE>, // Receive side AES-GCM key
@ -126,12 +135,12 @@ struct SessionKey {
rekey_at_counter: u64, // Rekey at or after this counter rekey_at_counter: u64, // Rekey at or after this counter
expire_at_counter: u64, // Hard error when this counter value is reached or exceeded expire_at_counter: u64, // Hard error when this counter value is reached or exceeded
confirmed: bool, // We have confirmed that the other side has this key confirmed: bool, // We have confirmed that the other side has this key
role_is_bob: bool, // Was this side "Bob" in this exchange? bob: bool, // Was this side "Bob" in this exchange?
} }
impl<Application: ApplicationLayer> Context<Application> { impl<Application: ApplicationLayer> Context<Application> {
/// Create a new session context. /// Create a new session context.
pub fn new(_: &Application, max_incomplete_session_queue_size: usize) -> Self { pub fn new(max_incomplete_session_queue_size: usize) -> Self {
Self { Self {
max_incomplete_session_queue_size, max_incomplete_session_queue_size,
initial_offer_defrag: Mutex::new(RingBufferMap::new(random::next_u32_secure())), initial_offer_defrag: Mutex::new(RingBufferMap::new(random::next_u32_secure())),
@ -145,28 +154,67 @@ impl<Application: ApplicationLayer> Context<Application> {
/// Perform periodic background service and cleanup tasks. /// Perform periodic background service and cleanup tasks.
/// ///
/// This returns the number of milliseconds until it should be called again. /// This returns the number of milliseconds until it should be called again.
pub fn service<SendFunction: FnMut(&Arc<Session<Application>>, &mut [u8])>(&self, mut send: SendFunction, current_time: i64) -> i64 { ///
/// * `send` - Function to send packets to remote sessions
/// * `mtu` - Physical MTU
/// * `current_time` - Current monotonic time in milliseconds
pub fn service<SendFunction: FnMut(&Arc<Session<Application>>, &mut [u8])>(
&self,
mut send: SendFunction,
mtu: usize,
current_time: i64,
) -> i64 {
let mut dead_active = Vec::new(); let mut dead_active = Vec::new();
let mut dead_pending = Vec::new(); let mut dead_pending = Vec::new();
let retry_cutoff = current_time - Application::RETRY_INTERVAL;
let negotiation_timeout_cutoff = current_time - Application::INCOMING_SESSION_NEGOTIATION_TIMEOUT_MS;
{ {
let sessions = self.sessions.read().unwrap(); let sessions = self.sessions.read().unwrap();
for (id, s) in sessions.active.iter() { for (id, s) in sessions.active.iter() {
if let Some(session) = s.upgrade() { if let Some(session) = s.upgrade() {
let state = session.state.read().unwrap(); let state = session.state.read().unwrap();
match &state.current_offer {
Offer::None => {
if let Some(key) = state.keys[state.current_key].as_ref() { if let Some(key) = state.keys[state.current_key].as_ref() {
if key.role_is_bob if key.bob
&& (current_time >= key.rekey_at_time || session.send_counter.load(Ordering::Relaxed) >= key.rekey_at_counter) && (current_time >= key.rekey_at_time
|| session.send_counter.load(Ordering::Relaxed) >= key.rekey_at_counter)
{ {
session.send_rekey(|b| send(&session, b)); session.initiate_rekey(|b| send(&session, b), current_time);
}
}
}
Offer::NoiseXKInit(offer) => {
if offer.last_retry_time.load(Ordering::Relaxed) < retry_cutoff {
offer.last_retry_time.store(current_time, Ordering::Relaxed);
let _ = send_with_fragmentation(
|b| send(&session, b),
&mut (offer.init_packet.clone()),
mtu,
PACKET_TYPE_ALICE_NOISE_XK_INIT,
None,
0,
1,
None,
);
}
}
Offer::RekeyInit(_, rekey_packet, last_retry_time) => {
if last_retry_time.load(Ordering::Relaxed) < retry_cutoff {
last_retry_time.store(current_time, Ordering::Relaxed);
send(&session, &mut (rekey_packet.clone()));
}
} }
} }
} else { } else {
dead_active.push(*id); dead_active.push(*id);
} }
} }
for (id, p) in sessions.incomplete.iter() {
if (p.timestamp - current_time) > Application::INCOMING_SESSION_NEGOTIATION_TIMEOUT_MS { for (id, incomplete) in sessions.incomplete.iter() {
if incomplete.timestamp < negotiation_timeout_cutoff {
dead_pending.push(*id); dead_pending.push(*id);
} }
} }
@ -182,20 +230,22 @@ impl<Application: ApplicationLayer> Context<Application> {
} }
} }
Application::INCOMING_SESSION_NEGOTIATION_TIMEOUT_MS * 2 Application::INCOMING_SESSION_NEGOTIATION_TIMEOUT_MS.min(Application::RETRY_INTERVAL)
} }
/// Create a new session and send initial packet(s) to other side. /// Create a new session and send initial packet(s) to other side.
/// ///
/// This will return Error::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 /// * `app` - Application layer instance
/// * `send` - User-supplied packet sending function /// * `send` - User-supplied packet sending function
/// * `mtu` - Physical MTU for calls to send() /// * `mtu` - Physical MTU for calls to send()
/// * `local_session_id` - This side's session ID
/// * `remote_s_public_p384` - Remote side's static public NIST P-384 key /// * `remote_s_public_p384` - Remote side's static public NIST P-384 key
/// * `psk` - Pre-shared key (use all zero if none) /// * `psk` - Pre-shared key (use all zero if none)
/// * `metadata` - Optional metadata to be included in initial handshake /// * `metadata` - Optional metadata to be included in initial handshake
/// * `application_data` - Arbitrary opaque data to include with session object /// * `application_data` - Arbitrary opaque data to include with session object
#[allow(unused_variables)] /// * `current_time` - Current monotonic time in milliseconds
pub fn open<SendFunction: FnMut(&mut [u8])>( pub fn open<SendFunction: FnMut(&mut [u8])>(
&self, &self,
app: &Application, app: &Application,
@ -207,11 +257,9 @@ impl<Application: ApplicationLayer> Context<Application> {
application_data: Application::Data, application_data: Application::Data,
current_time: i64, current_time: i64,
) -> Result<Arc<Session<Application>>, Error> { ) -> Result<Arc<Session<Application>>, Error> {
if let Some(md) = metadata.as_ref() { if (metadata.map(|md| md.len()).unwrap_or(0) + app.get_local_s_public_blob().len()) > MAX_INIT_PAYLOAD_SIZE {
if md.len() > MAX_METADATA_SIZE {
return Err(Error::DataTooLarge); return Err(Error::DataTooLarge);
} }
}
let alice_noise_e_secret = P384KeyPair::generate(); let alice_noise_e_secret = P384KeyPair::generate();
let alice_noise_e = alice_noise_e_secret.public_key_bytes().clone(); let alice_noise_e = alice_noise_e_secret.public_key_bytes().clone();
@ -234,19 +282,20 @@ impl<Application: ApplicationLayer> Context<Application> {
id: local_session_id, id: local_session_id,
application_data, application_data,
psk, psk,
send_counter: AtomicU64::new(2), // 1 is the counter value for this INIT message send_counter: AtomicU64::new(3), // 1 and 2 are reserved for init and final ack
receive_window: std::array::from_fn(|_| AtomicU64::new(0)), receive_window: std::array::from_fn(|_| AtomicU64::new(0)),
header_protection_cipher: Aes::new(&header_protection_key), header_protection_cipher: Aes::new(&header_protection_key),
state: RwLock::new(State { state: RwLock::new(State {
remote_session_id: None, remote_session_id: None,
keys: [None, None], keys: [None, None],
current_key: 0, current_key: 0,
offer: EphemeralOffer::NoiseXKInit(Box::new(NoiseXKOutgoing { current_offer: Offer::NoiseXKInit(Box::new(OutgoingSessionInit {
timestamp: current_time, last_retry_time: AtomicI64::new(current_time),
alice_noise_e_secret, alice_noise_e_secret,
noise_es: noise_es.clone(), noise_es: noise_es.clone(),
alice_hk_secret: Secret(alice_hk_secret.secret), alice_hk_secret: Secret(alice_hk_secret.secret),
metadata: metadata.map(|md| ArrayVec::try_from(md).unwrap()), metadata: metadata.map(|md| ArrayVec::try_from(md).unwrap()),
init_packet: [0u8; AliceNoiseXKInit::SIZE],
})), })),
}), }),
defrag: Mutex::new(RingBufferMap::new(random::xorshift64_random() as u32)), defrag: Mutex::new(RingBufferMap::new(random::xorshift64_random() as u32)),
@ -257,8 +306,15 @@ impl<Application: ApplicationLayer> Context<Application> {
(local_session_id, session) (local_session_id, session)
}; };
let mut init_buffer = [0u8; AliceNoiseXKInit::SIZE]; {
let init: &mut AliceNoiseXKInit = byte_array_as_proto_buffer_mut(&mut init_buffer).unwrap(); let mut state = session.state.write().unwrap();
let init_packet = if let Offer::NoiseXKInit(offer) = &mut state.current_offer {
&mut offer.init_packet
} else {
panic!();
};
let init: &mut AliceNoiseXKInit = byte_array_as_proto_buffer_mut(init_packet).unwrap();
init.session_protocol_version = SESSION_PROTOCOL_VERSION; init.session_protocol_version = SESSION_PROTOCOL_VERSION;
init.alice_noise_e = alice_noise_e; init.alice_noise_e = alice_noise_e;
init.alice_session_id = *local_session_id.as_bytes(); init.alice_session_id = *local_session_id.as_bytes();
@ -267,16 +323,26 @@ impl<Application: ApplicationLayer> Context<Application> {
let mut ctr = AesCtr::new(kbkdf::<AES_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION>(noise_es.as_bytes()).as_bytes()); let mut ctr = AesCtr::new(kbkdf::<AES_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION>(noise_es.as_bytes()).as_bytes());
ctr.reset_set_iv(&alice_noise_e[P384_PUBLIC_KEY_SIZE - AES_CTR_NONCE_SIZE..]); ctr.reset_set_iv(&alice_noise_e[P384_PUBLIC_KEY_SIZE - AES_CTR_NONCE_SIZE..]);
ctr.crypt_in_place(&mut init_buffer[AliceNoiseXKInit::ENC_START..AliceNoiseXKInit::AUTH_START]); ctr.crypt_in_place(&mut init_packet[AliceNoiseXKInit::ENC_START..AliceNoiseXKInit::AUTH_START]);
let hmac = hmac_sha384_2( let hmac = hmac_sha384_2(
kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION>(noise_es.as_bytes()).as_bytes(), kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION>(noise_es.as_bytes()).as_bytes(),
&create_message_nonce(PACKET_TYPE_ALICE_NOISE_XK_INIT, 1), &create_message_nonce(PACKET_TYPE_ALICE_NOISE_XK_INIT, 1),
&init_buffer[HEADER_SIZE..AliceNoiseXKInit::AUTH_START], &init_packet[HEADER_SIZE..AliceNoiseXKInit::AUTH_START],
); );
init_buffer[AliceNoiseXKInit::AUTH_START..AliceNoiseXKInit::AUTH_START + HMAC_SHA384_SIZE].copy_from_slice(&hmac); init_packet[AliceNoiseXKInit::AUTH_START..AliceNoiseXKInit::AUTH_START + HMAC_SHA384_SIZE].copy_from_slice(&hmac);
send_with_fragmentation(&mut send, &mut init_buffer, mtu, PACKET_TYPE_ALICE_NOISE_XK_INIT, None, 0, 1, None)?; send_with_fragmentation(
&mut send,
&mut (init_packet.clone()),
mtu,
PACKET_TYPE_ALICE_NOISE_XK_INIT,
None,
0,
1,
None,
)?;
}
return Ok(session); return Ok(session);
} }
@ -308,7 +374,6 @@ impl<Application: ApplicationLayer> Context<Application> {
/// * `incoming_packet_buf` - Buffer containing incoming wire packet (receive() takes ownership) /// * `incoming_packet_buf` - Buffer containing incoming wire packet (receive() takes ownership)
/// * `mtu` - Physical wire MTU for sending packets /// * `mtu` - Physical wire MTU for sending packets
/// * `current_time` - Current monotonic time in milliseconds /// * `current_time` - Current monotonic time in milliseconds
#[inline]
pub fn receive< pub fn receive<
'b, 'b,
SendFunction: FnMut(Option<&Arc<Session<Application>>>, &mut [u8]), SendFunction: FnMut(Option<&Arc<Session<Application>>>, &mut [u8]),
@ -332,7 +397,16 @@ impl<Application: ApplicationLayer> Context<Application> {
let mut incomplete = None; let mut incomplete = None;
if let Some(local_session_id) = SessionId::new_from_u64_le(memory::load_raw(incoming_packet)) { if let Some(local_session_id) = SessionId::new_from_u64_le(memory::load_raw(incoming_packet)) {
if let Some(session) = self.look_up_session(local_session_id) { if let Some(session) = self
.sessions
.read()
.unwrap()
.active
.get(&local_session_id)
.and_then(|s| s.upgrade())
{
debug_assert!(self.sessions.read().unwrap().incomplete.contains_key(&local_session_id));
session session
.header_protection_cipher .header_protection_cipher
.decrypt_block_in_place(&mut incoming_packet[HEADER_PROTECT_ENCRYPT_START..HEADER_PROTECT_ENCRYPT_END]); .decrypt_block_in_place(&mut incoming_packet[HEADER_PROTECT_ENCRYPT_START..HEADER_PROTECT_ENCRYPT_END]);
@ -455,18 +529,18 @@ impl<Application: ApplicationLayer> Context<Application> {
check_allow_incoming_session: &mut CheckAllowIncomingSession, check_allow_incoming_session: &mut CheckAllowIncomingSession,
check_accept_session: &mut CheckAcceptSession, check_accept_session: &mut CheckAcceptSession,
data_buf: &'b mut [u8], data_buf: &'b mut [u8],
counter: u64, incoming_counter: u64,
fragments: &[Application::IncomingPacketBuffer], fragments: &[Application::IncomingPacketBuffer],
packet_type: u8, packet_type: u8,
session: Option<Arc<Session<Application>>>, session: Option<Arc<Session<Application>>>,
incomplete: Option<Arc<NoiseXKIncoming>>, incomplete: Option<Arc<IncompleteIncomingSession>>,
key_index: usize, key_index: usize,
mtu: usize, mtu: usize,
current_time: i64, current_time: i64,
) -> Result<ReceiveResult<'b, Application>, Error> { ) -> Result<ReceiveResult<'b, Application>, Error> {
debug_assert!(fragments.len() >= 1); debug_assert!(fragments.len() >= 1);
let incoming_message_nonce = create_message_nonce(packet_type, counter); let incoming_message_nonce = create_message_nonce(packet_type, incoming_counter);
if packet_type == PACKET_TYPE_DATA { if packet_type == PACKET_TYPE_DATA {
if let Some(session) = session { if let Some(session) = session {
let state = session.state.read().unwrap(); let state = session.state.read().unwrap();
@ -510,7 +584,7 @@ impl<Application: ApplicationLayer> Context<Application> {
key.return_receive_cipher(c); key.return_receive_cipher(c);
if aead_authentication_ok { if aead_authentication_ok {
if session.update_receive_window(counter) { if session.update_receive_window(incoming_counter) {
// If the packet authenticated, this confirms that the other side indeed // If the packet authenticated, this confirms that the other side indeed
// knows this session key. In that case mark the session key as confirmed // knows this session key. In that case mark the session key as confirmed
// and if the current active key is older switch it to point to this one. // and if the current active key is older switch it to point to this one.
@ -575,15 +649,39 @@ impl<Application: ApplicationLayer> Context<Application> {
* to the current exchange. * to the current exchange.
*/ */
if session.is_some() || incomplete.is_some() || counter != 1 { if incoming_counter != 1 || session.is_some() {
return Err(Error::OutOfSequence); return Err(Error::OutOfSequence);
} }
// Hash the init packet so we can check to see if it's just being retransmitted.
let request_hash = SHA384::hash(&pkt_assembled);
let (alice_session_id, bob_session_id, noise_es_ee, bob_hk_ciphertext, header_protection_key, bob_noise_e) =
if let Some(incomplete) = incomplete {
// If we already have an incoming incomplete session record and the hash matches, recall the
// previous state so we can send an identical reply in response to a retransmit.
if secure_eq(&request_hash, &incomplete.request_hash) {
(
incomplete.alice_session_id,
incomplete.bob_session_id,
incomplete.noise_es_ee.clone(),
incomplete.bob_hk_ciphertext,
incomplete.header_protection_key.clone(),
*incomplete.bob_noise_e_secret.public_key_bytes(),
)
} else {
return Err(Error::FailedAuthentication);
}
} else {
// Otherwise parse the packet, authenticate, generate keys, etc. and record state in an
// incomplete state object until this phase of the negotiation is done.
let pkt: &AliceNoiseXKInit = byte_array_as_proto_buffer(pkt_assembled)?; let pkt: &AliceNoiseXKInit = byte_array_as_proto_buffer(pkt_assembled)?;
let alice_noise_e = P384PublicKey::from_bytes(&pkt.alice_noise_e).ok_or(Error::FailedAuthentication)?; let alice_noise_e = P384PublicKey::from_bytes(&pkt.alice_noise_e).ok_or(Error::FailedAuthentication)?;
let noise_es = app.get_local_s_keypair().agree(&alice_noise_e).ok_or(Error::FailedAuthentication)?; let noise_es = app.get_local_s_keypair().agree(&alice_noise_e).ok_or(Error::FailedAuthentication)?;
// Authenticate packet and prove that Alice knows our static public key. // Authenticate packet and also prove that Alice knows our static public key.
if !secure_eq( if !secure_eq(
&pkt.hmac_es, &pkt.hmac_es,
&hmac_sha384_2( &hmac_sha384_2(
@ -601,7 +699,8 @@ impl<Application: ApplicationLayer> Context<Application> {
} }
// Decrypt encrypted part of payload. // Decrypt encrypted part of payload.
let mut ctr = AesCtr::new(kbkdf::<AES_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION>(noise_es.as_bytes()).as_bytes()); let mut ctr =
AesCtr::new(kbkdf::<AES_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION>(noise_es.as_bytes()).as_bytes());
ctr.reset_set_iv(&SHA384::hash(&pkt.alice_noise_e)[..AES_CTR_NONCE_SIZE]); ctr.reset_set_iv(&SHA384::hash(&pkt.alice_noise_e)[..AES_CTR_NONCE_SIZE]);
ctr.crypt_in_place(&mut pkt_assembled[AliceNoiseXKInit::ENC_START..AliceNoiseXKInit::AUTH_START]); ctr.crypt_in_place(&mut pkt_assembled[AliceNoiseXKInit::ENC_START..AliceNoiseXKInit::AUTH_START]);
@ -619,12 +718,11 @@ impl<Application: ApplicationLayer> Context<Application> {
.ok_or(Error::FailedAuthentication)? .ok_or(Error::FailedAuthentication)?
.as_bytes(), .as_bytes(),
)); ));
let (bob_hk_ciphertext, hk) = pqc_kyber::encapsulate(&pkt.alice_hk_public, &mut random::SecureRandom::default()) let (bob_hk_ciphertext, hk) =
pqc_kyber::encapsulate(&pkt.alice_hk_public, &mut random::SecureRandom::default())
.map_err(|_| Error::FailedAuthentication) .map_err(|_| Error::FailedAuthentication)
.map(|(ct, hk)| (ct, Secret(hk)))?; .map(|(ct, hk)| (ct, Secret(hk)))?;
// Pick a session ID for our side and save the intermediate ephemeral state for this exchange.
let bob_session_id = {
let mut sessions = self.sessions.write().unwrap(); let mut sessions = self.sessions.write().unwrap();
let mut bob_session_id; let mut bob_session_id;
@ -654,53 +752,63 @@ impl<Application: ApplicationLayer> Context<Application> {
let _ = sessions.incomplete.remove(replace_id.as_ref().unwrap()); let _ = sessions.incomplete.remove(replace_id.as_ref().unwrap());
} }
// Reserve session ID on this side and record incomplete session state.
sessions.incomplete.insert( sessions.incomplete.insert(
bob_session_id, bob_session_id,
Arc::new(NoiseXKIncoming { Arc::new(IncompleteIncomingSession {
timestamp: current_time, timestamp: current_time,
request_hash,
alice_session_id, alice_session_id,
bob_session_id, bob_session_id,
noise_es_ee: noise_es_ee.clone(), noise_es_ee: noise_es_ee.clone(),
bob_hk_ciphertext,
hk, hk,
bob_noise_e_secret, bob_noise_e_secret,
header_protection_key: Secret(pkt.header_protection_key), header_protection_key: Secret(pkt.header_protection_key),
}), }),
); );
bob_session_id (
alice_session_id,
bob_session_id,
noise_es_ee,
bob_hk_ciphertext,
Secret(pkt.header_protection_key),
bob_noise_e,
)
}; };
// Create Bob's ephemeral counter-offer reply. // Create Bob's ephemeral counter-offer reply.
let mut reply_buffer = [0u8; BobNoiseXKAck::SIZE]; let mut ack_packet = [0u8; BobNoiseXKAck::SIZE];
let reply: &mut BobNoiseXKAck = byte_array_as_proto_buffer_mut(&mut reply_buffer)?; let ack: &mut BobNoiseXKAck = byte_array_as_proto_buffer_mut(&mut ack_packet)?;
reply.session_protocol_version = SESSION_PROTOCOL_VERSION; ack.session_protocol_version = SESSION_PROTOCOL_VERSION;
reply.bob_noise_e = bob_noise_e; ack.bob_noise_e = bob_noise_e;
reply.bob_session_id = *bob_session_id.as_bytes(); ack.bob_session_id = *bob_session_id.as_bytes();
reply.bob_hk_ciphertext = bob_hk_ciphertext; ack.bob_hk_ciphertext = bob_hk_ciphertext;
// Encrypt main section of reply. Technically we could get away without this but why not? // Encrypt main section of reply. Technically we could get away without this but why not?
let mut ctr = let mut ctr =
AesCtr::new(kbkdf::<AES_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION>(noise_es_ee.as_bytes()).as_bytes()); AesCtr::new(kbkdf::<AES_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION>(noise_es_ee.as_bytes()).as_bytes());
ctr.reset_set_iv(&bob_noise_e[P384_PUBLIC_KEY_SIZE - AES_CTR_NONCE_SIZE..]); ctr.reset_set_iv(&bob_noise_e[P384_PUBLIC_KEY_SIZE - AES_CTR_NONCE_SIZE..]);
ctr.crypt_in_place(&mut reply_buffer[BobNoiseXKAck::ENC_START..BobNoiseXKAck::AUTH_START]); ctr.crypt_in_place(&mut ack_packet[BobNoiseXKAck::ENC_START..BobNoiseXKAck::AUTH_START]);
// Add HMAC-SHA384 to reply packet. // Add HMAC-SHA384 to reply packet.
let reply_hmac = hmac_sha384_2( let reply_hmac = hmac_sha384_2(
kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION>(noise_es_ee.as_bytes()).as_bytes(), kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION>(noise_es_ee.as_bytes()).as_bytes(),
&create_message_nonce(PACKET_TYPE_BOB_NOISE_XK_ACK, 1), &create_message_nonce(PACKET_TYPE_BOB_NOISE_XK_ACK, 1),
&reply_buffer[HEADER_SIZE..BobNoiseXKAck::AUTH_START], &ack_packet[HEADER_SIZE..BobNoiseXKAck::AUTH_START],
); );
reply_buffer[BobNoiseXKAck::AUTH_START..].copy_from_slice(&reply_hmac); ack_packet[BobNoiseXKAck::AUTH_START..].copy_from_slice(&reply_hmac);
send_with_fragmentation( send_with_fragmentation(
|b| send(None, b), |b| send(None, b),
&mut reply_buffer, &mut ack_packet,
mtu, mtu,
PACKET_TYPE_BOB_NOISE_XK_ACK, PACKET_TYPE_BOB_NOISE_XK_ACK,
Some(alice_session_id), Some(alice_session_id),
0, 0,
1, 1,
Some(&Aes::new(&pkt.header_protection_key)), Some(&Aes::new(header_protection_key.as_bytes())),
)?; )?;
return Ok(ReceiveResult::Ok); return Ok(ReceiveResult::Ok);
@ -715,13 +823,13 @@ impl<Application: ApplicationLayer> Context<Application> {
* the negotiation. * the negotiation.
*/ */
if counter != 1 || incomplete.is_some() { if incoming_counter != 1 || incomplete.is_some() {
return Err(Error::OutOfSequence); return Err(Error::OutOfSequence);
} }
if let Some(session) = session { if let Some(session) = session {
let state = session.state.read().unwrap(); let state = session.state.read().unwrap();
if let EphemeralOffer::NoiseXKInit(outgoing_offer) = &state.offer { if let Offer::NoiseXKInit(outgoing_offer) = &state.current_offer {
let pkt: &BobNoiseXKAck = byte_array_as_proto_buffer(pkt_assembled)?; let pkt: &BobNoiseXKAck = byte_array_as_proto_buffer(pkt_assembled)?;
if let Some(bob_session_id) = SessionId::new_from_bytes(&pkt.bob_session_id) { if let Some(bob_session_id) = SessionId::new_from_bytes(&pkt.bob_session_id) {
@ -776,12 +884,7 @@ impl<Application: ApplicationLayer> Context<Application> {
&hmac_sha512(session.psk.as_bytes(), hk.as_bytes()), &hmac_sha512(session.psk.as_bytes(), hk.as_bytes()),
)); ));
let noise_es_ee_se_hk_psk_hmac_key = let reply_message_nonce = create_message_nonce(PACKET_TYPE_ALICE_NOISE_XK_ACK, 2);
kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION>(noise_es_ee_se_hk_psk.as_bytes());
let reply_counter = session.get_next_outgoing_counter().ok_or(Error::MaxKeyLifetimeExceeded)?;
debug_assert_eq!(reply_counter.get(), 2);
let reply_message_nonce = create_message_nonce(PACKET_TYPE_ALICE_NOISE_XK_ACK, reply_counter.get());
// Create reply informing Bob of our static identity now that we've verified Bob and set // Create reply informing Bob of our static identity now that we've verified Bob and set
// up forward secrecy. Also return Bob's opaque note. // up forward secrecy. Also return Bob's opaque note.
@ -826,7 +929,8 @@ impl<Application: ApplicationLayer> Context<Application> {
// key exchange. Bob won't be able to do this until he decrypts and parses Alice's // key exchange. Bob won't be able to do this until he decrypts and parses Alice's
// identity, so the first HMAC is to let him authenticate that first. // identity, so the first HMAC is to let him authenticate that first.
let hmac_es_ee_se_hk_psk = hmac_sha384_2( let hmac_es_ee_se_hk_psk = hmac_sha384_2(
noise_es_ee_se_hk_psk_hmac_key.as_bytes(), kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION>(noise_es_ee_se_hk_psk.as_bytes())
.as_bytes(),
&reply_message_nonce, &reply_message_nonce,
&reply_buffer[HEADER_SIZE..reply_len], &reply_buffer[HEADER_SIZE..reply_len],
); );
@ -840,12 +944,12 @@ impl<Application: ApplicationLayer> Context<Application> {
let _ = state.keys[0].insert(SessionKey::new::<Application>( let _ = state.keys[0].insert(SessionKey::new::<Application>(
noise_es_ee_se_hk_psk, noise_es_ee_se_hk_psk,
current_time, current_time,
reply_counter.get(), 2,
true, true,
false, false,
)); ));
state.current_key = 0; state.current_key = 0;
state.offer = EphemeralOffer::None; state.current_offer = Offer::None;
} }
send_with_fragmentation( send_with_fragmentation(
@ -855,7 +959,7 @@ impl<Application: ApplicationLayer> Context<Application> {
PACKET_TYPE_ALICE_NOISE_XK_ACK, PACKET_TYPE_ALICE_NOISE_XK_ACK,
Some(bob_session_id), Some(bob_session_id),
0, 0,
reply_counter.get(), 2,
Some(&session.header_protection_cipher), Some(&session.header_protection_cipher),
)?; )?;
@ -882,7 +986,7 @@ impl<Application: ApplicationLayer> Context<Application> {
* that Alice must return. * that Alice must return.
*/ */
if session.is_some() || counter != 2 { if incoming_counter != 2 || session.is_some() {
return Err(Error::OutOfSequence); return Err(Error::OutOfSequence);
} }
if pkt_assembled.len() < ALICE_NOISE_XK_ACK_MIN_SIZE { if pkt_assembled.len() < ALICE_NOISE_XK_ACK_MIN_SIZE {
@ -999,11 +1103,12 @@ impl<Application: ApplicationLayer> Context<Application> {
None, None,
], ],
current_key: 0, current_key: 0,
offer: EphemeralOffer::None, current_offer: Offer::None,
}), }),
defrag: Mutex::new(RingBufferMap::new(random::xorshift64_random() as u32)), defrag: Mutex::new(RingBufferMap::new(random::xorshift64_random() as u32)),
}); });
// Promote this from an incomplete session to an established session.
{ {
let mut sessions = self.sessions.write().unwrap(); let mut sessions = self.sessions.write().unwrap();
sessions.incomplete.remove(&incomplete.bob_session_id); sessions.incomplete.remove(&incomplete.bob_session_id);
@ -1020,11 +1125,15 @@ impl<Application: ApplicationLayer> Context<Application> {
if pkt_assembled.len() != AliceRekeyInit::SIZE { if pkt_assembled.len() != AliceRekeyInit::SIZE {
return Err(Error::InvalidPacket); return Err(Error::InvalidPacket);
} }
if incomplete.is_some() {
return Err(Error::OutOfSequence);
}
if let Some(session) = session { if let Some(session) = session {
let state = session.state.read().unwrap(); let state = session.state.read().unwrap();
if let Some(key) = state.keys[key_index].as_ref() { if let Some(key) = state.keys[key_index].as_ref() {
// Only the current "Alice" accepts rekeys initiated by the current "Bob." // Only the current "Alice" accepts rekeys initiated by the current "Bob."
if !key.role_is_bob { if !key.bob {
let mut c = key.get_receive_cipher(); let mut c = key.get_receive_cipher();
c.reset_init_gcm(&incoming_message_nonce); c.reset_init_gcm(&incoming_message_nonce);
c.crypt_in_place(&mut pkt_assembled[AliceRekeyInit::ENC_START..AliceRekeyInit::AUTH_START]); c.crypt_in_place(&mut pkt_assembled[AliceRekeyInit::ENC_START..AliceRekeyInit::AUTH_START]);
@ -1078,12 +1187,16 @@ impl<Application: ApplicationLayer> Context<Application> {
if pkt_assembled.len() != BobRekeyAck::SIZE { if pkt_assembled.len() != BobRekeyAck::SIZE {
return Err(Error::InvalidPacket); return Err(Error::InvalidPacket);
} }
if incomplete.is_some() {
return Err(Error::OutOfSequence);
}
if let Some(session) = session { if let Some(session) = session {
let state = session.state.read().unwrap(); let state = session.state.read().unwrap();
if let EphemeralOffer::RekeyInit(alice_e_secret) = &state.offer { if let Offer::RekeyInit(alice_e_secret, _, _) = &state.current_offer {
if let Some(key) = state.keys[key_index].as_ref() { if let Some(key) = state.keys[key_index].as_ref() {
// Only the current "Bob" initiates rekeys and expects this ACK. // Only the current "Bob" initiates rekeys and expects this ACK.
if key.role_is_bob { if key.bob {
let mut c = key.get_receive_cipher(); let mut c = key.get_receive_cipher();
c.reset_init_gcm(&incoming_message_nonce); c.reset_init_gcm(&incoming_message_nonce);
c.crypt_in_place(&mut pkt_assembled[BobRekeyAck::ENC_START..BobRekeyAck::AUTH_START]); c.crypt_in_place(&mut pkt_assembled[BobRekeyAck::ENC_START..BobRekeyAck::AUTH_START]);
@ -1102,11 +1215,11 @@ impl<Application: ApplicationLayer> Context<Application> {
let _ = state.keys[key_index ^ 1].replace(SessionKey::new::<Application>( let _ = state.keys[key_index ^ 1].replace(SessionKey::new::<Application>(
next_session_key, next_session_key,
current_time, current_time,
counter, session.send_counter.load(Ordering::Acquire),
true, true,
false, false,
)); ));
state.offer = EphemeralOffer::None; state.current_offer = Offer::None;
return Ok(ReceiveResult::Ok); return Ok(ReceiveResult::Ok);
} }
@ -1128,11 +1241,6 @@ impl<Application: ApplicationLayer> Context<Application> {
} }
} }
} }
/// Look up a session by local session ID.
fn look_up_session(&self, id: SessionId) -> Option<Arc<Session<Application>>> {
self.sessions.read().unwrap().active.get(&id).and_then(|s| s.upgrade())
}
} }
impl<Application: ApplicationLayer> Session<Application> { impl<Application: ApplicationLayer> Session<Application> {
@ -1206,7 +1314,7 @@ impl<Application: ApplicationLayer> Session<Application> {
/// This is called from the session context's service() method when it's time to rekey. /// This is called from the session context's service() method when it's time to rekey.
/// It should only be called when the current key was established in the 'bob' role. This /// It should only be called when the current key was established in the 'bob' role. This
/// is checked when rekey time is checked. /// is checked when rekey time is checked.
fn send_rekey<SendFunction: FnMut(&mut [u8])>(&self, mut send: SendFunction) { fn initiate_rekey<SendFunction: FnMut(&mut [u8])>(&self, mut send: SendFunction, current_time: i64) {
let rekey_e = P384KeyPair::generate(); let rekey_e = P384KeyPair::generate();
let mut rekey_buf = [0u8; AliceRekeyInit::SIZE]; let mut rekey_buf = [0u8; AliceRekeyInit::SIZE];
@ -1214,6 +1322,7 @@ impl<Application: ApplicationLayer> Session<Application> {
pkt.alice_e = *rekey_e.public_key_bytes(); pkt.alice_e = *rekey_e.public_key_bytes();
let state = self.state.read().unwrap(); let state = self.state.read().unwrap();
if let Some(remote_session_id) = state.remote_session_id {
if let Some(key) = state.keys[state.current_key].as_ref() { if let Some(key) = state.keys[state.current_key].as_ref() {
if let Some(counter) = self.get_next_outgoing_counter() { if let Some(counter) = self.get_next_outgoing_counter() {
if let Ok(mut gcm) = key.get_send_cipher(counter.get()) { if let Ok(mut gcm) = key.get_send_cipher(counter.get()) {
@ -1222,10 +1331,22 @@ impl<Application: ApplicationLayer> Session<Application> {
rekey_buf[AliceRekeyInit::AUTH_START..].copy_from_slice(&gcm.finish_encrypt()); rekey_buf[AliceRekeyInit::AUTH_START..].copy_from_slice(&gcm.finish_encrypt());
key.return_send_cipher(gcm); key.return_send_cipher(gcm);
debug_assert!(rekey_buf.len() <= MIN_TRANSPORT_MTU);
set_packet_header(
&mut rekey_buf,
1,
0,
PACKET_TYPE_ALICE_REKEY_INIT,
u64::from(remote_session_id),
state.current_key,
counter.get(),
);
send(&mut rekey_buf); send(&mut rekey_buf);
drop(state); drop(state);
self.state.write().unwrap().offer = EphemeralOffer::RekeyInit(rekey_e); self.state.write().unwrap().current_offer = Offer::RekeyInit(rekey_e, rekey_buf, AtomicI64::new(current_time));
}
} }
} }
} }
@ -1240,16 +1361,16 @@ impl<Application: ApplicationLayer> Session<Application> {
/// Check the receive window without mutating state. /// Check the receive window without mutating state.
#[inline(always)] #[inline(always)]
fn check_receive_window(&self, counter: u64) -> bool { fn check_receive_window(&self, counter: u64) -> bool {
let c = self.receive_window[(counter as usize) % COUNTER_WINDOW_MAX_OOO].load(Ordering::Acquire); let prev_counter = self.receive_window[(counter as usize) % COUNTER_WINDOW_MAX_OOO].load(Ordering::Acquire);
c < counter && counter.wrapping_sub(c) < COUNTER_WINDOW_MAX_SKIP_AHEAD 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. /// Update the receive window, returning true if the packet is still valid.
/// This should only be called after the packet is authenticated. /// This should only be called after the packet is authenticated.
#[inline(always)] #[inline(always)]
fn update_receive_window(&self, counter: u64) -> bool { fn update_receive_window(&self, counter: u64) -> bool {
let c = self.receive_window[(counter as usize) % COUNTER_WINDOW_MAX_OOO].fetch_max(counter, Ordering::AcqRel); let prev_counter = self.receive_window[(counter as usize) % COUNTER_WINDOW_MAX_OOO].fetch_max(counter, Ordering::AcqRel);
c < counter && counter.wrapping_sub(c) < COUNTER_WINDOW_MAX_SKIP_AHEAD prev_counter < counter && counter.wrapping_sub(prev_counter) < COUNTER_WINDOW_MAX_SKIP_AHEAD
} }
} }
@ -1258,7 +1379,7 @@ fn set_packet_header(
fragment_count: usize, fragment_count: usize,
fragment_no: usize, fragment_no: usize,
packet_type: u8, packet_type: u8,
recipient_session_id: u64, remote_session_id: u64,
key_index: usize, key_index: usize,
counter: u64, counter: u64,
) { ) {
@ -1276,7 +1397,7 @@ fn set_packet_header(
// [58-63] fragment number (0..63) // [58-63] fragment number (0..63)
// [64-127] 64-bit counter // [64-127] 64-bit counter
memory::store_raw( memory::store_raw(
(u64::from(recipient_session_id) (u64::from(remote_session_id)
| ((key_index & 1) as u64).wrapping_shl(48) | ((key_index & 1) as u64).wrapping_shl(48)
| (packet_type as u64).wrapping_shl(49) | (packet_type as u64).wrapping_shl(49)
| ((fragment_count - 1) as u64).wrapping_shl(52) | ((fragment_count - 1) as u64).wrapping_shl(52)
@ -1323,13 +1444,13 @@ fn send_with_fragmentation<SendFunction: FnMut(&mut [u8])>(
packet: &mut [u8], packet: &mut [u8],
mtu: usize, mtu: usize,
packet_type: u8, packet_type: u8,
recipient_session_id: Option<SessionId>, remote_session_id: Option<SessionId>,
key_index: usize, key_index: usize,
counter: u64, counter: u64,
header_protect_cipher: Option<&Aes>, header_protect_cipher: Option<&Aes>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let packet_len = packet.len(); let packet_len = packet.len();
let recipient_session_id = recipient_session_id.map_or(SessionId::NONE, |s| u64::from(s)); let recipient_session_id = remote_session_id.map_or(SessionId::NONE, |s| u64::from(s));
let fragment_count = ((packet_len as f32) / (mtu as f32)).ceil() as usize; let fragment_count = ((packet_len as f32) / (mtu as f32)).ceil() as usize;
let mut fragment_start = 0; let mut fragment_start = 0;
let mut fragment_end = packet_len.min(mtu); let mut fragment_end = packet_len.min(mtu);
@ -1385,7 +1506,7 @@ impl SessionKey {
rekey_at_counter: current_counter.checked_add(Application::REKEY_AFTER_USES).unwrap(), rekey_at_counter: current_counter.checked_add(Application::REKEY_AFTER_USES).unwrap(),
expire_at_counter: current_counter.checked_add(Application::EXPIRE_AFTER_USES).unwrap(), expire_at_counter: current_counter.checked_add(Application::EXPIRE_AFTER_USES).unwrap(),
confirmed, confirmed,
role_is_bob, bob: role_is_bob,
} }
} }