mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-06-07 13:03:45 +02:00
Change the header yet again to encrypt everything but the session ID and key index.
This commit is contained in:
parent
8202a831b2
commit
3db9603799
6 changed files with 163 additions and 195 deletions
|
@ -66,6 +66,14 @@ pub fn as_byte_array<T: Copy, const S: usize>(o: &T) -> &[u8; S] {
|
||||||
unsafe { &*(o as *const T).cast() }
|
unsafe { &*(o as *const T).cast() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a reference to a raw object as a byte array.
|
||||||
|
/// The template parameter S must equal the size of the object in bytes or this will panic.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn as_byte_array_mut<T: Copy, const S: usize>(o: &mut T) -> &mut [u8; S] {
|
||||||
|
assert_eq!(S, size_of::<T>());
|
||||||
|
unsafe { &mut *(o as *mut T).cast() }
|
||||||
|
}
|
||||||
|
|
||||||
/// Transmute an object to a byte array.
|
/// Transmute an object to a byte array.
|
||||||
/// The template parameter S must equal the size of the object in bytes or this will panic.
|
/// The template parameter S must equal the size of the object in bytes or this will panic.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
|
|
@ -25,7 +25,7 @@ pub trait ApplicationLayer: Sized {
|
||||||
///
|
///
|
||||||
/// This can be e.g. a pooled buffer that automatically returns itself to the pool when dropped.
|
/// This can be e.g. a pooled buffer that automatically returns itself to the pool when dropped.
|
||||||
/// It can also just be a Vec<u8> or Box<[u8]> or something like that.
|
/// It can also just be a Vec<u8> or Box<[u8]> or something like that.
|
||||||
type IncomingPacketBuffer: AsRef<[u8]>;
|
type IncomingPacketBuffer: AsRef<[u8]> + AsMut<[u8]>;
|
||||||
|
|
||||||
/// Remote physical address on whatever transport this session is using.
|
/// Remote physical address on whatever transport this session is using.
|
||||||
type RemoteAddress;
|
type RemoteAddress;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
pub const MIN_PACKET_SIZE: usize = HEADER_SIZE + AES_GCM_TAG_SIZE;
|
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 = 1280;
|
pub const MIN_TRANSPORT_MTU: usize = 64;
|
||||||
|
|
||||||
/// Minimum recommended interval between calls to service() on each session, in milliseconds.
|
/// Minimum recommended interval between calls to service() on each session, in milliseconds.
|
||||||
pub const SERVICE_INTERVAL: u64 = 10000;
|
pub const SERVICE_INTERVAL: u64 = 10000;
|
||||||
|
@ -55,6 +55,12 @@ pub(crate) const HYBRID_KEY_TYPE_KYBER1024: u8 = 1;
|
||||||
/// Size of packet header
|
/// Size of packet header
|
||||||
pub(crate) const HEADER_SIZE: usize = 16;
|
pub(crate) const HEADER_SIZE: usize = 16;
|
||||||
|
|
||||||
|
/// Start of single block AES encryption of a portion of the header (and some data).
|
||||||
|
pub(crate) const HEADER_CHECK_ENCRYPT_START: usize = 6;
|
||||||
|
|
||||||
|
/// End of single block AES encryption of a portion of the header (and some data).
|
||||||
|
pub(crate) const HEADER_CHECK_ENCRYPT_END: usize = 22;
|
||||||
|
|
||||||
/// Size of AES-GCM keys (256 bits)
|
/// Size of AES-GCM keys (256 bits)
|
||||||
pub(crate) const AES_KEY_SIZE: usize = 32;
|
pub(crate) const AES_KEY_SIZE: usize = 32;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
use std::num::NonZeroU64;
|
||||||
|
|
||||||
use zerotier_crypto::random;
|
use zerotier_crypto::random;
|
||||||
use zerotier_utils::memory::{array_range, as_byte_array};
|
use zerotier_utils::memory::{array_range, as_byte_array};
|
||||||
|
@ -8,44 +9,43 @@ use crate::constants::SESSION_ID_SIZE;
|
||||||
/// 48-bit session ID (most significant 16 bits of u64 are unused)
|
/// 48-bit session ID (most significant 16 bits of u64 are unused)
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct SessionId(u64); // stored little endian internally
|
pub struct SessionId(NonZeroU64); // stored little endian internally
|
||||||
|
|
||||||
impl SessionId {
|
impl SessionId {
|
||||||
/// The nil session ID used in messages initiating a new session.
|
pub const MAX: u64 = 0x7fffffffffff;
|
||||||
///
|
|
||||||
/// This is all 1's so that ZeroTier can easily tell the difference between ZSSP init packets
|
|
||||||
/// and ZeroTier V1 packets.
|
|
||||||
pub const NIL: SessionId = SessionId(0xffffffffffffu64.to_le());
|
|
||||||
|
|
||||||
#[inline]
|
/// Create a new session ID, panicing if 'i' is zero or exceeds MAX.
|
||||||
pub fn new_from_u64(i: u64) -> Option<SessionId> {
|
pub fn new(i: u64) -> SessionId {
|
||||||
if i < Self::NIL.0 {
|
assert!(i <= Self::MAX);
|
||||||
Some(Self(i.to_le()))
|
Self(NonZeroU64::new(i.to_le()).unwrap())
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
/// Create a new random session ID (non-cryptographic PRNG)
|
||||||
pub fn new_random() -> Self {
|
pub fn random() -> Self {
|
||||||
Self((random::next_u64_secure() % Self::NIL.0).to_le())
|
Self(NonZeroU64::new(((random::xorshift64_random() % (Self::MAX - 1)) + 1).to_le()).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get this session ID as a 48-bit little endian byte array.
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn as_bytes(&self) -> &[u8; SESSION_ID_SIZE] {
|
pub(crate) fn new_from_u64_le(i: u64) -> Option<SessionId> {
|
||||||
|
NonZeroU64::new(i & Self::MAX.to_le()).map(|i| Self(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get this session ID as a little-endian byte array.
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn as_bytes(&self) -> &[u8; SESSION_ID_SIZE] {
|
||||||
array_range::<u8, 8, 0, SESSION_ID_SIZE>(as_byte_array(&self.0))
|
array_range::<u8, 8, 0, SESSION_ID_SIZE>(as_byte_array(&self.0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<SessionId> for u64 {
|
impl From<SessionId> for u64 {
|
||||||
|
#[inline(always)]
|
||||||
fn from(sid: SessionId) -> Self {
|
fn from(sid: SessionId) -> Self {
|
||||||
u64::from_le(sid.0)
|
u64::from_le(sid.0.get())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for SessionId {
|
impl Display for SessionId {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.write_fmt(format_args!("{:06x}", u64::from_le(self.0)))
|
f.write_fmt(format_args!("{:06x}", u64::from_le(self.0.get())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,7 +84,7 @@ mod tests {
|
||||||
loop {
|
loop {
|
||||||
let mut new_id = self.session_id_counter.lock().unwrap();
|
let mut new_id = self.session_id_counter.lock().unwrap();
|
||||||
*new_id += 1;
|
*new_id += 1;
|
||||||
return Some((SessionId::new_from_u64(*new_id).unwrap(), self.psk.clone(), 0));
|
return Some((SessionId::new_from_u64_le((*new_id).to_le()).unwrap(), self.psk.clone(), 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,7 +108,7 @@ mod tests {
|
||||||
Session::start_new(
|
Session::start_new(
|
||||||
&alice_host,
|
&alice_host,
|
||||||
|data| bob_host.queue.lock().unwrap().push_front(data.to_vec()),
|
|data| bob_host.queue.lock().unwrap().push_front(data.to_vec()),
|
||||||
SessionId::new_random(),
|
SessionId::random(),
|
||||||
bob_host.local_s.public_key_bytes(),
|
bob_host.local_s.public_key_bytes(),
|
||||||
&[],
|
&[],
|
||||||
&psk,
|
&psk,
|
||||||
|
|
296
zssp/src/zssp.rs
296
zssp/src/zssp.rs
|
@ -3,7 +3,7 @@
|
||||||
// ZSSP: ZeroTier Secure Session Protocol
|
// ZSSP: ZeroTier Secure Session Protocol
|
||||||
// FIPS compliant Noise_IK with Jedi powers and built-in attack-resistant large payload (fragmentation) support.
|
// FIPS compliant Noise_IK with Jedi powers and built-in attack-resistant large payload (fragmentation) support.
|
||||||
|
|
||||||
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
use std::sync::{Mutex, RwLock};
|
use std::sync::{Mutex, RwLock};
|
||||||
|
|
||||||
use zerotier_crypto::aes::{Aes, AesGcm};
|
use zerotier_crypto::aes::{Aes, AesGcm};
|
||||||
|
@ -207,68 +207,41 @@ impl<Application: ApplicationLayer> Session<Application> {
|
||||||
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(remote_session_id) = state.remote_session_id {
|
||||||
if let Some(session_key) = state.session_keys[state.cur_session_key_idx].as_ref() {
|
if let Some(session_key) = state.session_keys[state.cur_session_key_idx].as_ref() {
|
||||||
// Total size of the armored packet we are going to send (may end up being fragmented)
|
|
||||||
let packet_len = data.len() + HEADER_SIZE + AES_GCM_TAG_SIZE;
|
|
||||||
|
|
||||||
// This outgoing packet's nonce counter value.
|
|
||||||
let counter = self.send_counter.fetch_add(1, Ordering::SeqCst);
|
let counter = self.send_counter.fetch_add(1, Ordering::SeqCst);
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////
|
|
||||||
// packet encoding for post-noise transport
|
|
||||||
////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
// Create initial header for first fragment of packet and place in first HEADER_SIZE bytes of buffer.
|
|
||||||
create_packet_header(
|
|
||||||
mtu_sized_buffer,
|
|
||||||
packet_len,
|
|
||||||
mtu_sized_buffer.len(),
|
|
||||||
PACKET_TYPE_DATA,
|
|
||||||
remote_session_id.into(),
|
|
||||||
session_key.ratchet_count,
|
|
||||||
counter,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Get an initialized AES-GCM cipher and re-initialize with a 96-bit IV built from remote session ID,
|
|
||||||
// packet type, and counter.
|
|
||||||
let mut c = session_key.get_send_cipher(counter)?;
|
let mut c = session_key.get_send_cipher(counter)?;
|
||||||
c.reset_init_gcm(&create_message_nonce(PACKET_TYPE_DATA, counter));
|
c.reset_init_gcm(&create_message_nonce(PACKET_TYPE_DATA, counter));
|
||||||
|
|
||||||
// Send first N-1 fragments of N total fragments.
|
let fragment_count =
|
||||||
let last_fragment_size;
|
(((data.len() + AES_GCM_TAG_SIZE) as f32) / (mtu_sized_buffer.len() - HEADER_SIZE) as f32).ceil() as usize;
|
||||||
if packet_len > mtu_sized_buffer.len() {
|
let fragment_max_chunk_size = mtu_sized_buffer.len() - HEADER_SIZE;
|
||||||
let mut header: [u8; 16] = mtu_sized_buffer[..HEADER_SIZE].try_into().unwrap();
|
let last_fragment_no = fragment_count - 1;
|
||||||
let fragment_data_mtu = mtu_sized_buffer.len() - HEADER_SIZE;
|
for fragment_no in 0..fragment_count {
|
||||||
let last_fragment_data_mtu = mtu_sized_buffer.len() - (HEADER_SIZE + AES_GCM_TAG_SIZE);
|
let chunk_size = fragment_max_chunk_size.min(data.len());
|
||||||
loop {
|
let mut fragment_size = chunk_size + HEADER_SIZE;
|
||||||
let fragment_data_size = fragment_data_mtu.min(data.len());
|
set_packet_header(
|
||||||
let fragment_size = fragment_data_size + HEADER_SIZE;
|
mtu_sized_buffer,
|
||||||
c.crypt(&data[..fragment_data_size], &mut mtu_sized_buffer[HEADER_SIZE..fragment_size]);
|
fragment_count,
|
||||||
data = &data[fragment_data_size..];
|
fragment_no,
|
||||||
//set_header_check_code(mtu_sized_buffer, &self.header_check_cipher);
|
PACKET_TYPE_DATA,
|
||||||
send(&mut mtu_sized_buffer[..fragment_size]);
|
u64::from(remote_session_id),
|
||||||
|
session_key.ratchet_count,
|
||||||
debug_assert!(header[15].wrapping_shr(2) < 63);
|
counter,
|
||||||
header[15] += 0x04; // increment fragment number
|
)?;
|
||||||
mtu_sized_buffer[..HEADER_SIZE].copy_from_slice(&header);
|
c.crypt(&data[..chunk_size], &mut mtu_sized_buffer[HEADER_SIZE..fragment_size]);
|
||||||
|
data = &data[chunk_size..];
|
||||||
if data.len() <= last_fragment_data_mtu {
|
if fragment_no == last_fragment_no {
|
||||||
break;
|
debug_assert!(data.is_empty());
|
||||||
}
|
let tagged_fragment_size = fragment_size + AES_GCM_TAG_SIZE;
|
||||||
|
mtu_sized_buffer[fragment_size..tagged_fragment_size].copy_from_slice(&c.finish_encrypt());
|
||||||
|
fragment_size = tagged_fragment_size;
|
||||||
}
|
}
|
||||||
last_fragment_size = data.len() + HEADER_SIZE + AES_GCM_TAG_SIZE;
|
self.header_check_cipher
|
||||||
} else {
|
.encrypt_block_in_place(&mut mtu_sized_buffer[HEADER_CHECK_ENCRYPT_START..HEADER_CHECK_ENCRYPT_END]);
|
||||||
last_fragment_size = packet_len;
|
send(&mut mtu_sized_buffer[..fragment_size]);
|
||||||
}
|
}
|
||||||
|
debug_assert!(data.is_empty());
|
||||||
|
|
||||||
// Send final fragment (or only fragment if no fragmentation was needed)
|
|
||||||
let payload_end = data.len() + HEADER_SIZE;
|
|
||||||
c.crypt(data, &mut mtu_sized_buffer[HEADER_SIZE..payload_end]);
|
|
||||||
let gcm_tag = c.finish_encrypt();
|
|
||||||
mtu_sized_buffer[payload_end..last_fragment_size].copy_from_slice(&gcm_tag);
|
|
||||||
//set_header_check_code(mtu_sized_buffer, &self.header_check_cipher);
|
|
||||||
send(&mut mtu_sized_buffer[..last_fragment_size]);
|
|
||||||
|
|
||||||
// Check reusable AES-GCM instance back into pool.
|
|
||||||
session_key.return_send_cipher(c);
|
session_key.return_send_cipher(c);
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
@ -391,27 +364,30 @@ impl<Application: ApplicationLayer> ReceiveContext<Application> {
|
||||||
remote_address: &Application::RemoteAddress,
|
remote_address: &Application::RemoteAddress,
|
||||||
mut send: SendFunction,
|
mut send: SendFunction,
|
||||||
data_buf: &'a mut [u8],
|
data_buf: &'a mut [u8],
|
||||||
incoming_packet_buf: Application::IncomingPacketBuffer,
|
mut incoming_packet_buf: Application::IncomingPacketBuffer,
|
||||||
mtu: usize,
|
mtu: usize,
|
||||||
current_time: i64,
|
current_time: i64,
|
||||||
) -> Result<ReceiveResult<'a, Application>, Error> {
|
) -> Result<ReceiveResult<'a, Application>, Error> {
|
||||||
let incoming_packet = incoming_packet_buf.as_ref();
|
let incoming_packet: &mut [u8] = incoming_packet_buf.as_mut();
|
||||||
if incoming_packet.len() < MIN_PACKET_SIZE {
|
if incoming_packet.len() < MIN_PACKET_SIZE {
|
||||||
unlikely_branch();
|
unlikely_branch();
|
||||||
return Err(Error::InvalidPacket);
|
return Err(Error::InvalidPacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
let raw_counter_and_key_index = u64::from_le(memory::load_raw(&incoming_packet[0..8]));
|
let raw_local_session_id_key_index = memory::load_raw(incoming_packet);
|
||||||
let counter = raw_counter_and_key_index.wrapping_shr(1);
|
let key_index = (u64::from_le(raw_local_session_id_key_index).wrapping_shr(47) & 1) as usize;
|
||||||
let key_index = (raw_counter_and_key_index & 1) as usize;
|
|
||||||
let raw_session_id_type_and_fragment_info = u64::from_le(memory::load_raw(&incoming_packet[8..16]));
|
|
||||||
let local_session_id = SessionId::new_from_u64(raw_session_id_type_and_fragment_info & 0xffffffffffffu64);
|
|
||||||
let packet_type = (raw_session_id_type_and_fragment_info.wrapping_shr(48) & 0x0f) as u8;
|
|
||||||
let fragment_count = ((raw_session_id_type_and_fragment_info.wrapping_shr(48 + 4) & 63) + 1) as u8;
|
|
||||||
let fragment_no = raw_session_id_type_and_fragment_info.wrapping_shr(48 + 10) as u8; // & 63 not needed
|
|
||||||
|
|
||||||
if let Some(local_session_id) = local_session_id {
|
if let Some(local_session_id) = SessionId::new_from_u64_le(raw_local_session_id_key_index) {
|
||||||
if let Some(session) = app.lookup_session(local_session_id) {
|
if let Some(session) = app.lookup_session(local_session_id) {
|
||||||
|
session
|
||||||
|
.header_check_cipher
|
||||||
|
.decrypt_block_in_place(&mut incoming_packet[HEADER_CHECK_ENCRYPT_START..HEADER_CHECK_ENCRYPT_END]);
|
||||||
|
let raw_header_a = u16::from_le(memory::load_raw(&incoming_packet[6..]));
|
||||||
|
let packet_type = (raw_header_a & 0xf) as u8;
|
||||||
|
let fragment_count = ((raw_header_a.wrapping_shr(4) & 63) + 1) as u8;
|
||||||
|
let fragment_no = raw_header_a.wrapping_shr(10) as u8;
|
||||||
|
let counter = u64::from_le(memory::load_raw(&incoming_packet[8..]));
|
||||||
|
|
||||||
if session.check_receive_window(counter) {
|
if session.check_receive_window(counter) {
|
||||||
if fragment_count > 1 {
|
if fragment_count > 1 {
|
||||||
if fragment_count <= (MAX_FRAGMENTS as u8) && fragment_no < fragment_count {
|
if fragment_count <= (MAX_FRAGMENTS as u8) && fragment_no < fragment_count {
|
||||||
|
@ -462,6 +438,15 @@ impl<Application: ApplicationLayer> ReceiveContext<Application> {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
unlikely_branch(); // we want data receive to be the priority branch, this is only occasionally used
|
unlikely_branch(); // we want data receive to be the priority branch, this is only occasionally used
|
||||||
|
|
||||||
|
self.incoming_init_header_check_cipher
|
||||||
|
.decrypt_block_in_place(&mut incoming_packet[HEADER_CHECK_ENCRYPT_START..HEADER_CHECK_ENCRYPT_END]);
|
||||||
|
let raw_header_a = u16::from_le(memory::load_raw(&incoming_packet[6..]));
|
||||||
|
let packet_type = (raw_header_a & 0xf) as u8;
|
||||||
|
let fragment_count = ((raw_header_a.wrapping_shr(4) & 63) + 1) as u8;
|
||||||
|
let fragment_no = raw_header_a.wrapping_shr(10) as u8;
|
||||||
|
let counter = u64::from_le(memory::load_raw(&incoming_packet[8..]));
|
||||||
|
|
||||||
if fragment_count > 1 {
|
if fragment_count > 1 {
|
||||||
let mut defrag = self.initial_offer_defrag.lock().unwrap();
|
let mut defrag = self.initial_offer_defrag.lock().unwrap();
|
||||||
let fragment_gather_array = defrag.get_or_create_mut(&counter, || GatherArray::new(fragment_count));
|
let fragment_gather_array = defrag.get_or_create_mut(&counter, || GatherArray::new(fragment_count));
|
||||||
|
@ -531,10 +516,6 @@ impl<Application: ApplicationLayer> ReceiveContext<Application> {
|
||||||
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(session_key) = state.session_keys[key_index].as_ref() {
|
if let Some(session_key) = state.session_keys[key_index].as_ref() {
|
||||||
////////////////////////////////////////////////////////////////
|
|
||||||
// packet decoding for post-noise transport
|
|
||||||
////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
let mut c = session_key.get_receive_cipher();
|
let mut c = session_key.get_receive_cipher();
|
||||||
c.reset_init_gcm(&message_nonce);
|
c.reset_init_gcm(&message_nonce);
|
||||||
|
|
||||||
|
@ -622,7 +603,7 @@ impl<Application: ApplicationLayer> ReceiveContext<Application> {
|
||||||
|
|
||||||
// To greatly simplify logic handling key exchange packets, assemble these first.
|
// To greatly simplify logic handling key exchange packets, assemble these first.
|
||||||
// Handling KEX packets isn't the fast path so the extra copying isn't significant.
|
// Handling KEX packets isn't the fast path so the extra copying isn't significant.
|
||||||
const KEX_BUF_LEN: usize = MIN_TRANSPORT_MTU * KEY_EXCHANGE_MAX_FRAGMENTS;
|
const KEX_BUF_LEN: usize = 4096;
|
||||||
let mut kex_packet = [0_u8; KEX_BUF_LEN];
|
let mut kex_packet = [0_u8; KEX_BUF_LEN];
|
||||||
let mut kex_packet_len = 0;
|
let mut kex_packet_len = 0;
|
||||||
for i in 0..fragments.len() {
|
for i in 0..fragments.len() {
|
||||||
|
@ -651,6 +632,7 @@ impl<Application: ApplicationLayer> ReceiveContext<Application> {
|
||||||
match packet_type {
|
match packet_type {
|
||||||
PACKET_TYPE_INITIAL_KEY_OFFER => {
|
PACKET_TYPE_INITIAL_KEY_OFFER => {
|
||||||
// alice (remote) -> bob (local)
|
// alice (remote) -> bob (local)
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////
|
||||||
// packet decoding for noise initial key offer
|
// packet decoding for noise initial key offer
|
||||||
// -> e, es, s, ss
|
// -> e, es, s, ss
|
||||||
|
@ -905,16 +887,6 @@ impl<Application: ApplicationLayer> ReceiveContext<Application> {
|
||||||
}
|
}
|
||||||
let payload_end = idx;
|
let payload_end = idx;
|
||||||
|
|
||||||
create_packet_header(
|
|
||||||
&mut reply_buf,
|
|
||||||
payload_end,
|
|
||||||
mtu,
|
|
||||||
PACKET_TYPE_KEY_COUNTER_OFFER,
|
|
||||||
alice_session_id.into(),
|
|
||||||
next_ratchet_count,
|
|
||||||
reply_counter,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let reply_message_nonce = create_message_nonce(PACKET_TYPE_KEY_COUNTER_OFFER, reply_counter);
|
let reply_message_nonce = create_message_nonce(PACKET_TYPE_KEY_COUNTER_OFFER, reply_counter);
|
||||||
|
|
||||||
// Encrypt reply packet using final Noise_IK key BEFORE mixing hybrid or ratcheting, since the other side
|
// Encrypt reply packet using final Noise_IK key BEFORE mixing hybrid or ratcheting, since the other side
|
||||||
|
@ -971,7 +943,16 @@ impl<Application: ApplicationLayer> ReceiveContext<Application> {
|
||||||
|
|
||||||
// Bob now has final key state for this exchange. Yay! Now reply to Alice so she can construct it.
|
// Bob now has final key state for this exchange. Yay! Now reply to Alice so she can construct it.
|
||||||
|
|
||||||
send_with_fragmentation(send, &mut reply_buf[..packet_end], mtu, &session.header_check_cipher);
|
send_with_fragmentation(
|
||||||
|
send,
|
||||||
|
&mut reply_buf[..packet_end],
|
||||||
|
mtu,
|
||||||
|
PACKET_TYPE_KEY_COUNTER_OFFER,
|
||||||
|
u64::from(alice_session_id),
|
||||||
|
next_ratchet_count,
|
||||||
|
reply_counter,
|
||||||
|
&session.header_check_cipher,
|
||||||
|
)?;
|
||||||
|
|
||||||
if new_session.is_some() {
|
if new_session.is_some() {
|
||||||
return Ok(ReceiveResult::OkNewSession(new_session.unwrap()));
|
return Ok(ReceiveResult::OkNewSession(new_session.unwrap()));
|
||||||
|
@ -1089,31 +1070,6 @@ impl<Application: ApplicationLayer> ReceiveContext<Application> {
|
||||||
hybrid_kk.is_some(),
|
hybrid_kk.is_some(),
|
||||||
);
|
);
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////
|
|
||||||
// packet encoding for post-noise session start ack
|
|
||||||
////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
let mut reply_buf = [0_u8; HEADER_SIZE + AES_GCM_TAG_SIZE];
|
|
||||||
|
|
||||||
create_packet_header(
|
|
||||||
&mut reply_buf,
|
|
||||||
HEADER_SIZE + AES_GCM_TAG_SIZE,
|
|
||||||
mtu,
|
|
||||||
PACKET_TYPE_NOP,
|
|
||||||
bob_session_id.into(),
|
|
||||||
next_ratchet_count,
|
|
||||||
reply_counter,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let mut c = session_key.get_send_cipher(reply_counter)?;
|
|
||||||
c.reset_init_gcm(&create_message_nonce(PACKET_TYPE_NOP, reply_counter));
|
|
||||||
let gcm_tag = c.finish_encrypt();
|
|
||||||
safe_write_all(&mut reply_buf, HEADER_SIZE, &gcm_tag)?;
|
|
||||||
session_key.return_send_cipher(c);
|
|
||||||
|
|
||||||
//set_header_check_code(&mut reply_buf, &session.header_check_cipher);
|
|
||||||
send(&mut reply_buf);
|
|
||||||
|
|
||||||
drop(state);
|
drop(state);
|
||||||
let mut state = session.state.write().unwrap();
|
let mut state = session.state.write().unwrap();
|
||||||
|
|
||||||
|
@ -1184,8 +1140,7 @@ fn send_ephemeral_offer<SendFunction: FnMut(&mut [u8])>(
|
||||||
////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// Create ephemeral offer packet (not fragmented yet).
|
// Create ephemeral offer packet (not fragmented yet).
|
||||||
const PACKET_BUF_SIZE: usize = MIN_TRANSPORT_MTU * KEY_EXCHANGE_MAX_FRAGMENTS;
|
let mut packet_buf = [0_u8; 4096];
|
||||||
let mut packet_buf = [0_u8; PACKET_BUF_SIZE];
|
|
||||||
let mut idx = HEADER_SIZE;
|
let mut idx = HEADER_SIZE;
|
||||||
|
|
||||||
idx = safe_write_all(&mut packet_buf, idx, &[SESSION_PROTOCOL_VERSION])?;
|
idx = safe_write_all(&mut packet_buf, idx, &[SESSION_PROTOCOL_VERSION])?;
|
||||||
|
@ -1219,16 +1174,7 @@ fn send_ephemeral_offer<SendFunction: FnMut(&mut [u8])>(
|
||||||
noise_es.as_bytes(),
|
noise_es.as_bytes(),
|
||||||
));
|
));
|
||||||
|
|
||||||
let bob_session_id = bob_session_id.unwrap_or(SessionId::NIL);
|
let bob_session_id = bob_session_id.map_or(0u64, |i| u64::from(i));
|
||||||
create_packet_header(
|
|
||||||
&mut packet_buf,
|
|
||||||
payload_end,
|
|
||||||
mtu,
|
|
||||||
PACKET_TYPE_INITIAL_KEY_OFFER,
|
|
||||||
bob_session_id,
|
|
||||||
ratchet_count,
|
|
||||||
counter,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let message_nonce = create_message_nonce(PACKET_TYPE_INITIAL_KEY_OFFER, counter);
|
let message_nonce = create_message_nonce(PACKET_TYPE_INITIAL_KEY_OFFER, counter);
|
||||||
|
|
||||||
|
@ -1264,16 +1210,22 @@ fn send_ephemeral_offer<SendFunction: FnMut(&mut [u8])>(
|
||||||
idx = safe_write_all(&mut packet_buf, idx, &hmac2)?;
|
idx = safe_write_all(&mut packet_buf, idx, &hmac2)?;
|
||||||
let packet_end = idx;
|
let packet_end = idx;
|
||||||
|
|
||||||
if let Some(header_check_cipher) = header_check_cipher {
|
let mut init_header_check_cipher_tmp = None;
|
||||||
send_with_fragmentation(send, &mut packet_buf[..packet_end], mtu, header_check_cipher);
|
send_with_fragmentation(
|
||||||
} else {
|
send,
|
||||||
send_with_fragmentation(
|
&mut packet_buf[..packet_end],
|
||||||
send,
|
mtu,
|
||||||
&mut packet_buf[..packet_end],
|
PACKET_TYPE_INITIAL_KEY_OFFER,
|
||||||
mtu,
|
bob_session_id,
|
||||||
&Aes::new(kbkdf512(&bob_s_public_blob_hash, KBKDF_KEY_USAGE_LABEL_HEADER_CHECK).first_n::<HEADER_CHECK_AES_KEY_SIZE>()),
|
ratchet_count,
|
||||||
);
|
counter,
|
||||||
}
|
header_check_cipher.unwrap_or_else(|| {
|
||||||
|
init_header_check_cipher_tmp = Some(Aes::new(
|
||||||
|
kbkdf512(&bob_s_public_blob_hash, KBKDF_KEY_USAGE_LABEL_HEADER_CHECK).first_n::<HEADER_CHECK_AES_KEY_SIZE>(),
|
||||||
|
));
|
||||||
|
init_header_check_cipher_tmp.as_ref().unwrap()
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
|
||||||
*ret_ephemeral_offer = Some(EphemeralOffer {
|
*ret_ephemeral_offer = Some(EphemeralOffer {
|
||||||
id,
|
id,
|
||||||
|
@ -1288,42 +1240,37 @@ fn send_ephemeral_offer<SendFunction: FnMut(&mut [u8])>(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Populate all but the header check code in the first 16 bytes of a packet or fragment.
|
fn set_packet_header(
|
||||||
fn create_packet_header(
|
packet: &mut [u8],
|
||||||
header_destination_buffer: &mut [u8],
|
fragment_count: usize,
|
||||||
packet_len: usize,
|
fragment_no: usize,
|
||||||
mtu: usize,
|
|
||||||
packet_type: u8,
|
packet_type: u8,
|
||||||
recipient_session_id: SessionId,
|
recipient_session_id: u64,
|
||||||
ratchet_count: u64,
|
ratchet_count: u64,
|
||||||
counter: u64,
|
counter: u64,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let fragment_count = ((packet_len as f32) / (mtu - HEADER_SIZE) as f32).ceil() as usize;
|
debug_assert!(packet.len() >= MIN_PACKET_SIZE);
|
||||||
|
|
||||||
debug_assert!(header_destination_buffer.len() >= HEADER_SIZE);
|
|
||||||
debug_assert!(mtu >= MIN_TRANSPORT_MTU);
|
|
||||||
debug_assert!(packet_len >= MIN_PACKET_SIZE);
|
|
||||||
debug_assert!(fragment_count > 0);
|
debug_assert!(fragment_count > 0);
|
||||||
debug_assert!(fragment_count <= MAX_FRAGMENTS);
|
debug_assert!(fragment_no < MAX_FRAGMENTS);
|
||||||
debug_assert!(packet_type <= 0x0f); // packet type is 4 bits
|
debug_assert!(packet_type <= 0x0f); // packet type is 4 bits
|
||||||
|
|
||||||
if fragment_count <= MAX_FRAGMENTS {
|
if fragment_count <= MAX_FRAGMENTS {
|
||||||
// Header indexed by bit/byte:
|
// [0-46] recipient session ID
|
||||||
// [0-0] ratchet count least significant bit
|
// [47-47] ratchet count least significant bit (key index)
|
||||||
// [1-63] counter
|
// -- start of header check cipher single block encrypt --
|
||||||
// [64-111] recipient's session ID (unique on their side)
|
// [48-51] packet type (0-15)
|
||||||
// [112-115] packet type (0-15)
|
// [52-57] fragment count (1..64 - 1, so 0 means 1 fragment)
|
||||||
// [116-121] number of fragments (0..63 for 1..64 fragments total)
|
// [58-63] fragment number (0..63)
|
||||||
// [122-127] fragment number (0, 1, 2, ...)
|
// [64-127] 64-bit counter
|
||||||
memory::store_raw(
|
memory::store_raw(
|
||||||
(counter.wrapping_shl(1) | (ratchet_count & 1)).to_le(),
|
(u64::from(recipient_session_id)
|
||||||
&mut header_destination_buffer[0..],
|
| (ratchet_count & 1).wrapping_shl(47)
|
||||||
);
|
| (packet_type as u64).wrapping_shl(48)
|
||||||
memory::store_raw(
|
| ((fragment_count - 1) as u64).wrapping_shl(52)
|
||||||
(u64::from(recipient_session_id) | (packet_type as u64).wrapping_shl(48) | ((fragment_count - 1) as u64).wrapping_shl(52))
|
| (fragment_no as u64).wrapping_shl(58))
|
||||||
.to_le(),
|
.to_le(),
|
||||||
&mut header_destination_buffer[8..],
|
packet,
|
||||||
);
|
);
|
||||||
|
memory::store_raw(counter.to_le(), &mut packet[8..]);
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
unlikely_branch();
|
unlikely_branch();
|
||||||
|
@ -1350,31 +1297,38 @@ fn create_message_nonce(packet_type: u8, counter: u64) -> [u8; 12] {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Break a packet into fragments and send them all.
|
/// Break a packet into fragments and send them all.
|
||||||
|
/// The contents of packet[] are mangled during this operation, so it should be discarded after.
|
||||||
fn send_with_fragmentation<SendFunction: FnMut(&mut [u8])>(
|
fn send_with_fragmentation<SendFunction: FnMut(&mut [u8])>(
|
||||||
send: &mut SendFunction,
|
send: &mut SendFunction,
|
||||||
packet: &mut [u8],
|
packet: &mut [u8],
|
||||||
mtu: usize,
|
mtu: usize,
|
||||||
|
packet_type: u8,
|
||||||
|
recipient_session_id: u64,
|
||||||
|
ratchet_count: u64,
|
||||||
|
counter: u64,
|
||||||
header_check_cipher: &Aes,
|
header_check_cipher: &Aes,
|
||||||
) {
|
) -> Result<(), Error> {
|
||||||
let packet_len = packet.len();
|
let packet_len = packet.len();
|
||||||
|
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);
|
||||||
let mut header: [u8; 16] = packet[..HEADER_SIZE].try_into().unwrap();
|
for fragment_no in 0..fragment_count {
|
||||||
loop {
|
|
||||||
let fragment = &mut packet[fragment_start..fragment_end];
|
let fragment = &mut packet[fragment_start..fragment_end];
|
||||||
//set_header_check_code(fragment, header_check_cipher);
|
set_packet_header(
|
||||||
|
fragment,
|
||||||
|
fragment_count,
|
||||||
|
fragment_no,
|
||||||
|
packet_type,
|
||||||
|
recipient_session_id,
|
||||||
|
ratchet_count,
|
||||||
|
counter,
|
||||||
|
)?;
|
||||||
|
header_check_cipher.encrypt_block_in_place(&mut fragment[6..22]);
|
||||||
send(fragment);
|
send(fragment);
|
||||||
if fragment_end < packet_len {
|
fragment_start = fragment_end - HEADER_SIZE;
|
||||||
debug_assert!(header[15].wrapping_shr(2) < 63);
|
fragment_end = (fragment_start + mtu).min(packet_len);
|
||||||
header[15] += 0x04; // increment fragment number
|
|
||||||
fragment_start = fragment_end - HEADER_SIZE;
|
|
||||||
fragment_end = (fragment_start + mtu).min(packet_len);
|
|
||||||
packet[fragment_start..(fragment_start + HEADER_SIZE)].copy_from_slice(&header);
|
|
||||||
} else {
|
|
||||||
debug_assert_eq!(fragment_end, packet_len);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse KEY_OFFER and KEY_COUNTER_OFFER starting after the unencrypted public key part.
|
/// Parse KEY_OFFER and KEY_COUNTER_OFFER starting after the unencrypted public key part.
|
||||||
|
@ -1387,7 +1341,7 @@ fn parse_dec_key_offer_after_header(
|
||||||
|
|
||||||
let mut session_id_buf = 0_u64.to_ne_bytes();
|
let mut session_id_buf = 0_u64.to_ne_bytes();
|
||||||
session_id_buf[..SESSION_ID_SIZE].copy_from_slice(safe_read_exact(&mut p, SESSION_ID_SIZE)?);
|
session_id_buf[..SESSION_ID_SIZE].copy_from_slice(safe_read_exact(&mut p, SESSION_ID_SIZE)?);
|
||||||
let alice_session_id = SessionId::new_from_u64(u64::from_le_bytes(session_id_buf)).ok_or(Error::InvalidPacket)?;
|
let alice_session_id = SessionId::new_from_u64_le(u64::from_ne_bytes(session_id_buf)).ok_or(Error::InvalidPacket)?;
|
||||||
|
|
||||||
let alice_s_public_blob_len = varint_safe_read(&mut p)?;
|
let alice_s_public_blob_len = varint_safe_read(&mut p)?;
|
||||||
let alice_s_public_blob = safe_read_exact(&mut p, alice_s_public_blob_len as usize)?;
|
let alice_s_public_blob = safe_read_exact(&mut p, alice_s_public_blob_len as usize)?;
|
||||||
|
|
Loading…
Add table
Reference in a new issue