From 757cc88abc7436da050c782c6ac8c38f7f0ca3ff Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Tue, 7 Mar 2023 13:03:48 -0500 Subject: [PATCH] Make ZSSP use just GCM to simplify, and change final ACK to auth and encrypt public blob separately from meta-data. --- zssp/src/main.rs | 8 +- zssp/src/proto.rs | 35 +++-- zssp/src/zssp.rs | 329 ++++++++++++++++++++-------------------------- 3 files changed, 165 insertions(+), 207 deletions(-) diff --git a/zssp/src/main.rs b/zssp/src/main.rs index f9db5a977..e6d11a2fc 100644 --- a/zssp/src/main.rs +++ b/zssp/src/main.rs @@ -80,7 +80,7 @@ fn alice_main( match context.receive( alice_app, || true, - |s_public, _| Some((P384PublicKey::from_bytes(s_public).unwrap(), Secret::default(), ())), + |s_public| Some((P384PublicKey::from_bytes(s_public).unwrap(), Secret::default(), ())), |_, b| { let _ = alice_out.send(b.to_vec()); }, @@ -96,7 +96,7 @@ fn alice_main( Ok(zssp::ReceiveResult::OkData(_, _)) => { //println!("[alice] received {}", data.len()); } - Ok(zssp::ReceiveResult::OkNewSession(s)) => { + Ok(zssp::ReceiveResult::OkNewSession(s, _)) => { println!("[alice] new session {}", s.id.to_string()); } Ok(zssp::ReceiveResult::Rejected) => {} @@ -178,7 +178,7 @@ fn bob_main( match context.receive( bob_app, || true, - |s_public, _| Some((P384PublicKey::from_bytes(s_public).unwrap(), Secret::default(), ())), + |s_public| Some((P384PublicKey::from_bytes(s_public).unwrap(), Secret::default(), ())), |_, b| { let _ = bob_out.send(b.to_vec()); }, @@ -204,7 +204,7 @@ fn bob_main( .is_ok()); transferred += data.len() as u64 * 2; // *2 because we are also sending this many bytes back } - Ok(zssp::ReceiveResult::OkNewSession(s)) => { + Ok(zssp::ReceiveResult::OkNewSession(s, _)) => { println!("[bob] new session {}", s.id.to_string()); let _ = bob_session.replace(s); } diff --git a/zssp/src/proto.rs b/zssp/src/proto.rs index ec8ce9991..3aed80907 100644 --- a/zssp/src/proto.rs +++ b/zssp/src/proto.rs @@ -9,7 +9,7 @@ use std::mem::size_of; use pqc_kyber::{KYBER_CIPHERTEXTBYTES, KYBER_PUBLICKEYBYTES}; -use zerotier_crypto::hash::{HMAC_SHA384_SIZE, SHA384_HASH_SIZE}; +use zerotier_crypto::hash::SHA384_HASH_SIZE; use zerotier_crypto::p384::P384_PUBLIC_KEY_SIZE; use crate::error::Error; @@ -45,8 +45,7 @@ pub(crate) const HEADER_SIZE: usize = 16; pub(crate) const HEADER_PROTECT_ENCRYPT_START: usize = 6; pub(crate) const HEADER_PROTECT_ENCRYPT_END: usize = 22; -pub(crate) const KBKDF_KEY_USAGE_LABEL_INIT_ENCRYPTION: u8 = b'x'; // AES-CTR encryption during initial setup -pub(crate) const KBKDF_KEY_USAGE_LABEL_INIT_AUTHENTICATION: u8 = b'X'; // HMAC-SHA384 during initial setup +pub(crate) const KBKDF_KEY_USAGE_LABEL_INIT_ENCRYPTION: u8 = b'x'; // AES-GCM encryption during initial setup pub(crate) const KBKDF_KEY_USAGE_LABEL_AES_GCM_ALICE_TO_BOB: u8 = b'A'; // AES-GCM in A->B direction pub(crate) const KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE: u8 = b'B'; // AES-GCM in B->A direction pub(crate) const KBKDF_KEY_USAGE_LABEL_RATCHET: u8 = b'R'; // Key used in derivatin of next session key @@ -71,18 +70,18 @@ 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 + // -- start AES-GCM(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], // -- end encrypted section - pub hmac_es: [u8; HMAC_SHA384_SIZE], + pub gcm_tag: [u8; AES_GCM_TAG_SIZE], } impl AliceNoiseXKInit { pub const ENC_START: usize = HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE; pub const AUTH_START: usize = Self::ENC_START + SessionId::SIZE + KYBER_PUBLICKEYBYTES + AES_HEADER_PROTECTION_KEY_SIZE; - pub const SIZE: usize = Self::AUTH_START + HMAC_SHA384_SIZE; + pub const SIZE: usize = Self::AUTH_START + AES_GCM_TAG_SIZE; } /// The response to AliceNoiceXKInit containing Bob's ephemeral keys. @@ -92,17 +91,17 @@ 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 + // -- start AES-GCM(es_ee) encrypted section pub bob_session_id: [u8; SessionId::SIZE], pub bob_hk_ciphertext: [u8; KYBER_CIPHERTEXTBYTES], // -- end encrypted sectiion - pub hmac_es_ee: [u8; HMAC_SHA384_SIZE], + pub gcm_tag: [u8; AES_GCM_TAG_SIZE], } impl BobNoiseXKAck { pub const ENC_START: usize = HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE; pub const AUTH_START: usize = Self::ENC_START + SessionId::SIZE + KYBER_CIPHERTEXTBYTES; - pub const SIZE: usize = Self::AUTH_START + HMAC_SHA384_SIZE; + pub const SIZE: usize = Self::AUTH_START + AES_GCM_TAG_SIZE; } /// Alice's final response containing her identity (she already knows Bob's) and meta-data. @@ -112,20 +111,20 @@ impl BobNoiseXKAck { pub(crate) struct AliceNoiseXKAck { pub header: [u8; HEADER_SIZE], pub session_protocol_version: u8, - // -- start AES-CTR(es_ee_hk) encrypted section pub alice_static_blob_length: [u8; 2], + // -- start AES-GCM(es_ee_hk) encrypted section pub alice_static_blob: [u8; ???], + // -- end encrypted section + pub gcm_tag_0: [u8; AES_GCM_TAG_SIZE], pub alice_metadata_length: [u8; 2], + // -- start AES-GCM(es_ee_se_hk_psk) encrypted section pub alice_metadata: [u8; ???], // -- end encrypted section - pub hmac_es_ee: [u8; HMAC_SHA384_SIZE], - pub hmac_es_ee_se_hk_psk: [u8; HMAC_SHA384_SIZE], + pub gcm_tag_1: [u8; AES_GCM_TAG_SIZE], } */ -pub(crate) const ALICE_NOISE_XK_ACK_ENC_START: usize = HEADER_SIZE + 1; -pub(crate) const ALICE_NOISE_XK_ACK_AUTH_SIZE: usize = HMAC_SHA384_SIZE + HMAC_SHA384_SIZE; -pub(crate) const ALICE_NOISE_XK_ACK_MIN_SIZE: usize = ALICE_NOISE_XK_ACK_ENC_START + 2 + 2 + ALICE_NOISE_XK_ACK_AUTH_SIZE; +pub(crate) const ALICE_NOISE_XK_ACK_MIN_SIZE: usize = HEADER_SIZE + 1 + 2 + AES_GCM_TAG_SIZE + 2 + AES_GCM_TAG_SIZE; #[allow(unused)] #[repr(C, packed)] @@ -135,7 +134,7 @@ pub(crate) struct RekeyInit { // -- start AES-GCM encrypted portion (using current key) pub alice_e: [u8; P384_PUBLIC_KEY_SIZE], // -- end AES-GCM encrypted portion - pub gcm_mac: [u8; AES_GCM_TAG_SIZE], + pub gcm_tag: [u8; AES_GCM_TAG_SIZE], } impl RekeyInit { @@ -151,9 +150,9 @@ pub(crate) struct RekeyAck { pub session_protocol_version: u8, // -- start AES-GCM encrypted portion (using current key) pub bob_e: [u8; P384_PUBLIC_KEY_SIZE], - pub next_key_fingerprint: [u8; SHA384_HASH_SIZE], + pub next_key_fingerprint: [u8; SHA384_HASH_SIZE], // SHA384(next secret) // -- end AES-GCM encrypted portion - pub gcm_mac: [u8; AES_GCM_TAG_SIZE], + pub gcm_tag: [u8; AES_GCM_TAG_SIZE], } impl RekeyAck { diff --git a/zssp/src/zssp.rs b/zssp/src/zssp.rs index a8a8f76b0..b2d73a7f1 100644 --- a/zssp/src/zssp.rs +++ b/zssp/src/zssp.rs @@ -10,12 +10,13 @@ // FIPS compliant Noise_XK with Jedi powers (Kyber1024) and built-in attack-resistant large payload (fragmentation) support. use std::collections::{HashMap, HashSet}; +use std::io::Write; use std::num::NonZeroU64; use std::sync::atomic::{AtomicI64, AtomicU64, Ordering}; use std::sync::{Arc, Mutex, RwLock, Weak}; -use zerotier_crypto::aes::{Aes, AesCtr, AesGcm}; -use zerotier_crypto::hash::{hmac_sha512, HMACSHA384, HMAC_SHA384_SIZE, SHA384}; +use zerotier_crypto::aes::{Aes, AesGcm}; +use zerotier_crypto::hash::{hmac_sha512, SHA384}; use zerotier_crypto::p384::{P384KeyPair, P384PublicKey, P384_ECDH_SHARED_SECRET_SIZE}; use zerotier_crypto::secret::Secret; use zerotier_crypto::{random, secure_eq}; @@ -63,8 +64,8 @@ pub enum ReceiveResult<'b, Application: ApplicationLayer> { /// Packet was valid and a data payload was decoded and authenticated. OkData(Arc>, &'b mut [u8]), - /// Packet was valid and a new session was created. - OkNewSession(Arc>), + /// Packet was valid and a new session was created, with optional attached meta-data. + OkNewSession(Arc>, Option<&'b mut [u8]>), /// Packet appears valid but was rejected by the application layer, e.g. a rejected new session attempt. Rejected, @@ -339,24 +340,22 @@ impl Context { panic!(); // should be impossible }; - let init: &mut AliceNoiseXKInit = byte_array_as_proto_buffer_mut(init_packet).unwrap(); - init.session_protocol_version = SESSION_PROTOCOL_VERSION; - init.alice_noise_e = alice_noise_e; - init.alice_session_id = *local_session_id.as_bytes(); - init.alice_hk_public = alice_hk_secret.public; - init.header_protection_key = header_protection_key.0; + { + let init: &mut AliceNoiseXKInit = byte_array_as_proto_buffer_mut(init_packet).unwrap(); + init.session_protocol_version = SESSION_PROTOCOL_VERSION; + init.alice_noise_e = alice_noise_e; + init.alice_session_id = *local_session_id.as_bytes(); + init.alice_hk_public = alice_hk_secret.public; + init.header_protection_key = header_protection_key.0; + } - aes_ctr_crypt_one_time_use_key( + let mut gcm = AesGcm::new( kbkdf::(noise_es.as_bytes()).as_bytes(), - &mut init_packet[AliceNoiseXKInit::ENC_START..AliceNoiseXKInit::AUTH_START], + true, ); - - let hmac = hmac_sha384_2( - kbkdf::(noise_es.as_bytes()).as_bytes(), - &create_message_nonce(PACKET_TYPE_ALICE_NOISE_XK_INIT, 1), - &init_packet[HEADER_SIZE..AliceNoiseXKInit::AUTH_START], - ); - init_packet[AliceNoiseXKInit::AUTH_START..AliceNoiseXKInit::AUTH_START + HMAC_SHA384_SIZE].copy_from_slice(&hmac); + gcm.reset_init_gcm(&create_message_nonce(PACKET_TYPE_ALICE_NOISE_XK_INIT, 1)); + gcm.crypt_in_place(&mut init_packet[AliceNoiseXKInit::ENC_START..AliceNoiseXKInit::AUTH_START]); + init_packet[AliceNoiseXKInit::AUTH_START..AliceNoiseXKInit::AUTH_START + AES_GCM_TAG_SIZE].copy_from_slice(&gcm.finish_encrypt()); send_with_fragmentation( &mut send, @@ -385,9 +384,9 @@ impl Context { /// with negotiation. False drops the packet. /// /// The check_accept_session function is called at the end of negotiation for an incoming session - /// with the caller's static public blob and meta-data if any. It must return the P-384 static public - /// key extracted from the supplied blob, a PSK (or all zeroes if none), and application data to - /// associate with the new session. A return of None abandons the session. + /// with the caller's static public blob. It must return the P-384 static public key extracted from + /// the supplied blob, a PSK (or all zeroes if none), and application data to associate with the new + /// session. A return of None rejects and abandons the session. /// /// Note that if check_accept_session accepts and returns Some() the session could still fail with /// receive() returning an error. A Some() return from check_accept_sesion doesn't guarantee @@ -409,7 +408,7 @@ impl Context { 'b, SendFunction: FnMut(Option<&Arc>>, &mut [u8]), CheckAllowIncomingSession: FnMut() -> bool, - CheckAcceptSession: FnMut(&[u8], Option<&[u8]>) -> Option<(P384PublicKey, Secret<64>, Application::Data)>, + CheckAcceptSession: FnMut(&[u8]) -> Option<(P384PublicKey, Secret<64>, Application::Data)>, >( &self, app: &Application, @@ -568,7 +567,7 @@ impl Context { 'b, SendFunction: FnMut(Option<&Arc>>, &mut [u8]), CheckAllowIncomingSession: FnMut() -> bool, - CheckAcceptSession: FnMut(&[u8], Option<&[u8]>) -> Option<(P384PublicKey, Secret<64>, Application::Data)>, + CheckAcceptSession: FnMut(&[u8]) -> Option<(P384PublicKey, Secret<64>, Application::Data)>, >( &self, app: &Application, @@ -716,15 +715,14 @@ impl Context { 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::(noise_es.as_bytes()).as_bytes(), - &incoming_message_nonce, - &pkt_assembled[HEADER_SIZE..AliceNoiseXKInit::AUTH_START], - ), - ) { + // Decrypt and authenticate init packet, also proving that caller knows our static identity. + let mut gcm = AesGcm::new( + kbkdf::(noise_es.as_bytes()).as_bytes(), + false, + ); + gcm.reset_init_gcm(&incoming_message_nonce); + gcm.crypt_in_place(&mut pkt_assembled[AliceNoiseXKInit::ENC_START..AliceNoiseXKInit::AUTH_START]); + if !gcm.finish_decrypt(&pkt_assembled[AliceNoiseXKInit::AUTH_START..AliceNoiseXKInit::AUTH_START + AES_GCM_TAG_SIZE]) { return Err(Error::FailedAuthentication); } @@ -733,12 +731,6 @@ impl Context { return Ok(ReceiveResult::Rejected); } - // Decrypt encrypted part of payload. - aes_ctr_crypt_one_time_use_key( - kbkdf::(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)?; let alice_session_id = SessionId::new_from_bytes(&pkt.alice_session_id).ok_or(Error::InvalidPacket)?; let header_protection_key = Secret(pkt.header_protection_key); @@ -809,19 +801,14 @@ impl Context { ack.bob_session_id = *bob_session_id.as_bytes(); ack.bob_hk_ciphertext = bob_hk_ciphertext; - // Encrypt main section of reply. - aes_ctr_crypt_one_time_use_key( + // Encrypt main section of reply and attach tag. + let mut gcm = AesGcm::new( kbkdf::(noise_es_ee.as_bytes()).as_bytes(), - &mut ack_packet[BobNoiseXKAck::ENC_START..BobNoiseXKAck::AUTH_START], + true, ); - - // Add HMAC-SHA384 to reply packet. - let reply_hmac = hmac_sha384_2( - kbkdf::(noise_es_ee.as_bytes()).as_bytes(), - &create_message_nonce(PACKET_TYPE_BOB_NOISE_XK_ACK, 1), - &ack_packet[HEADER_SIZE..BobNoiseXKAck::AUTH_START], - ); - ack_packet[BobNoiseXKAck::AUTH_START..].copy_from_slice(&reply_hmac); + gcm.reset_init_gcm(&create_message_nonce(PACKET_TYPE_BOB_NOISE_XK_ACK, 1)); + gcm.crypt_in_place(&mut ack_packet[BobNoiseXKAck::ENC_START..BobNoiseXKAck::AUTH_START]); + ack_packet[BobNoiseXKAck::AUTH_START..BobNoiseXKAck::AUTH_START + AES_GCM_TAG_SIZE].copy_from_slice(&gcm.finish_encrypt()); send_with_fragmentation( |b| send(None, b), @@ -875,26 +862,17 @@ impl Context { .as_bytes(), )); - let noise_es_ee_kex_hmac_key = - kbkdf::(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], - ), - ) { + // Decrypt and authenticate Bob's reply. + let mut gcm = AesGcm::new( + kbkdf::(noise_es_ee.as_bytes()).as_bytes(), + false, + ); + gcm.reset_init_gcm(&incoming_message_nonce); + gcm.crypt_in_place(&mut pkt_assembled[BobNoiseXKAck::ENC_START..BobNoiseXKAck::AUTH_START]); + if !gcm.finish_decrypt(&pkt_assembled[BobNoiseXKAck::AUTH_START..BobNoiseXKAck::AUTH_START + AES_GCM_TAG_SIZE]) { return Err(Error::FailedAuthentication); } - // Decrypt encrypted portion of message. - aes_ctr_crypt_one_time_use_key( - kbkdf::(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) { @@ -922,51 +900,47 @@ impl Context { // up forward secrecy. Also return Bob's opaque note. let mut reply_buffer = [0u8; MAX_NOISE_HANDSHAKE_SIZE]; reply_buffer[HEADER_SIZE] = SESSION_PROTOCOL_VERSION; - let mut reply_len = HEADER_SIZE + 1; - let mut reply_buffer_append = |b: &[u8]| { - let reply_len_new = reply_len + b.len(); - debug_assert!(reply_len_new <= MAX_NOISE_HANDSHAKE_SIZE); - reply_buffer[reply_len..reply_len_new].copy_from_slice(b); - reply_len = reply_len_new; - }; + let mut rw = &mut reply_buffer[HEADER_SIZE + 1..]; + let alice_s_public_blob = app.get_local_s_public_blob(); assert!(alice_s_public_blob.len() <= (u16::MAX as usize)); - reply_buffer_append(&(alice_s_public_blob.len() as u16).to_le_bytes()); - reply_buffer_append(alice_s_public_blob); + rw.write_all(&(alice_s_public_blob.len() as u16).to_le_bytes())?; + let mut enc_start = MAX_NOISE_HANDSHAKE_SIZE - rw.len(); + rw.write_all(alice_s_public_blob)?; + + let mut reply_len = MAX_NOISE_HANDSHAKE_SIZE - rw.len(); + let mut gcm = AesGcm::new( + kbkdf::(&hmac_sha512( + noise_es_ee.as_bytes(), + hk.as_bytes(), + )) + .as_bytes(), + true, + ); + gcm.reset_init_gcm(&reply_message_nonce); + gcm.crypt_in_place(&mut reply_buffer[enc_start..reply_len]); + let mut rw = &mut reply_buffer[reply_len..]; + rw.write_all(&gcm.finish_encrypt())?; + if let Some(md) = outgoing_offer.metadata.as_ref() { - reply_buffer_append(&(md.len() as u16).to_le_bytes()); - reply_buffer_append(md.as_ref()); + assert!(md.len() <= (u16::MAX as usize)); + rw.write_all(&(md.len() as u16).to_le_bytes())?; + enc_start = MAX_NOISE_HANDSHAKE_SIZE - rw.len(); + rw.write_all(md.as_ref())?; } else { - reply_buffer_append(&[0u8, 0u8]); // no meta-data + rw.write_all(&[0u8, 0u8])?; // no meta-data + enc_start = MAX_NOISE_HANDSHAKE_SIZE - rw.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], + reply_len = MAX_NOISE_HANDSHAKE_SIZE - rw.len(); + let mut gcm = AesGcm::new( + kbkdf::(noise_es_ee_se_hk_psk.as_bytes()).as_bytes(), + true, ); - - // First attach HMAC allowing Bob to verify that this is from the same Alice and to - // verify the authenticity of encrypted data. - let hmac_es_ee = hmac_sha384_2( - noise_es_ee_kex_hmac_key.as_bytes(), - &reply_message_nonce, - &reply_buffer[HEADER_SIZE..reply_len], - ); - reply_buffer[reply_len..reply_len + HMAC_SHA384_SIZE].copy_from_slice(&hmac_es_ee); - reply_len += HMAC_SHA384_SIZE; - - // Then attach the final HMAC permitting Bob to verify the authenticity of the whole - // key exchange. Bob won't be able to do this until he decrypts and parses Alice's - // identity, so the first HMAC is to let him authenticate that first. - let hmac_es_ee_se_hk_psk = hmac_sha384_2( - kbkdf::(noise_es_ee_se_hk_psk.as_bytes()).as_bytes(), - &reply_message_nonce, - &reply_buffer[HEADER_SIZE..reply_len], - ); - reply_buffer[reply_len..reply_len + HMAC_SHA384_SIZE].copy_from_slice(&hmac_es_ee_se_hk_psk); - reply_len += HMAC_SHA384_SIZE; + gcm.reset_init_gcm(&reply_message_nonce); + gcm.crypt_in_place(&mut reply_buffer[enc_start..reply_len]); + reply_buffer[reply_len..reply_len + AES_GCM_TAG_SIZE].copy_from_slice(&gcm.finish_encrypt()); + reply_len += AES_GCM_TAG_SIZE; drop(state); { @@ -1025,62 +999,20 @@ impl Context { } if let Some(incoming) = incoming { - // Check the first HMAC to verify against the currently known noise_es_ee key, which verifies - // that this reply is part of this session. - let auth_start = pkt_assembled.len() - ALICE_NOISE_XK_ACK_AUTH_SIZE; - if !secure_eq( - &pkt_assembled[auth_start..pkt_assembled.len() - HMAC_SHA384_SIZE], - &hmac_sha384_2( - kbkdf::(incoming.noise_es_ee.as_bytes()).as_bytes(), - &incoming_message_nonce, - &pkt_assembled[HEADER_SIZE..auth_start], - ), - ) { - return Err(Error::FailedAuthentication); - } + let mut r = PktReader(pkt_assembled, HEADER_SIZE + 1); - // Make a copy of pkt_assembled so we can check the second HMAC against original ciphertext later. - let mut pkt_assembly_buffer_copy = [0u8; MAX_NOISE_HANDSHAKE_SIZE]; - pkt_assembly_buffer_copy[..pkt_assembled.len()].copy_from_slice(pkt_assembled); + let alice_static_public_blob_size = r.read_u16()? as usize; + let alice_static_public_blob = r.read_decrypt_auth( + alice_static_public_blob_size, + kbkdf::(&hmac_sha512( + incoming.noise_es_ee.as_bytes(), + incoming.hk.as_bytes(), + )), + &incoming_message_nonce, + )?; - // Decrypt encrypted section so we can finally learn Alice's static identity. - 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], - ); - - // Read the static public blob and optional meta-data. - let mut pkt_assembled_ptr = HEADER_SIZE + 1; - let mut pkt_assembled_field_end = pkt_assembled_ptr + 2; - if pkt_assembled_field_end >= pkt_assembled.len() { - return Err(Error::InvalidPacket); - } - let alice_static_public_blob_size = - u16::from_le_bytes(pkt_assembled[pkt_assembled_ptr..pkt_assembled_field_end].try_into().unwrap()) as usize; - pkt_assembled_ptr = pkt_assembled_field_end; - pkt_assembled_field_end = pkt_assembled_ptr + alice_static_public_blob_size; - if pkt_assembled_field_end >= pkt_assembled.len() { - return Err(Error::InvalidPacket); - } - let alice_static_public_blob = &pkt_assembled[pkt_assembled_ptr..pkt_assembled_field_end]; - pkt_assembled_ptr = pkt_assembled_field_end; - pkt_assembled_field_end = pkt_assembled_ptr + 2; - if pkt_assembled_field_end >= pkt_assembled.len() { - return Err(Error::InvalidPacket); - } - let alice_meta_data_size = - u16::from_le_bytes(pkt_assembled[pkt_assembled_ptr..pkt_assembled_field_end].try_into().unwrap()) as usize; - pkt_assembled_ptr = pkt_assembled_field_end; - pkt_assembled_field_end = pkt_assembled_ptr + alice_meta_data_size; - let alice_meta_data = if alice_meta_data_size > 0 { - Some(&pkt_assembled[pkt_assembled_ptr..pkt_assembled_field_end]) - } else { - None - }; - - // Check session acceptance and fish Alice's NIST P-384 static public key out of - // her static public blob. - let check_result = check_accept_session(alice_static_public_blob, alice_meta_data); + // Check session acceptance and fish Alice's NIST P-384 static public key out of her static public blob. + let check_result = check_accept_session(alice_static_public_blob); if check_result.is_none() { self.sessions.write().unwrap().incoming.remove(&incoming.bob_session_id); return Ok(ReceiveResult::Rejected); @@ -1100,17 +1032,18 @@ impl Context { &hmac_sha512(psk.as_bytes(), incoming.hk.as_bytes()), )); - // Verify the packet using the final key to verify the whole key exchange. - if !secure_eq( - &pkt_assembly_buffer_copy[auth_start + HMAC_SHA384_SIZE..pkt_assembled.len()], - &hmac_sha384_2( - kbkdf::(noise_es_ee_se_hk_psk.as_bytes()).as_bytes(), - &incoming_message_nonce, - &pkt_assembly_buffer_copy[HEADER_SIZE..auth_start + HMAC_SHA384_SIZE], - ), - ) { - return Err(Error::FailedAuthentication); + // Decrypt meta-data and verify the final key in the process. Copy meta-data + // into the temporary data buffer to return. + let alice_meta_data_size = r.read_u16()? as usize; + let alice_meta_data = r.read_decrypt_auth( + alice_meta_data_size, + kbkdf::(noise_es_ee_se_hk_psk.as_bytes()), + &incoming_message_nonce, + )?; + if alice_meta_data.len() > data_buf.len() { + return Err(Error::DataTooLarge); } + data_buf[..alice_meta_data.len()].copy_from_slice(alice_meta_data); let session = Arc::new(Session { id: incoming.bob_session_id, @@ -1131,6 +1064,7 @@ impl Context { defrag: std::array::from_fn(|_| Mutex::new(Fragged::new())), }); + // Promote incoming session to active. { let mut sessions = self.sessions.write().unwrap(); sessions.incoming.remove(&incoming.bob_session_id); @@ -1139,7 +1073,14 @@ impl Context { let _ = session.send_nop(|b| send(Some(&session), b)); - return Ok(ReceiveResult::OkNewSession(session)); + return Ok(ReceiveResult::OkNewSession( + session, + if alice_meta_data.is_empty() { + None + } else { + Some(&mut data_buf[..alice_meta_data.len()]) + }, + )); } else { return Err(Error::UnknownLocalSessionId); } @@ -1650,22 +1591,40 @@ impl SessionKey { } } -/// Shortcut to HMAC data split into two slices. -fn hmac_sha384_2(key: &[u8], a: &[u8], b: &[u8]) -> [u8; 48] { - let mut hmac = HMACSHA384::new(key); - hmac.update(a); - hmac.update(b); - hmac.finish() -} +/// Helper for parsing variable length ALICE_NOISE_XK_ACK +struct PktReader<'a>(&'a mut [u8], usize); -/// 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); +impl<'a> PktReader<'a> { + fn read_u16(&mut self) -> Result { + let tmp = self.1 + 2; + if tmp <= self.0.len() { + let n = u16::from_le_bytes(self.0[self.1..tmp].try_into().unwrap()); + self.1 = tmp; + Ok(n) + } else { + Err(Error::InvalidPacket) + } + } + + fn read_decrypt_auth<'b>(&'b mut self, l: usize, k: Secret, nonce: &[u8]) -> Result<&'b [u8], Error> { + let mut tmp = self.1 + l; + if (tmp + AES_GCM_TAG_SIZE) <= self.0.len() { + let mut gcm = AesGcm::new(k.as_bytes(), false); + gcm.reset_init_gcm(nonce); + gcm.crypt_in_place(&mut self.0[self.1..tmp]); + let s = &self.0[self.1..tmp]; + self.1 = tmp; + tmp += AES_GCM_TAG_SIZE; + if !gcm.finish_decrypt(&self.0[self.1..tmp]) { + Err(Error::FailedAuthentication) + } else { + self.1 = tmp; + Ok(s) + } + } else { + Err(Error::InvalidPacket) + } + } } /// HMAC-SHA512 key derivation based on: https://csrc.nist.gov/publications/detail/sp/800-108/final (page 7)