It opens a session.

This commit is contained in:
Adam Ierymenko 2023-02-28 17:03:00 -05:00
commit a2e0854d96
3 changed files with 402 additions and 222 deletions

View file

@ -1,3 +1,184 @@
use zssp::*; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {} use zerotier_crypto::p384::{P384KeyPair, P384PublicKey};
use zerotier_crypto::secret::Secret;
use zerotier_utils::ms_monotonic;
const TEST_MTU: usize = 1500;
struct TestApplication {
identity_key: P384KeyPair,
}
impl zssp::ApplicationLayer for TestApplication {
type Data = ();
type IncomingPacketBuffer = Vec<u8>;
fn get_local_s_public_blob(&self) -> &[u8] {
self.identity_key.public_key_bytes()
}
fn get_local_s_keypair(&self) -> &zerotier_crypto::p384::P384KeyPair {
&self.identity_key
}
}
fn alice_main(
run: &AtomicBool,
alice_app: &TestApplication,
bob_app: &TestApplication,
alice_out: mpsc::SyncSender<Vec<u8>>,
alice_in: mpsc::Receiver<Vec<u8>>,
) {
let context = zssp::Context::<TestApplication>::new(16);
let mut data_buf = [0u8; 65536];
let mut next_service = ms_monotonic() + 500;
let alice_session = context
.open(
alice_app,
|b| {
let _ = alice_out.send(b.to_vec());
},
TEST_MTU,
bob_app.identity_key.public_key(),
Secret::default(),
None,
(),
ms_monotonic(),
)
.unwrap();
println!("[alice] opening session {}", alice_session.id.to_string());
while run.load(Ordering::Relaxed) {
let pkt = alice_in.try_recv();
let current_time = ms_monotonic();
if let Ok(pkt) = pkt {
//println!("bob >> alice {}", pkt.len());
match context.receive(
alice_app,
|| true,
|s_public, _| Some((P384PublicKey::from_bytes(s_public).unwrap(), Secret::default(), ())),
|_, b| {
let _ = alice_out.send(b.to_vec());
},
&mut data_buf,
pkt,
TEST_MTU,
current_time,
) {
Ok(zssp::ReceiveResult::Ok) => {
println!("[alice] ok");
}
Ok(zssp::ReceiveResult::OkData(_, data)) => {
println!("[alice] received {}", data.len());
}
Ok(zssp::ReceiveResult::OkNewSession(s)) => {
println!("[alice] new session {}", s.id.to_string());
}
Ok(zssp::ReceiveResult::Rejected) => {}
Err(e) => {
println!("[alice] ERROR {}", e.to_string());
}
}
}
if current_time >= next_service {
next_service = current_time
+ context.service(
|_, b| {
let _ = alice_out.send(b.to_vec());
},
TEST_MTU,
current_time,
);
}
}
}
fn bob_main(
run: &AtomicBool,
_alice_app: &TestApplication,
bob_app: &TestApplication,
bob_out: mpsc::SyncSender<Vec<u8>>,
bob_in: mpsc::Receiver<Vec<u8>>,
) {
let context = zssp::Context::<TestApplication>::new(16);
let mut data_buf = [0u8; 65536];
let mut next_service = ms_monotonic() + 500;
while run.load(Ordering::Relaxed) {
let pkt = bob_in.recv_timeout(Duration::from_millis(10));
let current_time = ms_monotonic();
if let Ok(pkt) = pkt {
//println!("alice >> bob {}", pkt.len());
match context.receive(
bob_app,
|| true,
|s_public, _| Some((P384PublicKey::from_bytes(s_public).unwrap(), Secret::default(), ())),
|_, b| {
let _ = bob_out.send(b.to_vec());
},
&mut data_buf,
pkt,
TEST_MTU,
current_time,
) {
Ok(zssp::ReceiveResult::Ok) => {
println!("[bob] ok");
}
Ok(zssp::ReceiveResult::OkData(_, data)) => {
println!("[bob] received {}", data.len());
}
Ok(zssp::ReceiveResult::OkNewSession(s)) => {
println!("[bob] new session {}", s.id.to_string());
}
Ok(zssp::ReceiveResult::Rejected) => {}
Err(e) => {
println!("[bob] ERROR {}", e.to_string());
}
}
}
if current_time >= next_service {
next_service = current_time
+ context.service(
|_, b| {
let _ = bob_out.send(b.to_vec());
},
TEST_MTU,
current_time,
);
}
}
}
fn main() {
let run = AtomicBool::new(true);
let alice_app = TestApplication { identity_key: P384KeyPair::generate() };
let bob_app = TestApplication { identity_key: P384KeyPair::generate() };
let (alice_out, bob_in) = mpsc::sync_channel::<Vec<u8>>(128);
let (bob_out, alice_in) = mpsc::sync_channel::<Vec<u8>>(128);
thread::scope(|ts| {
let alice_thread = ts.spawn(|| alice_main(&run, &alice_app, &bob_app, alice_out, alice_in));
let bob_thread = ts.spawn(|| bob_main(&run, &alice_app, &bob_app, bob_out, bob_in));
thread::sleep(Duration::from_secs(60 * 10));
run.store(false, Ordering::SeqCst);
let _ = alice_thread.join();
let _ = bob_thread.join();
});
std::process::exit(0);
}

View file

@ -52,11 +52,10 @@ pub(crate) const MAX_NOISE_HANDSHAKE_SIZE: usize = MAX_NOISE_HANDSHAKE_FRAGMENTS
pub(crate) const BASE_KEY_SIZE: usize = 64; pub(crate) const BASE_KEY_SIZE: usize = 64;
pub(crate) const AES_KEY_SIZE: usize = 32; pub(crate) const AES_256_KEY_SIZE: usize = 32;
pub(crate) const AES_HEADER_PROTECTION_KEY_SIZE: usize = 16; pub(crate) const AES_HEADER_PROTECTION_KEY_SIZE: usize = 16;
pub(crate) const AES_GCM_TAG_SIZE: usize = 16; pub(crate) const AES_GCM_TAG_SIZE: usize = 16;
pub(crate) const AES_GCM_NONCE_SIZE: usize = 12; pub(crate) const AES_GCM_NONCE_SIZE: usize = 12;
pub(crate) const AES_CTR_NONCE_SIZE: usize = 12;
/// The first packet in Noise_XK exchange containing Alice's ephemeral keys, session ID, and a random /// The first packet in Noise_XK exchange containing Alice's ephemeral keys, session ID, and a random
/// symmetric key to protect header fragmentation fields for this session. /// symmetric key to protect header fragmentation fields for this session.
@ -66,7 +65,7 @@ pub(crate) struct AliceNoiseXKInit {
pub header: [u8; HEADER_SIZE], pub header: [u8; HEADER_SIZE],
pub session_protocol_version: u8, pub session_protocol_version: u8,
pub alice_noise_e: [u8; P384_PUBLIC_KEY_SIZE], pub alice_noise_e: [u8; P384_PUBLIC_KEY_SIZE],
// -- start AES-CTR(es) encrypted section (IV is last 12 bytes of alice_noise_e)) // -- start AES-CTR(es) encrypted section
pub alice_session_id: [u8; SessionId::SIZE], pub alice_session_id: [u8; SessionId::SIZE],
pub alice_hk_public: [u8; KYBER_PUBLICKEYBYTES], pub alice_hk_public: [u8; KYBER_PUBLICKEYBYTES],
pub header_protection_key: [u8; AES_HEADER_PROTECTION_KEY_SIZE], pub header_protection_key: [u8; AES_HEADER_PROTECTION_KEY_SIZE],
@ -87,7 +86,7 @@ pub(crate) struct BobNoiseXKAck {
pub header: [u8; HEADER_SIZE], pub header: [u8; HEADER_SIZE],
pub session_protocol_version: u8, pub session_protocol_version: u8,
pub bob_noise_e: [u8; P384_PUBLIC_KEY_SIZE], pub bob_noise_e: [u8; P384_PUBLIC_KEY_SIZE],
// -- start AES-CTR(es_ee) encrypted section (IV is last 12 bytes of bob_noise_e) // -- start AES-CTR(es_ee) encrypted section
pub bob_session_id: [u8; SessionId::SIZE], pub bob_session_id: [u8; SessionId::SIZE],
pub bob_hk_ciphertext: [u8; KYBER_CIPHERTEXTBYTES], pub bob_hk_ciphertext: [u8; KYBER_CIPHERTEXTBYTES],
// -- end encrypted sectiion // -- end encrypted sectiion
@ -107,7 +106,7 @@ impl BobNoiseXKAck {
pub(crate) struct AliceNoiseXKAck { pub(crate) struct AliceNoiseXKAck {
pub header: [u8; HEADER_SIZE], pub header: [u8; HEADER_SIZE],
pub session_protocol_version: u8, pub session_protocol_version: u8,
// -- start AES-CTR(es_ee) encrypted section (IV is first 12 bytes of hk) // -- start AES-CTR(es_ee_hk) encrypted section
pub alice_static_blob_length: [u8; 2], pub alice_static_blob_length: [u8; 2],
pub alice_static_blob: [u8; ???], pub alice_static_blob: [u8; ???],
pub alice_metadata_length: [u8; 2], pub alice_metadata_length: [u8; 2],

View file

@ -38,8 +38,7 @@ use crate::sessionid::SessionId;
/// defragment incoming packets that are not yet associated with a session. /// defragment incoming packets that are not yet associated with a session.
pub struct Context<Application: ApplicationLayer> { pub struct Context<Application: ApplicationLayer> {
max_incomplete_session_queue_size: usize, max_incomplete_session_queue_size: usize,
initial_offer_defrag: defrag: Mutex<RingBufferMap<u64, GatherArray<Application::IncomingPacketBuffer, MAX_NOISE_HANDSHAKE_FRAGMENTS>, 256, 256>>,
Mutex<RingBufferMap<u64, GatherArray<Application::IncomingPacketBuffer, MAX_NOISE_HANDSHAKE_FRAGMENTS>, 256, 256>>,
sessions: RwLock<SessionsById<Application>>, sessions: RwLock<SessionsById<Application>>,
} }
@ -49,7 +48,7 @@ struct SessionsById<Application: ApplicationLayer> {
active: HashMap<SessionId, Weak<Session<Application>>>, active: HashMap<SessionId, Weak<Session<Application>>>,
// Incomplete sessions in the middle of three-phase Noise_XK negotiation, expired after timeout. // Incomplete sessions in the middle of three-phase Noise_XK negotiation, expired after timeout.
incomplete: HashMap<SessionId, Arc<IncompleteIncomingSession>>, incoming: HashMap<SessionId, Arc<IncomingIncompleteSession>>,
} }
/// Result generated by the context packet receive function, with possible payloads. /// Result generated by the context packet receive function, with possible payloads.
@ -94,7 +93,7 @@ struct State {
} }
/// State related to an incoming session not yet fully established. /// State related to an incoming session not yet fully established.
struct IncompleteIncomingSession { struct IncomingIncompleteSession {
timestamp: i64, timestamp: i64,
request_hash: [u8; SHA384_HASH_SIZE], request_hash: [u8; SHA384_HASH_SIZE],
alice_session_id: SessionId, alice_session_id: SessionId,
@ -126,8 +125,8 @@ enum Offer {
/// An ephemeral session key with expiration info. /// 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_256_KEY_SIZE>, // Receive side AES-GCM key
send_key: Secret<AES_KEY_SIZE>, // Send side AES-GCM key send_key: Secret<AES_256_KEY_SIZE>, // Send side AES-GCM key
receive_cipher_pool: Mutex<Vec<Box<AesGcm>>>, // Pool of reusable sending ciphers receive_cipher_pool: Mutex<Vec<Box<AesGcm>>>, // Pool of reusable sending ciphers
send_cipher_pool: Mutex<Vec<Box<AesGcm>>>, // Pool of reusable receiving ciphers send_cipher_pool: Mutex<Vec<Box<AesGcm>>>, // Pool of reusable receiving ciphers
rekey_at_time: i64, // Rekey at or after this time (ticks) rekey_at_time: i64, // Rekey at or after this time (ticks)
@ -142,10 +141,10 @@ impl<Application: ApplicationLayer> Context<Application> {
pub fn new(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())), defrag: Mutex::new(RingBufferMap::new(random::next_u32_secure())),
sessions: RwLock::new(SessionsById { sessions: RwLock::new(SessionsById {
active: HashMap::with_capacity(64), active: HashMap::with_capacity(64),
incomplete: HashMap::with_capacity(64), incoming: HashMap::with_capacity(64),
}), }),
} }
} }
@ -212,8 +211,8 @@ impl<Application: ApplicationLayer> Context<Application> {
} }
} }
for (id, incomplete) in sessions.incomplete.iter() { for (id, incoming) in sessions.incoming.iter() {
if incomplete.timestamp < negotiation_timeout_cutoff { if incoming.timestamp < negotiation_timeout_cutoff {
dead_pending.push(*id); dead_pending.push(*id);
} }
} }
@ -225,7 +224,7 @@ impl<Application: ApplicationLayer> Context<Application> {
sessions.active.remove(id); sessions.active.remove(id);
} }
for id in dead_pending.iter() { for id in dead_pending.iter() {
sessions.incomplete.remove(id); sessions.incoming.remove(id);
} }
} }
@ -272,7 +271,7 @@ impl<Application: ApplicationLayer> Context<Application> {
let mut local_session_id; let mut local_session_id;
loop { loop {
local_session_id = SessionId::random(); local_session_id = SessionId::random();
if !sessions.active.contains_key(&local_session_id) && !sessions.incomplete.contains_key(&local_session_id) { if !sessions.active.contains_key(&local_session_id) && !sessions.incoming.contains_key(&local_session_id) {
break; break;
} }
} }
@ -320,9 +319,10 @@ impl<Application: ApplicationLayer> Context<Application> {
init.alice_hk_public = alice_hk_secret.public; init.alice_hk_public = alice_hk_secret.public;
init.header_protection_key = header_protection_key.0; init.header_protection_key = header_protection_key.0;
let mut ctr = AesCtr::new(kbkdf::<AES_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION>(noise_es.as_bytes()).as_bytes()); aes_ctr_crypt_one_time_use_key(
ctr.reset_set_iv(&alice_noise_e[P384_PUBLIC_KEY_SIZE - AES_CTR_NONCE_SIZE..]); kbkdf::<AES_256_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION>(noise_es.as_bytes()).as_bytes(),
ctr.crypt_in_place(&mut init_packet[AliceNoiseXKInit::ENC_START..AliceNoiseXKInit::AUTH_START]); &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(),
@ -394,7 +394,7 @@ impl<Application: ApplicationLayer> Context<Application> {
return Err(Error::InvalidPacket); return Err(Error::InvalidPacket);
} }
let mut incomplete = None; let mut incoming = 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 if let Some(session) = self
.sessions .sessions
@ -404,27 +404,27 @@ impl<Application: ApplicationLayer> Context<Application> {
.get(&local_session_id) .get(&local_session_id)
.and_then(|s| s.upgrade()) .and_then(|s| s.upgrade())
{ {
debug_assert!(self.sessions.read().unwrap().incomplete.contains_key(&local_session_id)); debug_assert!(!self.sessions.read().unwrap().incoming.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]);
let (key_index, packet_type, fragment_count, fragment_no, counter) = parse_packet_header(&incoming_packet); let (key_index, packet_type, fragment_count, fragment_no, incoming_counter) = parse_packet_header(&incoming_packet);
if session.check_receive_window(counter) { if session.check_receive_window(incoming_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 {
let mut defrag = session.defrag.lock().unwrap(); let mut defrag = session.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(&incoming_counter, || GatherArray::new(fragment_count));
if let Some(assembled_packet) = fragment_gather_array.add(fragment_no, incoming_packet_buf) { if let Some(assembled_packet) = fragment_gather_array.add(fragment_no, incoming_packet_buf) {
drop(defrag); // release lock drop(defrag); // release lock
return self.receive_complete( return self.process_complete_incoming_packet(
app, app,
&mut send, &mut send,
&mut check_allow_incoming_session, &mut check_allow_incoming_session,
&mut check_accept_session, &mut check_accept_session,
data_buf, data_buf,
counter, incoming_counter,
assembled_packet.as_ref(), assembled_packet.as_ref(),
packet_type, packet_type,
Some(session), Some(session),
@ -440,13 +440,13 @@ impl<Application: ApplicationLayer> Context<Application> {
return Err(Error::InvalidPacket); return Err(Error::InvalidPacket);
} }
} else { } else {
return self.receive_complete( return self.process_complete_incoming_packet(
app, app,
&mut send, &mut send,
&mut check_allow_incoming_session, &mut check_allow_incoming_session,
&mut check_accept_session, &mut check_accept_session,
data_buf, data_buf,
counter, incoming_counter,
&[incoming_packet_buf], &[incoming_packet_buf],
packet_type, packet_type,
Some(session), Some(session),
@ -460,53 +460,54 @@ impl<Application: ApplicationLayer> Context<Application> {
return Err(Error::OutOfSequence); return Err(Error::OutOfSequence);
} }
} else { } else {
if let Some(p) = self.sessions.read().unwrap().incomplete.get(&local_session_id).cloned() { if let Some(i) = self.sessions.read().unwrap().incoming.get(&local_session_id).cloned() {
Aes::new(p.header_protection_key.as_bytes()) Aes::new(i.header_protection_key.as_bytes())
.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]);
incomplete = Some(p); incoming = Some(i);
} else { } else {
println!("unknown {}", local_session_id.to_string());
return Err(Error::UnknownLocalSessionId); return Err(Error::UnknownLocalSessionId);
} }
} }
} }
// If we make it here the packet is not associated with a session or is associated with an // If we make it here the packet is not associated with a session or is associated with an
// incomplete session (Noise_XK mid-negotiation). // incoming session (Noise_XK mid-negotiation).
let (key_index, packet_type, fragment_count, fragment_no, counter) = parse_packet_header(&incoming_packet); let (key_index, packet_type, fragment_count, fragment_no, incoming_counter) = parse_packet_header(&incoming_packet);
if fragment_count > 1 { if fragment_count > 1 {
let mut defrag = self.initial_offer_defrag.lock().unwrap(); let mut defrag = self.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(&incoming_counter, || GatherArray::new(fragment_count));
if let Some(assembled_packet) = fragment_gather_array.add(fragment_no, incoming_packet_buf) { if let Some(assembled_packet) = fragment_gather_array.add(fragment_no, incoming_packet_buf) {
drop(defrag); // release lock drop(defrag); // release lock
return self.receive_complete( return self.process_complete_incoming_packet(
app, app,
&mut send, &mut send,
&mut check_allow_incoming_session, &mut check_allow_incoming_session,
&mut check_accept_session, &mut check_accept_session,
data_buf, data_buf,
counter, incoming_counter,
assembled_packet.as_ref(), assembled_packet.as_ref(),
packet_type, packet_type,
None, None,
incomplete, incoming,
key_index, key_index,
mtu, mtu,
current_time, current_time,
); );
} }
} else { } else {
return self.receive_complete( return self.process_complete_incoming_packet(
app, app,
&mut send, &mut send,
&mut check_allow_incoming_session, &mut check_allow_incoming_session,
&mut check_accept_session, &mut check_accept_session,
data_buf, data_buf,
counter, incoming_counter,
&[incoming_packet_buf], &[incoming_packet_buf],
packet_type, packet_type,
None, None,
incomplete, incoming,
key_index, key_index,
mtu, mtu,
current_time, current_time,
@ -516,7 +517,7 @@ impl<Application: ApplicationLayer> Context<Application> {
return Ok(ReceiveResult::Ok); return Ok(ReceiveResult::Ok);
} }
fn receive_complete< fn process_complete_incoming_packet<
'b, 'b,
SendFunction: FnMut(Option<&Arc<Session<Application>>>, &mut [u8]), SendFunction: FnMut(Option<&Arc<Session<Application>>>, &mut [u8]),
CheckAllowIncomingSession: FnMut() -> bool, CheckAllowIncomingSession: FnMut() -> bool,
@ -532,14 +533,16 @@ impl<Application: ApplicationLayer> Context<Application> {
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<IncompleteIncomingSession>>, incoming: Option<Arc<IncomingIncompleteSession>>,
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);
// Generate incoming message nonce for decryption and authentication.
let incoming_message_nonce = create_message_nonce(packet_type, incoming_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();
@ -648,30 +651,27 @@ impl<Application: ApplicationLayer> Context<Application> {
return Err(Error::OutOfSequence); return Err(Error::OutOfSequence);
} }
// Hash the init packet so we can check to see if it's just being retransmitted. // Hash the init packet so we can check to see if it's just being retransmitted. Alice may
// attempt to retransmit this packet until she receives a response.
let request_hash = SHA384::hash(&pkt_assembled); 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) = let (alice_session_id, mut bob_session_id, noise_es_ee, bob_hk_ciphertext, header_protection_key, bob_noise_e);
if let Some(incomplete) = incomplete { if let Some(incoming) = incoming {
// If we already have an incoming incomplete session record and the hash matches, recall the // If we've already seen this exact packet before, just recall the same state so we send the
// previous state so we can send an identical reply in response to a retransmit. // same response.
if secure_eq(&request_hash, &incoming.request_hash) {
if secure_eq(&request_hash, &incomplete.request_hash) { alice_session_id = incoming.alice_session_id;
( bob_session_id = incoming.bob_session_id;
incomplete.alice_session_id, noise_es_ee = incoming.noise_es_ee.clone();
incomplete.bob_session_id, bob_hk_ciphertext = incoming.bob_hk_ciphertext;
incomplete.noise_es_ee.clone(), header_protection_key = incoming.header_protection_key.clone();
incomplete.bob_hk_ciphertext, bob_noise_e = *incoming.bob_noise_e_secret.public_key_bytes();
incomplete.header_protection_key.clone(),
*incomplete.bob_noise_e_secret.public_key_bytes(),
)
} else { } else {
return Err(Error::FailedAuthentication); return Err(Error::FailedAuthentication);
} }
} else { } else {
// Otherwise parse the packet, authenticate, generate keys, etc. and record state in an // Otherwise parse the packet, authenticate, generate keys, etc. and record state in an
// incomplete state object until this phase of the negotiation is done. // incoming 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)?;
@ -694,48 +694,48 @@ impl<Application: ApplicationLayer> Context<Application> {
} }
// Decrypt encrypted part of payload. // Decrypt encrypted part of payload.
let mut ctr = aes_ctr_crypt_one_time_use_key(
AesCtr::new(kbkdf::<AES_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION>(noise_es.as_bytes()).as_bytes()); kbkdf::<AES_256_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]); &mut pkt_assembled[AliceNoiseXKInit::ENC_START..AliceNoiseXKInit::AUTH_START],
ctr.crypt_in_place(&mut pkt_assembled[AliceNoiseXKInit::ENC_START..AliceNoiseXKInit::AUTH_START]); );
let pkt: &AliceNoiseXKInit = byte_array_as_proto_buffer(pkt_assembled)?; let pkt: &AliceNoiseXKInit = byte_array_as_proto_buffer(pkt_assembled)?;
let alice_session_id = SessionId::new_from_bytes(&pkt.alice_session_id).ok_or(Error::InvalidPacket)?; alice_session_id = SessionId::new_from_bytes(&pkt.alice_session_id).ok_or(Error::InvalidPacket)?;
header_protection_key = Secret(pkt.header_protection_key);
// Create Bob's ephemeral keys and derive noise_es_ee by agreeing with Alice's. Also create // Create Bob's ephemeral keys and derive noise_es_ee by agreeing with Alice's. Also create
// a Kyber ciphertext to send back to Alice. // a Kyber ciphertext to send back to Alice.
let bob_noise_e_secret = P384KeyPair::generate(); let bob_noise_e_secret = P384KeyPair::generate();
let bob_noise_e = bob_noise_e_secret.public_key_bytes().clone(); bob_noise_e = bob_noise_e_secret.public_key_bytes().clone();
let noise_es_ee = Secret(hmac_sha512( noise_es_ee = Secret(hmac_sha512(
noise_es.as_bytes(), noise_es.as_bytes(),
bob_noise_e_secret bob_noise_e_secret
.agree(&alice_noise_e) .agree(&alice_noise_e)
.ok_or(Error::FailedAuthentication)? .ok_or(Error::FailedAuthentication)?
.as_bytes(), .as_bytes(),
)); ));
let (bob_hk_ciphertext, hk) = let (hk_ct, hk) = pqc_kyber::encapsulate(&pkt.alice_hk_public, &mut random::SecureRandom::default())
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)))?;
bob_hk_ciphertext = hk_ct;
let mut sessions = self.sessions.write().unwrap(); let mut sessions = self.sessions.write().unwrap();
let mut bob_session_id;
loop { loop {
bob_session_id = SessionId::random(); bob_session_id = SessionId::random();
if !sessions.active.contains_key(&bob_session_id) && !sessions.incomplete.contains_key(&bob_session_id) { if !sessions.active.contains_key(&bob_session_id) && !sessions.incoming.contains_key(&bob_session_id) {
break; break;
} }
} }
if sessions.incomplete.len() >= self.max_incomplete_session_queue_size { if sessions.incoming.len() >= self.max_incomplete_session_queue_size {
// If this queue is too big, we remove the latest entry and replace it. The latest // If this queue is too big, we remove the latest entry and replace it. The latest
// is used because under flood conditions this is most likely to be another bogus // is used because under flood conditions this is most likely to be another bogus
// entry. If we find one that is actually timed out, that one is replaced instead. // entry. If we find one that is actually timed out, that one is replaced instead.
let mut newest = i64::MIN; let mut newest = i64::MIN;
let mut replace_id = None; let mut replace_id = None;
let cutoff_time = current_time - Application::INCOMING_SESSION_NEGOTIATION_TIMEOUT_MS; let cutoff_time = current_time - Application::INCOMING_SESSION_NEGOTIATION_TIMEOUT_MS;
for (id, s) in sessions.incomplete.iter() { for (id, s) in sessions.incoming.iter() {
if s.timestamp <= cutoff_time { if s.timestamp <= cutoff_time {
replace_id = Some(*id); replace_id = Some(*id);
break; break;
@ -744,13 +744,13 @@ impl<Application: ApplicationLayer> Context<Application> {
replace_id = Some(*id); replace_id = Some(*id);
} }
} }
let _ = sessions.incomplete.remove(replace_id.as_ref().unwrap()); let _ = sessions.incoming.remove(replace_id.as_ref().unwrap());
} }
// Reserve session ID on this side and record incomplete session state. // Reserve session ID on this side and record incomplete session state.
sessions.incomplete.insert( sessions.incoming.insert(
bob_session_id, bob_session_id,
Arc::new(IncompleteIncomingSession { Arc::new(IncomingIncompleteSession {
timestamp: current_time, timestamp: current_time,
request_hash, request_hash,
alice_session_id, alice_session_id,
@ -762,16 +762,7 @@ impl<Application: ApplicationLayer> Context<Application> {
header_protection_key: Secret(pkt.header_protection_key), header_protection_key: Secret(pkt.header_protection_key),
}), }),
); );
}
(
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 ack_packet = [0u8; BobNoiseXKAck::SIZE]; let mut ack_packet = [0u8; BobNoiseXKAck::SIZE];
@ -781,11 +772,11 @@ impl<Application: ApplicationLayer> Context<Application> {
ack.bob_session_id = *bob_session_id.as_bytes(); ack.bob_session_id = *bob_session_id.as_bytes();
ack.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.
let mut ctr = aes_ctr_crypt_one_time_use_key(
AesCtr::new(kbkdf::<AES_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION>(noise_es_ee.as_bytes()).as_bytes()); kbkdf::<AES_256_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..]); &mut ack_packet[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(
@ -818,7 +809,7 @@ impl<Application: ApplicationLayer> Context<Application> {
* the negotiation. * the negotiation.
*/ */
if incoming_counter != 1 || incomplete.is_some() { if incoming_counter != 1 || incoming.is_some() {
return Err(Error::OutOfSequence); return Err(Error::OutOfSequence);
} }
@ -827,7 +818,6 @@ impl<Application: ApplicationLayer> Context<Application> {
if let Offer::NoiseXKInit(outgoing_offer) = &state.current_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) {
// Derive noise_es_ee from Bob's ephemeral public key. // Derive noise_es_ee from Bob's ephemeral public key.
let bob_noise_e = P384PublicKey::from_bytes(&pkt.bob_noise_e).ok_or(Error::FailedAuthentication)?; let bob_noise_e = P384PublicKey::from_bytes(&pkt.bob_noise_e).ok_or(Error::FailedAuthentication)?;
let noise_es_ee = Secret(hmac_sha512( let noise_es_ee = Secret(hmac_sha512(
@ -838,8 +828,7 @@ impl<Application: ApplicationLayer> Context<Application> {
.ok_or(Error::FailedAuthentication)? .ok_or(Error::FailedAuthentication)?
.as_bytes(), .as_bytes(),
)); ));
let noise_es_ee_kex_enc_key =
kbkdf::<AES_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION>(noise_es_ee.as_bytes());
let noise_es_ee_kex_hmac_key = let noise_es_ee_kex_hmac_key =
kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION>(noise_es_ee.as_bytes()); kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION>(noise_es_ee.as_bytes());
@ -856,11 +845,13 @@ impl<Application: ApplicationLayer> Context<Application> {
} }
// Decrypt encrypted portion of message. // Decrypt encrypted portion of message.
let mut ctr = AesCtr::new(noise_es_ee_kex_enc_key.as_bytes()); aes_ctr_crypt_one_time_use_key(
ctr.reset_set_iv(&SHA384::hash(&pkt.bob_noise_e)[..AES_CTR_NONCE_SIZE]); kbkdf::<AES_256_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION>(noise_es_ee.as_bytes()).as_bytes(),
ctr.crypt_in_place(&mut pkt_assembled[BobNoiseXKAck::ENC_START..BobNoiseXKAck::AUTH_START]); &mut pkt_assembled[BobNoiseXKAck::ENC_START..BobNoiseXKAck::AUTH_START],
);
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) {
// Complete Noise_XKpsk3 by mixing in noise_se followed by the PSK. The PSK as far as // Complete Noise_XKpsk3 by mixing in noise_se followed by the PSK. The PSK as far as
// the Noise pattern is concerned is the result of mixing the externally supplied PSK // the Noise pattern is concerned is the result of mixing the externally supplied PSK
// with the Kyber1024 shared secret (hk). Kyber is treated as part of the PSK because // with the Kyber1024 shared secret (hk). Kyber is treated as part of the PSK because
@ -903,12 +894,12 @@ impl<Application: ApplicationLayer> Context<Application> {
reply_buffer_append(&[0u8, 0u8]); // no meta-data reply_buffer_append(&[0u8, 0u8]); // no meta-data
} }
// Encrypt Alice's static identity and other inner payload items. The IV here // Encrypt Alice's static identity and other inner payload items. The key used here is
// is a hash of 'hk' making it actually a secret and "borrowing" a little PQ // mixed with 'hk' to make identity secrecy PQ forward secure.
// forward secrecy for Alice's identity. aes_ctr_crypt_one_time_use_key(
let mut ctr = AesCtr::new(noise_es_ee_kex_enc_key.as_bytes()); &hmac_sha512(noise_es_ee.as_bytes(), hk.as_bytes())[..AES_256_KEY_SIZE],
ctr.reset_set_iv(&hk.as_bytes()[..AES_CTR_NONCE_SIZE]); &mut reply_buffer[HEADER_SIZE + 1..reply_len],
ctr.crypt_in_place(&mut reply_buffer[HEADER_SIZE + 1..reply_len]); );
// First attach HMAC allowing Bob to verify that this is from the same Alice and to // First attach HMAC allowing Bob to verify that this is from the same Alice and to
// verify the authenticity of encrypted data. // verify the authenticity of encrypted data.
@ -983,9 +974,9 @@ impl<Application: ApplicationLayer> Context<Application> {
return Err(Error::InvalidPacket); return Err(Error::InvalidPacket);
} }
if let Some(incomplete) = incomplete { if let Some(incoming) = incoming {
// Check timeout, negotiations aren't allowed to take longer than this. // Check timeout, negotiations aren't allowed to take longer than this.
if (current_time - incomplete.timestamp) > Application::INCOMING_SESSION_NEGOTIATION_TIMEOUT_MS { if (current_time - incoming.timestamp) > Application::INCOMING_SESSION_NEGOTIATION_TIMEOUT_MS {
return Err(Error::UnknownLocalSessionId); return Err(Error::UnknownLocalSessionId);
} }
@ -995,7 +986,7 @@ impl<Application: ApplicationLayer> Context<Application> {
if !secure_eq( if !secure_eq(
&pkt_assembled[auth_start..pkt_assembled.len() - HMAC_SHA384_SIZE], &pkt_assembled[auth_start..pkt_assembled.len() - HMAC_SHA384_SIZE],
&hmac_sha384_2( &hmac_sha384_2(
kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION>(incomplete.noise_es_ee.as_bytes()) kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION>(incoming.noise_es_ee.as_bytes())
.as_bytes(), .as_bytes(),
&incoming_message_nonce, &incoming_message_nonce,
&pkt_assembled[HEADER_SIZE..auth_start], &pkt_assembled[HEADER_SIZE..auth_start],
@ -1009,11 +1000,10 @@ impl<Application: ApplicationLayer> Context<Application> {
pkt_assembly_buffer_copy[..pkt_assembled.len()].copy_from_slice(pkt_assembled); pkt_assembly_buffer_copy[..pkt_assembled.len()].copy_from_slice(pkt_assembled);
// Decrypt encrypted section so we can finally learn Alice's static identity. // Decrypt encrypted section so we can finally learn Alice's static identity.
let mut ctr = AesCtr::new( aes_ctr_crypt_one_time_use_key(
kbkdf::<AES_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION>(incomplete.noise_es_ee.as_bytes()).as_bytes(), &hmac_sha512(incoming.noise_es_ee.as_bytes(), incoming.hk.as_bytes())[..AES_256_KEY_SIZE],
&mut pkt_assembled[ALICE_NOISE_XK_ACK_ENC_START..auth_start],
); );
ctr.reset_set_iv(&incomplete.hk.as_bytes()[..AES_CTR_NONCE_SIZE]);
ctr.crypt_in_place(&mut pkt_assembled[ALICE_NOISE_XK_ACK_ENC_START..auth_start]);
// Read the static public blob and optional meta-data. // Read the static public blob and optional meta-data.
let mut pkt_assembled_ptr = HEADER_SIZE + 1; let mut pkt_assembled_ptr = HEADER_SIZE + 1;
@ -1048,7 +1038,7 @@ impl<Application: ApplicationLayer> Context<Application> {
// her static public blob. // her static public blob.
let check_result = check_accept_session(alice_static_public_blob, alice_meta_data); let check_result = check_accept_session(alice_static_public_blob, alice_meta_data);
if check_result.is_none() { if check_result.is_none() {
self.sessions.write().unwrap().incomplete.remove(&incomplete.bob_session_id); self.sessions.write().unwrap().incoming.remove(&incoming.bob_session_id);
return Ok(ReceiveResult::Rejected); return Ok(ReceiveResult::Rejected);
} }
let (alice_noise_s, psk, application_data) = check_result.unwrap(); let (alice_noise_s, psk, application_data) = check_result.unwrap();
@ -1056,14 +1046,14 @@ impl<Application: ApplicationLayer> Context<Application> {
// Complete Noise_XKpsk3 on Bob's side. // Complete Noise_XKpsk3 on Bob's side.
let noise_es_ee_se_hk_psk = Secret(hmac_sha512( let noise_es_ee_se_hk_psk = Secret(hmac_sha512(
&hmac_sha512( &hmac_sha512(
incomplete.noise_es_ee.as_bytes(), incoming.noise_es_ee.as_bytes(),
incomplete incoming
.bob_noise_e_secret .bob_noise_e_secret
.agree(&alice_noise_s) .agree(&alice_noise_s)
.ok_or(Error::FailedAuthentication)? .ok_or(Error::FailedAuthentication)?
.as_bytes(), .as_bytes(),
), ),
&hmac_sha512(psk.as_bytes(), incomplete.hk.as_bytes()), &hmac_sha512(psk.as_bytes(), incoming.hk.as_bytes()),
)); ));
// Verify the packet using the final key to verify the whole key exchange. // Verify the packet using the final key to verify the whole key exchange.
@ -1073,21 +1063,21 @@ impl<Application: ApplicationLayer> Context<Application> {
kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION>(noise_es_ee_se_hk_psk.as_bytes()) kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION>(noise_es_ee_se_hk_psk.as_bytes())
.as_bytes(), .as_bytes(),
&incoming_message_nonce, &incoming_message_nonce,
&pkt_assembly_buffer_copy[HEADER_SIZE..auth_start], &pkt_assembly_buffer_copy[HEADER_SIZE..auth_start + HMAC_SHA384_SIZE],
), ),
) { ) {
return Err(Error::FailedAuthentication); return Err(Error::FailedAuthentication);
} }
let session = Arc::new(Session { let session = Arc::new(Session {
id: incomplete.bob_session_id, id: incoming.bob_session_id,
application_data, application_data,
psk, psk,
send_counter: AtomicU64::new(2), // 1 was already used during negotiation send_counter: AtomicU64::new(2), // 1 was already used during negotiation
receive_window: std::array::from_fn(|_| AtomicU64::new(0)), receive_window: std::array::from_fn(|_| AtomicU64::new(0)),
header_protection_cipher: Aes::new(incomplete.header_protection_key.as_bytes()), header_protection_cipher: Aes::new(incoming.header_protection_key.as_bytes()),
state: RwLock::new(State { state: RwLock::new(State {
remote_session_id: Some(incomplete.alice_session_id), remote_session_id: Some(incoming.alice_session_id),
keys: [ keys: [
Some(SessionKey::new::<Application>(noise_es_ee_se_hk_psk, current_time, 2, true)), Some(SessionKey::new::<Application>(noise_es_ee_se_hk_psk, current_time, 2, true)),
None, None,
@ -1101,8 +1091,8 @@ impl<Application: ApplicationLayer> Context<Application> {
// Promote this from an incomplete session to an established session. // 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.incoming.remove(&incoming.bob_session_id);
sessions.active.insert(incomplete.bob_session_id, Arc::downgrade(&session)); sessions.active.insert(incoming.bob_session_id, Arc::downgrade(&session));
} }
return Ok(ReceiveResult::OkNewSession(session)); return Ok(ReceiveResult::OkNewSession(session));
@ -1115,7 +1105,7 @@ 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() { if incoming.is_some() {
return Err(Error::OutOfSequence); return Err(Error::OutOfSequence);
} }
@ -1178,7 +1168,7 @@ 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() { if incoming.is_some() {
return Err(Error::OutOfSequence); return Err(Error::OutOfSequence);
} }
@ -1480,8 +1470,8 @@ fn assemble_fragments_into<A: ApplicationLayer>(fragments: &[A::IncomingPacketBu
impl SessionKey { impl SessionKey {
fn new<Application: ApplicationLayer>(key: Secret<BASE_KEY_SIZE>, current_time: i64, current_counter: u64, role_is_bob: bool) -> Self { fn new<Application: ApplicationLayer>(key: Secret<BASE_KEY_SIZE>, current_time: i64, current_counter: u64, role_is_bob: bool) -> Self {
let a2b = kbkdf::<AES_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_AES_GCM_ALICE_TO_BOB>(key.as_bytes()); let a2b = kbkdf::<AES_256_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_AES_GCM_ALICE_TO_BOB>(key.as_bytes());
let b2a = kbkdf::<AES_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE>(key.as_bytes()); let b2a = kbkdf::<AES_256_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE>(key.as_bytes());
let (receive_key, send_key) = if role_is_bob { let (receive_key, send_key) = if role_is_bob {
(a2b, b2a) (a2b, b2a)
} else { } else {
@ -1549,6 +1539,16 @@ fn hmac_sha384_2(key: &[u8], a: &[u8], b: &[u8]) -> [u8; 48] {
hmac.finish() hmac.finish()
} }
/// Shortcut to AES-CTR encrypt or decrypt with a zero IV.
///
/// This is used during Noise_XK handshaking. Each stage uses a different key to encrypt the
/// payload that is used only once per handshake and per session.
fn aes_ctr_crypt_one_time_use_key(key: &[u8], data: &mut [u8]) {
let mut ctr = AesCtr::new(key);
ctr.reset_set_iv(&[0u8; 12]);
ctr.crypt_in_place(data);
}
/// HMAC-SHA512 key derivation based on: https://csrc.nist.gov/publications/detail/sp/800-108/final (page 7) /// HMAC-SHA512 key derivation based on: https://csrc.nist.gov/publications/detail/sp/800-108/final (page 7)
/// Cryptographically this isn't meaningfully different from HMAC(key, [label]) but this is how NIST rolls. /// Cryptographically this isn't meaningfully different from HMAC(key, [label]) but this is how NIST rolls.
fn kbkdf<const OUTPUT_BYTES: usize, const LABEL: u8>(key: &[u8]) -> Secret<OUTPUT_BYTES> { fn kbkdf<const OUTPUT_BYTES: usize, const LABEL: u8>(key: &[u8]) -> Secret<OUTPUT_BYTES> {