mirror of
https://github.com/ZeroTier/ZeroTierOne
synced 2025-08-22 22:33:58 -07:00
It opens a session.
This commit is contained in:
parent
9f8c4c9aae
commit
a2e0854d96
3 changed files with 402 additions and 222 deletions
185
zssp/src/main.rs
185
zssp/src/main.rs
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
@ -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],
|
||||||
|
|
242
zssp/src/zssp.rs
242
zssp/src/zssp.rs
|
@ -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> {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue