mirror of
https://github.com/ZeroTier/ZeroTierOne
synced 2025-08-22 14:23:59 -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 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_GCM_TAG_SIZE: usize = 16;
|
||||
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
|
||||
/// symmetric key to protect header fragmentation fields for this session.
|
||||
|
@ -66,7 +65,7 @@ pub(crate) struct AliceNoiseXKInit {
|
|||
pub header: [u8; HEADER_SIZE],
|
||||
pub session_protocol_version: u8,
|
||||
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_hk_public: [u8; KYBER_PUBLICKEYBYTES],
|
||||
pub header_protection_key: [u8; AES_HEADER_PROTECTION_KEY_SIZE],
|
||||
|
@ -87,7 +86,7 @@ pub(crate) struct BobNoiseXKAck {
|
|||
pub header: [u8; HEADER_SIZE],
|
||||
pub session_protocol_version: u8,
|
||||
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_hk_ciphertext: [u8; KYBER_CIPHERTEXTBYTES],
|
||||
// -- end encrypted sectiion
|
||||
|
@ -107,7 +106,7 @@ impl BobNoiseXKAck {
|
|||
pub(crate) struct AliceNoiseXKAck {
|
||||
pub header: [u8; HEADER_SIZE],
|
||||
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: [u8; ???],
|
||||
pub alice_metadata_length: [u8; 2],
|
||||
|
|
430
zssp/src/zssp.rs
430
zssp/src/zssp.rs
|
@ -38,8 +38,7 @@ use crate::sessionid::SessionId;
|
|||
/// defragment incoming packets that are not yet associated with a session.
|
||||
pub struct Context<Application: ApplicationLayer> {
|
||||
max_incomplete_session_queue_size: usize,
|
||||
initial_offer_defrag:
|
||||
Mutex<RingBufferMap<u64, GatherArray<Application::IncomingPacketBuffer, MAX_NOISE_HANDSHAKE_FRAGMENTS>, 256, 256>>,
|
||||
defrag: Mutex<RingBufferMap<u64, GatherArray<Application::IncomingPacketBuffer, MAX_NOISE_HANDSHAKE_FRAGMENTS>, 256, 256>>,
|
||||
sessions: RwLock<SessionsById<Application>>,
|
||||
}
|
||||
|
||||
|
@ -49,7 +48,7 @@ struct SessionsById<Application: ApplicationLayer> {
|
|||
active: HashMap<SessionId, Weak<Session<Application>>>,
|
||||
|
||||
// Incomplete sessions in the middle of three-phase Noise_XK negotiation, expired after timeout.
|
||||
incomplete: HashMap<SessionId, Arc<IncompleteIncomingSession>>,
|
||||
incoming: HashMap<SessionId, Arc<IncomingIncompleteSession>>,
|
||||
}
|
||||
|
||||
/// 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.
|
||||
struct IncompleteIncomingSession {
|
||||
struct IncomingIncompleteSession {
|
||||
timestamp: i64,
|
||||
request_hash: [u8; SHA384_HASH_SIZE],
|
||||
alice_session_id: SessionId,
|
||||
|
@ -126,8 +125,8 @@ enum Offer {
|
|||
/// An ephemeral session key with expiration info.
|
||||
struct SessionKey {
|
||||
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
|
||||
send_key: Secret<AES_KEY_SIZE>, // Send side AES-GCM key
|
||||
receive_key: Secret<AES_256_KEY_SIZE>, // Receive 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
|
||||
send_cipher_pool: Mutex<Vec<Box<AesGcm>>>, // Pool of reusable receiving ciphers
|
||||
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 {
|
||||
Self {
|
||||
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 {
|
||||
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() {
|
||||
if incomplete.timestamp < negotiation_timeout_cutoff {
|
||||
for (id, incoming) in sessions.incoming.iter() {
|
||||
if incoming.timestamp < negotiation_timeout_cutoff {
|
||||
dead_pending.push(*id);
|
||||
}
|
||||
}
|
||||
|
@ -225,7 +224,7 @@ impl<Application: ApplicationLayer> Context<Application> {
|
|||
sessions.active.remove(id);
|
||||
}
|
||||
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;
|
||||
loop {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -320,9 +319,10 @@ impl<Application: ApplicationLayer> Context<Application> {
|
|||
init.alice_hk_public = alice_hk_secret.public;
|
||||
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());
|
||||
ctr.reset_set_iv(&alice_noise_e[P384_PUBLIC_KEY_SIZE - AES_CTR_NONCE_SIZE..]);
|
||||
ctr.crypt_in_place(&mut init_packet[AliceNoiseXKInit::ENC_START..AliceNoiseXKInit::AUTH_START]);
|
||||
aes_ctr_crypt_one_time_use_key(
|
||||
kbkdf::<AES_256_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION>(noise_es.as_bytes()).as_bytes(),
|
||||
&mut init_packet[AliceNoiseXKInit::ENC_START..AliceNoiseXKInit::AUTH_START],
|
||||
);
|
||||
|
||||
let hmac = hmac_sha384_2(
|
||||
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);
|
||||
}
|
||||
|
||||
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(session) = self
|
||||
.sessions
|
||||
|
@ -404,27 +404,27 @@ impl<Application: ApplicationLayer> Context<Application> {
|
|||
.get(&local_session_id)
|
||||
.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
|
||||
.header_protection_cipher
|
||||
.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 <= (MAX_FRAGMENTS as u8) && fragment_no < fragment_count {
|
||||
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) {
|
||||
drop(defrag); // release lock
|
||||
return self.receive_complete(
|
||||
return self.process_complete_incoming_packet(
|
||||
app,
|
||||
&mut send,
|
||||
&mut check_allow_incoming_session,
|
||||
&mut check_accept_session,
|
||||
data_buf,
|
||||
counter,
|
||||
incoming_counter,
|
||||
assembled_packet.as_ref(),
|
||||
packet_type,
|
||||
Some(session),
|
||||
|
@ -440,13 +440,13 @@ impl<Application: ApplicationLayer> Context<Application> {
|
|||
return Err(Error::InvalidPacket);
|
||||
}
|
||||
} else {
|
||||
return self.receive_complete(
|
||||
return self.process_complete_incoming_packet(
|
||||
app,
|
||||
&mut send,
|
||||
&mut check_allow_incoming_session,
|
||||
&mut check_accept_session,
|
||||
data_buf,
|
||||
counter,
|
||||
incoming_counter,
|
||||
&[incoming_packet_buf],
|
||||
packet_type,
|
||||
Some(session),
|
||||
|
@ -460,53 +460,54 @@ impl<Application: ApplicationLayer> Context<Application> {
|
|||
return Err(Error::OutOfSequence);
|
||||
}
|
||||
} else {
|
||||
if let Some(p) = self.sessions.read().unwrap().incomplete.get(&local_session_id).cloned() {
|
||||
Aes::new(p.header_protection_key.as_bytes())
|
||||
if let Some(i) = self.sessions.read().unwrap().incoming.get(&local_session_id).cloned() {
|
||||
Aes::new(i.header_protection_key.as_bytes())
|
||||
.decrypt_block_in_place(&mut incoming_packet[HEADER_PROTECT_ENCRYPT_START..HEADER_PROTECT_ENCRYPT_END]);
|
||||
incomplete = Some(p);
|
||||
incoming = Some(i);
|
||||
} else {
|
||||
println!("unknown {}", local_session_id.to_string());
|
||||
return Err(Error::UnknownLocalSessionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
let mut defrag = self.initial_offer_defrag.lock().unwrap();
|
||||
let fragment_gather_array = defrag.get_or_create_mut(&counter, || GatherArray::new(fragment_count));
|
||||
let mut defrag = self.defrag.lock().unwrap();
|
||||
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) {
|
||||
drop(defrag); // release lock
|
||||
return self.receive_complete(
|
||||
return self.process_complete_incoming_packet(
|
||||
app,
|
||||
&mut send,
|
||||
&mut check_allow_incoming_session,
|
||||
&mut check_accept_session,
|
||||
data_buf,
|
||||
counter,
|
||||
incoming_counter,
|
||||
assembled_packet.as_ref(),
|
||||
packet_type,
|
||||
None,
|
||||
incomplete,
|
||||
incoming,
|
||||
key_index,
|
||||
mtu,
|
||||
current_time,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return self.receive_complete(
|
||||
return self.process_complete_incoming_packet(
|
||||
app,
|
||||
&mut send,
|
||||
&mut check_allow_incoming_session,
|
||||
&mut check_accept_session,
|
||||
data_buf,
|
||||
counter,
|
||||
incoming_counter,
|
||||
&[incoming_packet_buf],
|
||||
packet_type,
|
||||
None,
|
||||
incomplete,
|
||||
incoming,
|
||||
key_index,
|
||||
mtu,
|
||||
current_time,
|
||||
|
@ -516,7 +517,7 @@ impl<Application: ApplicationLayer> Context<Application> {
|
|||
return Ok(ReceiveResult::Ok);
|
||||
}
|
||||
|
||||
fn receive_complete<
|
||||
fn process_complete_incoming_packet<
|
||||
'b,
|
||||
SendFunction: FnMut(Option<&Arc<Session<Application>>>, &mut [u8]),
|
||||
CheckAllowIncomingSession: FnMut() -> bool,
|
||||
|
@ -532,14 +533,16 @@ impl<Application: ApplicationLayer> Context<Application> {
|
|||
fragments: &[Application::IncomingPacketBuffer],
|
||||
packet_type: u8,
|
||||
session: Option<Arc<Session<Application>>>,
|
||||
incomplete: Option<Arc<IncompleteIncomingSession>>,
|
||||
incoming: Option<Arc<IncomingIncompleteSession>>,
|
||||
key_index: usize,
|
||||
mtu: usize,
|
||||
current_time: i64,
|
||||
) -> Result<ReceiveResult<'b, Application>, Error> {
|
||||
debug_assert!(fragments.len() >= 1);
|
||||
|
||||
// Generate incoming message nonce for decryption and authentication.
|
||||
let incoming_message_nonce = create_message_nonce(packet_type, incoming_counter);
|
||||
|
||||
if packet_type == PACKET_TYPE_DATA {
|
||||
if let Some(session) = session {
|
||||
let state = session.state.read().unwrap();
|
||||
|
@ -648,130 +651,118 @@ impl<Application: ApplicationLayer> Context<Application> {
|
|||
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 (alice_session_id, bob_session_id, noise_es_ee, bob_hk_ciphertext, header_protection_key, bob_noise_e) =
|
||||
if let Some(incomplete) = incomplete {
|
||||
// If we already have an incoming incomplete session record and the hash matches, recall the
|
||||
// previous state so we can send an identical reply in response to a retransmit.
|
||||
|
||||
if secure_eq(&request_hash, &incomplete.request_hash) {
|
||||
(
|
||||
incomplete.alice_session_id,
|
||||
incomplete.bob_session_id,
|
||||
incomplete.noise_es_ee.clone(),
|
||||
incomplete.bob_hk_ciphertext,
|
||||
incomplete.header_protection_key.clone(),
|
||||
*incomplete.bob_noise_e_secret.public_key_bytes(),
|
||||
)
|
||||
} else {
|
||||
return Err(Error::FailedAuthentication);
|
||||
}
|
||||
let (alice_session_id, mut bob_session_id, noise_es_ee, bob_hk_ciphertext, header_protection_key, bob_noise_e);
|
||||
if let Some(incoming) = incoming {
|
||||
// If we've already seen this exact packet before, just recall the same state so we send the
|
||||
// same response.
|
||||
if secure_eq(&request_hash, &incoming.request_hash) {
|
||||
alice_session_id = incoming.alice_session_id;
|
||||
bob_session_id = incoming.bob_session_id;
|
||||
noise_es_ee = incoming.noise_es_ee.clone();
|
||||
bob_hk_ciphertext = incoming.bob_hk_ciphertext;
|
||||
header_protection_key = incoming.header_protection_key.clone();
|
||||
bob_noise_e = *incoming.bob_noise_e_secret.public_key_bytes();
|
||||
} else {
|
||||
// Otherwise parse the packet, authenticate, generate keys, etc. and record state in an
|
||||
// incomplete state object until this phase of the negotiation is done.
|
||||
return Err(Error::FailedAuthentication);
|
||||
}
|
||||
} else {
|
||||
// Otherwise parse the packet, authenticate, generate keys, etc. and record state in an
|
||||
// incoming state object until this phase of the negotiation is done.
|
||||
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 noise_es = app.get_local_s_keypair().agree(&alice_noise_e).ok_or(Error::FailedAuthentication)?;
|
||||
|
||||
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 noise_es = app.get_local_s_keypair().agree(&alice_noise_e).ok_or(Error::FailedAuthentication)?;
|
||||
// Authenticate packet and also prove that Alice knows our static public key.
|
||||
if !secure_eq(
|
||||
&pkt.hmac_es,
|
||||
&hmac_sha384_2(
|
||||
kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION>(noise_es.as_bytes()).as_bytes(),
|
||||
&incoming_message_nonce,
|
||||
&pkt_assembled[HEADER_SIZE..AliceNoiseXKInit::AUTH_START],
|
||||
),
|
||||
) {
|
||||
return Err(Error::FailedAuthentication);
|
||||
}
|
||||
|
||||
// Authenticate packet and also prove that Alice knows our static public key.
|
||||
if !secure_eq(
|
||||
&pkt.hmac_es,
|
||||
&hmac_sha384_2(
|
||||
kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION>(noise_es.as_bytes()).as_bytes(),
|
||||
&incoming_message_nonce,
|
||||
&pkt_assembled[HEADER_SIZE..AliceNoiseXKInit::AUTH_START],
|
||||
),
|
||||
) {
|
||||
return Err(Error::FailedAuthentication);
|
||||
// Let application filter incoming connection attempt by whatever criteria it wants.
|
||||
if !check_allow_incoming_session() {
|
||||
return Ok(ReceiveResult::Rejected);
|
||||
}
|
||||
|
||||
// Decrypt encrypted part of payload.
|
||||
aes_ctr_crypt_one_time_use_key(
|
||||
kbkdf::<AES_256_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION>(noise_es.as_bytes()).as_bytes(),
|
||||
&mut pkt_assembled[AliceNoiseXKInit::ENC_START..AliceNoiseXKInit::AUTH_START],
|
||||
);
|
||||
|
||||
let pkt: &AliceNoiseXKInit = byte_array_as_proto_buffer(pkt_assembled)?;
|
||||
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
|
||||
// a Kyber ciphertext to send back to Alice.
|
||||
let bob_noise_e_secret = P384KeyPair::generate();
|
||||
bob_noise_e = bob_noise_e_secret.public_key_bytes().clone();
|
||||
noise_es_ee = Secret(hmac_sha512(
|
||||
noise_es.as_bytes(),
|
||||
bob_noise_e_secret
|
||||
.agree(&alice_noise_e)
|
||||
.ok_or(Error::FailedAuthentication)?
|
||||
.as_bytes(),
|
||||
));
|
||||
let (hk_ct, hk) = pqc_kyber::encapsulate(&pkt.alice_hk_public, &mut random::SecureRandom::default())
|
||||
.map_err(|_| Error::FailedAuthentication)
|
||||
.map(|(ct, hk)| (ct, Secret(hk)))?;
|
||||
bob_hk_ciphertext = hk_ct;
|
||||
|
||||
let mut sessions = self.sessions.write().unwrap();
|
||||
|
||||
loop {
|
||||
bob_session_id = SessionId::random();
|
||||
if !sessions.active.contains_key(&bob_session_id) && !sessions.incoming.contains_key(&bob_session_id) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Let application filter incoming connection attempt by whatever criteria it wants.
|
||||
if !check_allow_incoming_session() {
|
||||
return Ok(ReceiveResult::Rejected);
|
||||
}
|
||||
|
||||
// Decrypt encrypted part of payload.
|
||||
let mut ctr =
|
||||
AesCtr::new(kbkdf::<AES_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION>(noise_es.as_bytes()).as_bytes());
|
||||
ctr.reset_set_iv(&SHA384::hash(&pkt.alice_noise_e)[..AES_CTR_NONCE_SIZE]);
|
||||
ctr.crypt_in_place(&mut pkt_assembled[AliceNoiseXKInit::ENC_START..AliceNoiseXKInit::AUTH_START]);
|
||||
|
||||
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)?;
|
||||
|
||||
// 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.
|
||||
let bob_noise_e_secret = P384KeyPair::generate();
|
||||
let bob_noise_e = bob_noise_e_secret.public_key_bytes().clone();
|
||||
let noise_es_ee = Secret(hmac_sha512(
|
||||
noise_es.as_bytes(),
|
||||
bob_noise_e_secret
|
||||
.agree(&alice_noise_e)
|
||||
.ok_or(Error::FailedAuthentication)?
|
||||
.as_bytes(),
|
||||
));
|
||||
let (bob_hk_ciphertext, hk) =
|
||||
pqc_kyber::encapsulate(&pkt.alice_hk_public, &mut random::SecureRandom::default())
|
||||
.map_err(|_| Error::FailedAuthentication)
|
||||
.map(|(ct, hk)| (ct, Secret(hk)))?;
|
||||
|
||||
let mut sessions = self.sessions.write().unwrap();
|
||||
|
||||
let mut bob_session_id;
|
||||
loop {
|
||||
bob_session_id = SessionId::random();
|
||||
if !sessions.active.contains_key(&bob_session_id) && !sessions.incomplete.contains_key(&bob_session_id) {
|
||||
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
|
||||
// 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.
|
||||
let mut newest = i64::MIN;
|
||||
let mut replace_id = None;
|
||||
let cutoff_time = current_time - Application::INCOMING_SESSION_NEGOTIATION_TIMEOUT_MS;
|
||||
for (id, s) in sessions.incoming.iter() {
|
||||
if s.timestamp <= cutoff_time {
|
||||
replace_id = Some(*id);
|
||||
break;
|
||||
} else if s.timestamp >= newest {
|
||||
newest = s.timestamp;
|
||||
replace_id = Some(*id);
|
||||
}
|
||||
}
|
||||
let _ = sessions.incoming.remove(replace_id.as_ref().unwrap());
|
||||
}
|
||||
|
||||
if sessions.incomplete.len() >= self.max_incomplete_session_queue_size {
|
||||
// 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
|
||||
// entry. If we find one that is actually timed out, that one is replaced instead.
|
||||
let mut newest = i64::MIN;
|
||||
let mut replace_id = None;
|
||||
let cutoff_time = current_time - Application::INCOMING_SESSION_NEGOTIATION_TIMEOUT_MS;
|
||||
for (id, s) in sessions.incomplete.iter() {
|
||||
if s.timestamp <= cutoff_time {
|
||||
replace_id = Some(*id);
|
||||
break;
|
||||
} else if s.timestamp >= newest {
|
||||
newest = s.timestamp;
|
||||
replace_id = Some(*id);
|
||||
}
|
||||
}
|
||||
let _ = sessions.incomplete.remove(replace_id.as_ref().unwrap());
|
||||
}
|
||||
|
||||
// Reserve session ID on this side and record incomplete session state.
|
||||
sessions.incomplete.insert(
|
||||
bob_session_id,
|
||||
Arc::new(IncompleteIncomingSession {
|
||||
timestamp: current_time,
|
||||
request_hash,
|
||||
alice_session_id,
|
||||
bob_session_id,
|
||||
noise_es_ee: noise_es_ee.clone(),
|
||||
bob_hk_ciphertext,
|
||||
hk,
|
||||
bob_noise_e_secret,
|
||||
header_protection_key: Secret(pkt.header_protection_key),
|
||||
}),
|
||||
);
|
||||
|
||||
(
|
||||
// Reserve session ID on this side and record incomplete session state.
|
||||
sessions.incoming.insert(
|
||||
bob_session_id,
|
||||
Arc::new(IncomingIncompleteSession {
|
||||
timestamp: current_time,
|
||||
request_hash,
|
||||
alice_session_id,
|
||||
bob_session_id,
|
||||
noise_es_ee,
|
||||
noise_es_ee: noise_es_ee.clone(),
|
||||
bob_hk_ciphertext,
|
||||
Secret(pkt.header_protection_key),
|
||||
bob_noise_e,
|
||||
)
|
||||
};
|
||||
hk,
|
||||
bob_noise_e_secret,
|
||||
header_protection_key: Secret(pkt.header_protection_key),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// Create Bob's ephemeral counter-offer reply.
|
||||
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_hk_ciphertext = bob_hk_ciphertext;
|
||||
|
||||
// Encrypt main section of reply. Technically we could get away without this but why not?
|
||||
let mut ctr =
|
||||
AesCtr::new(kbkdf::<AES_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION>(noise_es_ee.as_bytes()).as_bytes());
|
||||
ctr.reset_set_iv(&bob_noise_e[P384_PUBLIC_KEY_SIZE - AES_CTR_NONCE_SIZE..]);
|
||||
ctr.crypt_in_place(&mut ack_packet[BobNoiseXKAck::ENC_START..BobNoiseXKAck::AUTH_START]);
|
||||
// Encrypt main section of reply.
|
||||
aes_ctr_crypt_one_time_use_key(
|
||||
kbkdf::<AES_256_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION>(noise_es_ee.as_bytes()).as_bytes(),
|
||||
&mut ack_packet[BobNoiseXKAck::ENC_START..BobNoiseXKAck::AUTH_START],
|
||||
);
|
||||
|
||||
// Add HMAC-SHA384 to reply packet.
|
||||
let reply_hmac = hmac_sha384_2(
|
||||
|
@ -818,7 +809,7 @@ impl<Application: ApplicationLayer> Context<Application> {
|
|||
* the negotiation.
|
||||
*/
|
||||
|
||||
if incoming_counter != 1 || incomplete.is_some() {
|
||||
if incoming_counter != 1 || incoming.is_some() {
|
||||
return Err(Error::OutOfSequence);
|
||||
}
|
||||
|
||||
|
@ -827,40 +818,40 @@ impl<Application: ApplicationLayer> Context<Application> {
|
|||
if let Offer::NoiseXKInit(outgoing_offer) = &state.current_offer {
|
||||
let pkt: &BobNoiseXKAck = byte_array_as_proto_buffer(pkt_assembled)?;
|
||||
|
||||
// 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 noise_es_ee = Secret(hmac_sha512(
|
||||
outgoing_offer.noise_es.as_bytes(),
|
||||
outgoing_offer
|
||||
.alice_noise_e_secret
|
||||
.agree(&bob_noise_e)
|
||||
.ok_or(Error::FailedAuthentication)?
|
||||
.as_bytes(),
|
||||
));
|
||||
|
||||
let noise_es_ee_kex_hmac_key =
|
||||
kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION>(noise_es_ee.as_bytes());
|
||||
|
||||
// Authenticate Bob's reply and the validity of bob_noise_e.
|
||||
if !secure_eq(
|
||||
&pkt.hmac_es_ee,
|
||||
&hmac_sha384_2(
|
||||
noise_es_ee_kex_hmac_key.as_bytes(),
|
||||
&incoming_message_nonce,
|
||||
&pkt_assembled[HEADER_SIZE..BobNoiseXKAck::AUTH_START],
|
||||
),
|
||||
) {
|
||||
return Err(Error::FailedAuthentication);
|
||||
}
|
||||
|
||||
// Decrypt encrypted portion of message.
|
||||
aes_ctr_crypt_one_time_use_key(
|
||||
kbkdf::<AES_256_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION>(noise_es_ee.as_bytes()).as_bytes(),
|
||||
&mut pkt_assembled[BobNoiseXKAck::ENC_START..BobNoiseXKAck::AUTH_START],
|
||||
);
|
||||
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.
|
||||
let bob_noise_e = P384PublicKey::from_bytes(&pkt.bob_noise_e).ok_or(Error::FailedAuthentication)?;
|
||||
let noise_es_ee = Secret(hmac_sha512(
|
||||
outgoing_offer.noise_es.as_bytes(),
|
||||
outgoing_offer
|
||||
.alice_noise_e_secret
|
||||
.agree(&bob_noise_e)
|
||||
.ok_or(Error::FailedAuthentication)?
|
||||
.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 =
|
||||
kbkdf::<HMAC_SHA384_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_AUTHENTICATION>(noise_es_ee.as_bytes());
|
||||
|
||||
// Authenticate Bob's reply and the validity of bob_noise_e.
|
||||
if !secure_eq(
|
||||
&pkt.hmac_es_ee,
|
||||
&hmac_sha384_2(
|
||||
noise_es_ee_kex_hmac_key.as_bytes(),
|
||||
&incoming_message_nonce,
|
||||
&pkt_assembled[HEADER_SIZE..BobNoiseXKAck::AUTH_START],
|
||||
),
|
||||
) {
|
||||
return Err(Error::FailedAuthentication);
|
||||
}
|
||||
|
||||
// Decrypt encrypted portion of message.
|
||||
let mut ctr = AesCtr::new(noise_es_ee_kex_enc_key.as_bytes());
|
||||
ctr.reset_set_iv(&SHA384::hash(&pkt.bob_noise_e)[..AES_CTR_NONCE_SIZE]);
|
||||
ctr.crypt_in_place(&mut pkt_assembled[BobNoiseXKAck::ENC_START..BobNoiseXKAck::AUTH_START]);
|
||||
let pkt: &BobNoiseXKAck = byte_array_as_proto_buffer(pkt_assembled)?;
|
||||
|
||||
// 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
|
||||
// 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
|
||||
}
|
||||
|
||||
// Encrypt Alice's static identity and other inner payload items. The IV here
|
||||
// is a hash of 'hk' making it actually a secret and "borrowing" a little PQ
|
||||
// forward secrecy for Alice's identity.
|
||||
let mut ctr = AesCtr::new(noise_es_ee_kex_enc_key.as_bytes());
|
||||
ctr.reset_set_iv(&hk.as_bytes()[..AES_CTR_NONCE_SIZE]);
|
||||
ctr.crypt_in_place(&mut reply_buffer[HEADER_SIZE + 1..reply_len]);
|
||||
// Encrypt Alice's static identity and other inner payload items. The key used here is
|
||||
// mixed with 'hk' to make identity secrecy PQ forward secure.
|
||||
aes_ctr_crypt_one_time_use_key(
|
||||
&hmac_sha512(noise_es_ee.as_bytes(), hk.as_bytes())[..AES_256_KEY_SIZE],
|
||||
&mut reply_buffer[HEADER_SIZE + 1..reply_len],
|
||||
);
|
||||
|
||||
// First attach HMAC allowing Bob to verify that this is from the same Alice and to
|
||||
// verify the authenticity of encrypted data.
|
||||
|
@ -983,9 +974,9 @@ impl<Application: ApplicationLayer> Context<Application> {
|
|||
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.
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -995,7 +986,7 @@ impl<Application: ApplicationLayer> Context<Application> {
|
|||
if !secure_eq(
|
||||
&pkt_assembled[auth_start..pkt_assembled.len() - HMAC_SHA384_SIZE],
|
||||
&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(),
|
||||
&incoming_message_nonce,
|
||||
&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);
|
||||
|
||||
// Decrypt encrypted section so we can finally learn Alice's static identity.
|
||||
let mut ctr = AesCtr::new(
|
||||
kbkdf::<AES_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_KEX_ENCRYPTION>(incomplete.noise_es_ee.as_bytes()).as_bytes(),
|
||||
aes_ctr_crypt_one_time_use_key(
|
||||
&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.
|
||||
let mut pkt_assembled_ptr = HEADER_SIZE + 1;
|
||||
|
@ -1048,7 +1038,7 @@ impl<Application: ApplicationLayer> Context<Application> {
|
|||
// her static public blob.
|
||||
let check_result = check_accept_session(alice_static_public_blob, alice_meta_data);
|
||||
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);
|
||||
}
|
||||
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.
|
||||
let noise_es_ee_se_hk_psk = Secret(hmac_sha512(
|
||||
&hmac_sha512(
|
||||
incomplete.noise_es_ee.as_bytes(),
|
||||
incomplete
|
||||
incoming.noise_es_ee.as_bytes(),
|
||||
incoming
|
||||
.bob_noise_e_secret
|
||||
.agree(&alice_noise_s)
|
||||
.ok_or(Error::FailedAuthentication)?
|
||||
.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.
|
||||
|
@ -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())
|
||||
.as_bytes(),
|
||||
&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);
|
||||
}
|
||||
|
||||
let session = Arc::new(Session {
|
||||
id: incomplete.bob_session_id,
|
||||
id: incoming.bob_session_id,
|
||||
application_data,
|
||||
psk,
|
||||
send_counter: AtomicU64::new(2), // 1 was already used during negotiation
|
||||
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 {
|
||||
remote_session_id: Some(incomplete.alice_session_id),
|
||||
remote_session_id: Some(incoming.alice_session_id),
|
||||
keys: [
|
||||
Some(SessionKey::new::<Application>(noise_es_ee_se_hk_psk, current_time, 2, true)),
|
||||
None,
|
||||
|
@ -1101,8 +1091,8 @@ impl<Application: ApplicationLayer> Context<Application> {
|
|||
// Promote this from an incomplete session to an established session.
|
||||
{
|
||||
let mut sessions = self.sessions.write().unwrap();
|
||||
sessions.incomplete.remove(&incomplete.bob_session_id);
|
||||
sessions.active.insert(incomplete.bob_session_id, Arc::downgrade(&session));
|
||||
sessions.incoming.remove(&incoming.bob_session_id);
|
||||
sessions.active.insert(incoming.bob_session_id, Arc::downgrade(&session));
|
||||
}
|
||||
|
||||
return Ok(ReceiveResult::OkNewSession(session));
|
||||
|
@ -1115,7 +1105,7 @@ impl<Application: ApplicationLayer> Context<Application> {
|
|||
if pkt_assembled.len() != AliceRekeyInit::SIZE {
|
||||
return Err(Error::InvalidPacket);
|
||||
}
|
||||
if incomplete.is_some() {
|
||||
if incoming.is_some() {
|
||||
return Err(Error::OutOfSequence);
|
||||
}
|
||||
|
||||
|
@ -1178,7 +1168,7 @@ impl<Application: ApplicationLayer> Context<Application> {
|
|||
if pkt_assembled.len() != BobRekeyAck::SIZE {
|
||||
return Err(Error::InvalidPacket);
|
||||
}
|
||||
if incomplete.is_some() {
|
||||
if incoming.is_some() {
|
||||
return Err(Error::OutOfSequence);
|
||||
}
|
||||
|
||||
|
@ -1480,8 +1470,8 @@ fn assemble_fragments_into<A: ApplicationLayer>(fragments: &[A::IncomingPacketBu
|
|||
|
||||
impl SessionKey {
|
||||
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 b2a = kbkdf::<AES_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE>(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_256_KEY_SIZE, KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE>(key.as_bytes());
|
||||
let (receive_key, send_key) = if role_is_bob {
|
||||
(a2b, b2a)
|
||||
} else {
|
||||
|
@ -1549,6 +1539,16 @@ fn hmac_sha384_2(key: &[u8], a: &[u8], b: &[u8]) -> [u8; 48] {
|
|||
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)
|
||||
/// 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> {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue