From dfc9d5c4e0a97e52bb5f19ae417cd9dafa067710 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Wed, 18 Jan 2023 10:29:12 -0500 Subject: [PATCH] Noise XK work in progress. --- crypto/src/hash.rs | 2 + zssp/src/applicationlayer.rs | 3 + zssp/src/constants.rs | 37 ++----- zssp/src/lib.rs | 1 + zssp/src/proto.rs | 154 ++++++++++++++++++++++++++ zssp/src/sessionid.rs | 7 ++ zssp/src/zssp.rs | 202 ++++++++++++++++++++++++++++++++++- 7 files changed, 371 insertions(+), 35 deletions(-) create mode 100644 zssp/src/proto.rs diff --git a/crypto/src/hash.rs b/crypto/src/hash.rs index 85131f54e..d148517c2 100644 --- a/crypto/src/hash.rs +++ b/crypto/src/hash.rs @@ -7,6 +7,8 @@ use std::ptr::null; pub const SHA512_HASH_SIZE: usize = 64; pub const SHA384_HASH_SIZE: usize = 48; +pub const HMAC_SHA512_SIZE: usize = 64; +pub const HMAC_SHA384_SIZE: usize = 48; pub struct SHA512(Option); diff --git a/zssp/src/applicationlayer.rs b/zssp/src/applicationlayer.rs index 6a366d590..c29a9e8fd 100644 --- a/zssp/src/applicationlayer.rs +++ b/zssp/src/applicationlayer.rs @@ -62,6 +62,9 @@ pub trait ApplicationLayer: Sized { /// Rate limit and check an attempted new session (called before accept_new_session). fn check_new_session(&self, rc: &ReceiveContext, remote_address: &Self::RemoteAddress) -> bool; + /// Get a new locally unique session ID. + fn new_session(&self, remote_address: &Self::RemoteAddress) -> Option<(SessionId, Self::Data)>; + /// Check whether a new session should be accepted. /// /// On success a tuple of local session ID, static secret, and associated object is returned. The diff --git a/zssp/src/constants.rs b/zssp/src/constants.rs index f38973cf1..97ce0dfa1 100644 --- a/zssp/src/constants.rs +++ b/zssp/src/constants.rs @@ -7,19 +7,6 @@ pub const MIN_TRANSPORT_MTU: usize = 64; /// Minimum recommended interval between calls to service() on each session, in milliseconds. pub const SERVICE_INTERVAL: u64 = 10000; -/// Setting this to true enables kyber1024 post-quantum forward secrecy. -/// -/// Kyber1024 is used for data forward secrecy but not authentication. Authentication would -/// require Kyber1024 in identities, which would make them huge, and isn't needed for our -/// threat model which is data warehousing today to decrypt tomorrow. Breaking authentication -/// is only relevant today, not in some mid to far future where a QC that can break 384-bit ECC -/// exists. -/// -/// This is normally enabled but could be disabled at build time for e.g. very small devices. -/// It might not even be necessary there to disable it since it's not that big and is usually -/// faster than NIST P-384 ECDH. -pub(crate) const JEDI: bool = true; - /// Maximum number of fragments for data packets. pub(crate) const MAX_FRAGMENTS: usize = 48; // hard protocol max: 63 @@ -61,14 +48,10 @@ pub(crate) const HEADER_CHECK_ENCRYPT_START: usize = 6; /// End of single block AES encryption of a portion of the header (and some data). pub(crate) const HEADER_CHECK_ENCRYPT_END: usize = 22; -/// Size of AES-GCM keys (256 bits) pub(crate) const AES_KEY_SIZE: usize = 32; - -/// Size of AES-GCM MAC tags +pub(crate) const AES_HEADER_CHECK_KEY_SIZE: usize = 16; pub(crate) const AES_GCM_TAG_SIZE: usize = 16; - -/// Size of HMAC-SHA384 MAC tags -pub(crate) const HMAC_SIZE: usize = 48; +pub(crate) const AES_GCM_NONCE_SIZE: usize = 12; /// Size of a session ID, which behaves a bit like a TCP port number. /// @@ -84,17 +67,11 @@ pub(crate) const COUNTER_WINDOW_MAX_OUT_OF_ORDER: usize = 16; /// the counter yields an invalid value. pub(crate) const COUNTER_WINDOW_MAX_SKIP_AHEAD: u64 = 16777216; -// Packet types can range from 0 to 15 (4 bits) -- 0-3 are defined and 4-15 are reserved for future use -pub(crate) const PACKET_TYPE_DATA: u8 = 0; -pub(crate) const PACKET_TYPE_INITIAL_KEY_OFFER: u8 = 1; // "alice" -pub(crate) const PACKET_TYPE_KEY_COUNTER_OFFER: u8 = 2; // "bob" - // Key usage labels for sub-key derivation using NIST-style KBKDF (basically just HMAC KDF). -pub(crate) const KBKDF_KEY_USAGE_LABEL_HMAC: u8 = b'M'; // HMAC-SHA384 authentication for key exchanges +pub(crate) const KBKDF_KEY_USAGE_LABEL_KEX_PAYLOAD_ENCRYPTION: u8 = b'M'; // intermediate keys used in key exchanges pub(crate) const KBKDF_KEY_USAGE_LABEL_HEADER_CHECK: u8 = b'H'; // AES-based header check code generation 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_RATCHETING: u8 = b'R'; // Key input for next ephemeral ratcheting // AES key size for header check code generation pub(crate) const HEADER_CHECK_AES_KEY_SIZE: usize = 16; @@ -106,8 +83,8 @@ pub(crate) const HEADER_CHECK_AES_KEY_SIZE: usize = 16; /// the primary algorithm from NIST P-384 or the transport cipher from AES-GCM. pub(crate) const INITIAL_KEY: [u8; 64] = [ // macOS command line to generate: - // echo -n 'ZSSP_Noise_IKpsk2_NISTP384_?KYBER1024_AESGCM_SHA512' | shasum -a 512 | cut -d ' ' -f 1 | xxd -r -p | xxd -i - 0x35, 0x6a, 0x75, 0xc0, 0xbf, 0xbe, 0xc3, 0x59, 0x70, 0x94, 0x50, 0x69, 0x4c, 0xa2, 0x08, 0x40, 0xc7, 0xdf, 0x67, 0xa8, 0x68, 0x52, - 0x6e, 0xd5, 0xdd, 0x77, 0xec, 0x59, 0x6f, 0x8e, 0xa1, 0x99, 0xb4, 0x32, 0x85, 0xaf, 0x7f, 0x0d, 0xa9, 0x6c, 0x01, 0xfb, 0x72, 0x46, - 0xc0, 0x09, 0x58, 0xb8, 0xe0, 0xa8, 0xcf, 0xb1, 0x58, 0x04, 0x6e, 0x32, 0xba, 0xa8, 0xb8, 0xf9, 0x0a, 0xa4, 0xbf, 0x36, + // echo -n 'ZSSP_Noise_XKpsk2_NISTP384_?KYBER1024_AESGCM_SHA512' | shasum -a 512 | cut -d ' ' -f 1 | xxd -r -p | xxd -i + 0xc7, 0xde, 0xa3, 0xbe, 0x84, 0xe5, 0x91, 0x25, 0x30, 0x59, 0xc1, 0xc9, 0x5d, 0x22, 0xf5, 0x5a, 0xd0, 0x67, 0x9e, 0xf9, 0xf6, 0xbb, + 0xc0, 0x2a, 0x7f, 0xd0, 0x12, 0xb2, 0x0f, 0xed, 0x64, 0x7a, 0x86, 0x9f, 0x82, 0x19, 0xca, 0x84, 0xad, 0xf6, 0x61, 0xda, 0x59, 0xcc, + 0x40, 0xcf, 0x57, 0x68, 0x3e, 0xe4, 0xd6, 0xe7, 0xd1, 0xad, 0xe9, 0x56, 0x50, 0xf2, 0x38, 0x22, 0x88, 0xa3, 0x5c, 0x7f, ]; diff --git a/zssp/src/lib.rs b/zssp/src/lib.rs index 0c05db40f..cad124c93 100644 --- a/zssp/src/lib.rs +++ b/zssp/src/lib.rs @@ -1,5 +1,6 @@ mod applicationlayer; mod error; +mod proto; mod sessionid; mod tests; mod zssp; diff --git a/zssp/src/proto.rs b/zssp/src/proto.rs new file mode 100644 index 000000000..ac204a72d --- /dev/null +++ b/zssp/src/proto.rs @@ -0,0 +1,154 @@ +use std::mem::size_of; + +use pqc_kyber::{KYBER_CIPHERTEXTBYTES, KYBER_PUBLICKEYBYTES}; +use zerotier_crypto::p384::P384_PUBLIC_KEY_SIZE; + +use crate::applicationlayer::ApplicationLayer; +use crate::constants::{AES_GCM_TAG_SIZE, HEADER_SIZE, MIN_PACKET_SIZE, SESSION_ID_SIZE}; +use crate::error::Error; + +/// Maximum packet size for handshake packets +/// +/// Packed structs are padded to this size so they can be recast to byte arrays of this size. +pub(crate) const NOISE_MAX_HANDSHAKE_PACKET_SIZE: usize = 2048; + +pub(crate) const PACKET_TYPE_DATA: u8 = 0; +pub(crate) const PACKET_TYPE_ALICE_EPHEMERAL_OFFER: u8 = 1; +pub(crate) const PACKET_TYPE_BOB_EPHEMERAL_COUNTER_OFFER: u8 = 2; +pub(crate) const PACKET_TYPE_ALICE_STATIC_ACK: u8 = 3; +pub(crate) const PACKET_TYPE_ALICE_REKEY_INIT: u8 = 4; +pub(crate) const PACKET_TYPE_BOB_REKEY_ACK: u8 = 5; + +pub(crate) const NOISE_XK_ALICE_EPHEMERAL_OFFER_ENCRYPTED_SECTION_START: usize = HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE; +pub(crate) const NOISE_XK_ALICE_EPHEMERAL_OFFER_ENCRYPTED_SECTION_END: usize = + NOISE_XK_ALICE_EPHEMERAL_OFFER_ENCRYPTED_SECTION_START + SESSION_ID_SIZE + KYBER_PUBLICKEYBYTES + 8; +pub(crate) const NOISE_XK_ALICE_EPHEMERAL_OFFER_SIZE: usize = NOISE_XK_ALICE_EPHEMERAL_OFFER_ENCRYPTED_SECTION_END + AES_GCM_TAG_SIZE; + +pub(crate) trait ProtocolFlatBuffer {} + +#[allow(unused)] +#[repr(C, packed)] +pub(crate) struct NoiseXKAliceEphemeralOffer { + pub header: [u8; HEADER_SIZE], + pub session_protocol_version: u8, + pub alice_noise_e: [u8; P384_PUBLIC_KEY_SIZE], + // -- start AES-GCM(es) encrypted section (IV is first 12 bytes of SHA384(alice_noise_e)) + pub alice_session_id: [u8; SESSION_ID_SIZE], + pub alice_hk_public: [u8; KYBER_PUBLICKEYBYTES], + pub salt: [u8; 8], + // -- end encrypted section + pub gcm_mac: [u8; 16], + _padding: [u8; NOISE_MAX_HANDSHAKE_PACKET_SIZE - NOISE_XK_ALICE_EPHEMERAL_OFFER_SIZE], +} + +pub(crate) const NOISE_XK_BOB_EPHEMERAL_COUNTER_OFFER_ENCRYPTED_SECTION_START: usize = HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE; +pub(crate) const NOISE_XK_BOB_EPHEMERAL_COUNTER_OFFER_ENCRYPTED_SECTION_END: usize = + NOISE_XK_BOB_EPHEMERAL_COUNTER_OFFER_ENCRYPTED_SECTION_START + SESSION_ID_SIZE + KYBER_CIPHERTEXTBYTES; +pub(crate) const NOISE_XK_BOB_EPHEMERAL_COUNTER_OFFER_SIZE: usize = + NOISE_XK_BOB_EPHEMERAL_COUNTER_OFFER_ENCRYPTED_SECTION_END + AES_GCM_TAG_SIZE; + +#[allow(unused)] +#[repr(C, packed)] +pub(crate) struct NoiseXKBobEphemeralCounterOffer { + pub header: [u8; HEADER_SIZE], + pub session_protocol_version: u8, + pub bob_noise_e: [u8; P384_PUBLIC_KEY_SIZE], + // -- start AES-GCM(es_ee) encrypted section (IV is first 12 bytes of SHA384(bob_noise_e)) + pub bob_session_id: [u8; SESSION_ID_SIZE], + pub bob_hk_ciphertext: [u8; KYBER_CIPHERTEXTBYTES], + // -- end encrypted sectiion + pub gcm_mac: [u8; 16], + _padding: [u8; NOISE_MAX_HANDSHAKE_PACKET_SIZE - NOISE_XK_BOB_EPHEMERAL_COUNTER_OFFER_SIZE], +} + +/* +pub(crate) const NOISE_XK_ALICE_STATIC_ACK_FIXED_FIELDS_SIZE: usize = HEADER_SIZE + 1; + +#[allow(unused)] +#[repr(C, packed)] +pub(crate) struct NoiseXKAliceStaticAck { + pub header: [u8; HEADER_SIZE], + pub session_protocol_version: u8, + // -- start AES-GCM(es_ee) encrypted section (IV is first 12 bytes of SHA384(hk)) + _var_length_fields_and_padding: [u8; NOISE_MAX_HANDSHAKE_PACKET_SIZE - NOISE_XK_ALICE_STATIC_ACK_FIXED_FIELDS_SIZE], + // alice_static_blob_length: u16, + // alice_static_blob: [u8; ???], + // alice_metadata_length: u16, + // alice_metadata: [u8; ???], + // hmac_es_ee_se_hk_psk: [u8; HMAC_SHA384_SIZE], + // -- end encrypted section + // pub gcm_mac: [u8; 16], +} +*/ + +#[allow(unused)] +#[repr(C, packed)] +pub(crate) struct AliceRekeyInit { + pub header: [u8; HEADER_SIZE], + // -- start AES-GCM encrypted portion (using current key) + pub alice_e: [u8; P384_PUBLIC_KEY_SIZE], + pub alice_hk_public: [u8; KYBER_PUBLICKEYBYTES], + // -- end AES-GCM encrypted portion + pub gcm_mac: [u8; 16], +} + +#[allow(unused)] +#[repr(C, packed)] +pub(crate) struct BobRekeyAck { + pub header: [u8; HEADER_SIZE], + // -- start AES-GCM encrypted portion (using current key) + pub bob_e: [u8; P384_PUBLIC_KEY_SIZE], + pub bob_hk_ciphertext: [u8; KYBER_CIPHERTEXTBYTES], + // -- end AES-GCM encrypted portion + pub gcm_mac: [u8; 16], +} + +// Annotate only these structs as being compatible with packet_buffer_as_bytes(). These structs +// are packed flat buffers containing only byte or byte array fields, making them safe to treat +// this way even on architectures that require type size aligned access. +impl ProtocolFlatBuffer for NoiseXKAliceEphemeralOffer {} +impl ProtocolFlatBuffer for NoiseXKBobEphemeralCounterOffer {} +//impl ProtocolFlatBuffer for NoiseXKAliceStaticAck {} +impl ProtocolFlatBuffer for AliceRekeyInit {} +impl ProtocolFlatBuffer for BobRekeyAck {} + +/// Assemble a series of fragments into a buffer and return the length of the assembled packet in bytes. +pub(crate) fn assemble_fragments_into(fragments: &[A::IncomingPacketBuffer], d: &mut [u8]) -> Result { + let mut l = 0; + for i in 0..fragments.len() { + let mut ff = fragments[i].as_ref(); + if ff.len() <= MIN_PACKET_SIZE { + return Err(Error::InvalidPacket); + } + if i > 0 { + ff = &ff[HEADER_SIZE..]; + } + let j = l + ff.len(); + if j > d.len() { + return Err(Error::InvalidPacket); + } + d[l..j].copy_from_slice(ff); + l = j; + } + return Ok(l); +} + +// Down here is where the only unsafe code here lives. It's instrumented with assertions wherever +// possible and just helps us efficiently cast to/from flat buffers. + +#[inline(always)] +pub(crate) fn new_packet_buffer() -> B { + unsafe { std::mem::zeroed() } +} + +#[inline(always)] +pub(crate) fn packet_buffer_as_bytes(b: &B) -> &[u8; NOISE_MAX_HANDSHAKE_PACKET_SIZE] { + assert_eq!(size_of::(), NOISE_MAX_HANDSHAKE_PACKET_SIZE); + unsafe { &*(b as *const B).cast() } +} + +#[inline(always)] +pub(crate) fn packet_buffer_as_bytes_mut(b: &mut B) -> &mut [u8; NOISE_MAX_HANDSHAKE_PACKET_SIZE] { + assert_eq!(size_of::(), NOISE_MAX_HANDSHAKE_PACKET_SIZE); + unsafe { &mut *(b as *mut B).cast() } +} diff --git a/zssp/src/sessionid.rs b/zssp/src/sessionid.rs index 09f32bd63..d0b6c2e42 100644 --- a/zssp/src/sessionid.rs +++ b/zssp/src/sessionid.rs @@ -25,6 +25,13 @@ impl SessionId { Self(NonZeroU64::new(((random::xorshift64_random() % (Self::MAX - 1)) + 1).to_le()).unwrap()) } + pub(crate) fn new_from_bytes(b: &[u8; SESSION_ID_SIZE]) -> Option { + let mut tmp = [0u8; 8]; + tmp[..SESSION_ID_SIZE].copy_from_slice(b); + Self::new_from_u64_le(u64::from_ne_bytes(tmp)) + } + + /// Create from a u64 that is already in little-endian byte order. #[inline(always)] pub(crate) fn new_from_u64_le(i: u64) -> Option { NonZeroU64::new(i & Self::MAX.to_le()).map(|i| Self(i)) diff --git a/zssp/src/zssp.rs b/zssp/src/zssp.rs index ffef800b1..6b90e80f0 100644 --- a/zssp/src/zssp.rs +++ b/zssp/src/zssp.rs @@ -3,9 +3,12 @@ // ZSSP: ZeroTier Secure Session Protocol // FIPS compliant Noise_IK with Jedi powers and built-in attack-resistant large payload (fragmentation) support. +use std::io::Write; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::{Mutex, RwLock}; +use pqc_kyber::KYBER_SECRETKEYBYTES; + use zerotier_crypto::aes::{Aes, AesGcm}; use zerotier_crypto::hash::{hmac_sha512, HMACSHA384, SHA384}; use zerotier_crypto::p384::{P384KeyPair, P384PublicKey, P384_PUBLIC_KEY_SIZE}; @@ -22,6 +25,7 @@ use zerotier_utils::varint; use crate::applicationlayer::ApplicationLayer; use crate::constants::*; use crate::error::Error; +use crate::proto::*; use crate::sessionid::SessionId; /// Result generated by the packet receive function, with possible payloads. @@ -63,16 +67,31 @@ pub struct Session { send_counter: AtomicU64, // Outgoing packet counter and nonce state receive_window: [AtomicU64; COUNTER_WINDOW_MAX_OUT_OF_ORDER], // Receive window for anti-replay and deduplication - psk: Secret<64>, // Arbitrary PSK provided by external code - noise_ss: Secret<48>, // Static raw shared ECDH NIST P-384 key header_check_cipher: Aes, // Cipher used for header check codes (not Noise related) - state: RwLock, // Mutable parts of state (other than defrag buffers) - remote_s_public_blob_hash: [u8; 48], // SHA384(remote static public key blob) - remote_s_public_p384_bytes: [u8; P384_PUBLIC_KEY_SIZE], // Remote NIST P-384 static public key + offer: Mutex, // Most recently sent ephemeral offer + state: RwLock, // Miscellaneous mutable state + //psk: Secret<64>, // Arbitrary PSK provided by external code + //noise_ss: Secret<48>, // Static raw shared ECDH NIST P-384 key + //state: RwLock, // Mutable parts of state (other than defrag buffers) + //remote_s_public_blob_hash: [u8; 48], // SHA384(remote static public key blob) + //remote_s_public_p384_bytes: [u8; P384_PUBLIC_KEY_SIZE], // Remote NIST P-384 static public key defrag: Mutex, 8, 8>>, } +enum EphemeralOffer { + None, + Alice(Secret<64>, P384KeyPair, [u8; KYBER_SECRETKEYBYTES]), + Bob(Secret<64>, P384KeyPair), +} + +struct State { + keys: [Option; 2], + current_key: usize, + psk: Secret<64>, +} + +/* struct SessionMutableState { remote_session_id: Option, // The other side's 48-bit session ID session_keys: [Option; 2], // Buffers to store last and latest key by 1-bit key index @@ -80,6 +99,7 @@ struct SessionMutableState { offer: Option, // Most recent ephemeral offer sent to remote last_remote_offer: i64, // Time of most recent ephemeral offer (ms) } +*/ /// A shared symmetric session key. struct SessionKey { @@ -98,6 +118,7 @@ struct SessionKey { jedi: bool, // True if Kyber1024 was used (both sides enabled) } +/* /// Alice's KEY_OFFER, remembered so Noise agreement process can resume on KEY_COUNTER_OFFER. struct EphemeralOffer { id: [u8; 16], // Arbitrary random offer ID @@ -108,6 +129,7 @@ struct EphemeralOffer { alice_e_keypair: P384KeyPair, // NIST P-384 key pair (Noise ephemeral key for Alice) alice_hk_keypair: Option, // Kyber1024 key pair (PQ hybrid ephemeral key for Alice) } +*/ /// Was this side the one who sent the first offer (Alice) or countered (Bob). /// @@ -130,6 +152,7 @@ impl Session { /// * `application_data` - Arbitrary object to put into session /// * `mtu` - Physical wire maximum transmission unit (current value, can change through the course of a session) /// * `current_time` - Current monotonic time in milliseconds since an arbitrary time in the past + /* pub fn start_new( app: &Application, mut send: SendFunction, @@ -190,12 +213,14 @@ impl Session { } return Err(Error::InvalidParameter); } + */ /// Send data over the session. /// /// * `send` - Function to call to send physical packet(s) /// * `mtu_sized_buffer` - A writable work buffer whose size also specifies the physical MTU /// * `data` - Data to send + /* #[inline] pub fn send( &self, @@ -253,7 +278,9 @@ impl Session { } return Err(Error::SessionNotEstablished); } + */ + /* /// Check whether this session is established. pub fn established(&self) -> bool { let state = self.state.read().unwrap(); @@ -267,6 +294,7 @@ impl Session { .as_ref() .map(|k| (k.secret_fingerprint, k.ratchet_count, k.role, k.jedi)) } + */ /// This function needs to be called on each session at least every SERVICE_INTERVAL milliseconds. /// @@ -285,6 +313,7 @@ impl Session { current_time: i64, force_expire: bool, ) { + /* let state = self.state.read().unwrap(); if state.session_keys[state.cur_session_key_idx].as_ref().map_or(true, |k| { matches!(k.role, Role::Bob) @@ -323,6 +352,7 @@ impl Session { } } } + */ } /// Check the receive window without mutating state. @@ -509,6 +539,7 @@ impl ReceiveContext { let message_nonce = create_message_nonce(packet_type, counter); if packet_type == PACKET_TYPE_DATA { + /* if let Some(session) = session { let state = session.state.read().unwrap(); if let Some(session_key) = state.session_keys[key_index].as_ref() { @@ -589,9 +620,169 @@ impl ReceiveContext { unlikely_branch(); return Err(Error::SessionNotEstablished); } + */ + todo!() } else { unlikely_branch(); + match packet_type { + PACKET_TYPE_ALICE_EPHEMERAL_OFFER => { + // Alice (remote) --> Bob (local) + + let mut pkt: NoiseXKAliceEphemeralOffer = new_packet_buffer(); + if assemble_fragments_into::(fragments, packet_buffer_as_bytes_mut(&mut pkt))? + != NOISE_XK_ALICE_EPHEMERAL_OFFER_SIZE + { + return Err(Error::InvalidPacket); + } + if pkt.session_protocol_version != SESSION_PROTOCOL_VERSION { + return Err(Error::UnknownProtocolVersion); + } + + 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 mut gcm = AesGcm::new( + &kbkdf512(noise_es.as_bytes(), KBKDF_KEY_USAGE_LABEL_KEX_PAYLOAD_ENCRYPTION).as_bytes()[..AES_KEY_SIZE], + false, + ); + gcm.reset_init_gcm(&SHA384::hash(&pkt.alice_noise_e)[..AES_GCM_NONCE_SIZE]); + gcm.crypt_in_place( + &mut packet_buffer_as_bytes_mut(&mut pkt) + [NOISE_XK_ALICE_EPHEMERAL_OFFER_ENCRYPTED_SECTION_START..NOISE_XK_ALICE_EPHEMERAL_OFFER_ENCRYPTED_SECTION_END], + ); + if !gcm.finish_decrypt(&pkt.gcm_mac) { + return Err(Error::FailedAuthentication); + } + + let alice_session_id = SessionId::new_from_bytes(&pkt.alice_session_id).ok_or(Error::InvalidPacket)?; + let (bob_hk_ciphertext, hk) = pqc_kyber::encapsulate(&pkt.alice_hk_public, &mut random::SecureRandom::default()) + .map_err(|_| Error::FailedAuthentication)?; + + let (bob_session_id, application_data) = app.new_session(remote_address).ok_or(Error::NewSessionRejected)?; + let bob_e_secret = P384KeyPair::generate(); + + let noise_es_ee = Secret(hmac_sha512( + noise_es.as_bytes(), + bob_e_secret.agree(&alice_noise_e).ok_or(Error::FailedAuthentication)?.as_bytes(), + )); + + let mut reply: NoiseXKBobEphemeralCounterOffer = new_packet_buffer(); + reply.session_protocol_version = SESSION_PROTOCOL_VERSION; + reply.bob_noise_e = bob_e_secret.public_key_bytes().clone(); + reply.bob_session_id = bob_session_id.as_bytes().clone(); + reply.bob_hk_ciphertext = bob_hk_ciphertext; + + let mut gcm = AesGcm::new( + &kbkdf512(noise_es_ee.as_bytes(), KBKDF_KEY_USAGE_LABEL_KEX_PAYLOAD_ENCRYPTION).as_bytes()[..AES_KEY_SIZE], + true, + ); + gcm.reset_init_gcm(&SHA384::hash(bob_e_secret.public_key_bytes())[..AES_GCM_NONCE_SIZE]); + gcm.crypt_in_place( + &mut packet_buffer_as_bytes_mut(&mut reply)[NOISE_XK_BOB_EPHEMERAL_COUNTER_OFFER_ENCRYPTED_SECTION_START + ..NOISE_XK_BOB_EPHEMERAL_COUNTER_OFFER_ENCRYPTED_SECTION_END], + ); + reply.gcm_mac = gcm.finish_encrypt(); + + let header_check_cipher = Aes::new( + &kbkdf512(noise_es.as_bytes(), KBKDF_KEY_USAGE_LABEL_HEADER_CHECK).as_bytes()[..AES_HEADER_CHECK_KEY_SIZE], + ); + + send_with_fragmentation( + send, + &mut packet_buffer_as_bytes_mut(&mut reply)[..NOISE_XK_BOB_EPHEMERAL_COUNTER_OFFER_SIZE], + mtu, + PACKET_TYPE_BOB_EPHEMERAL_COUNTER_OFFER, + u64::from(alice_session_id), + 0, + 1, + &header_check_cipher, + )?; + + return Ok(ReceiveResult::OkNewSession(Session { + id: bob_session_id, + application_data, + send_counter: AtomicU64::new(2), // 1 was used in reply + receive_window: std::array::from_fn(|_| AtomicU64::new(0)), + header_check_cipher, + offer: Mutex::new(EphemeralOffer::Bob(noise_es_ee, bob_e_secret)), + state: RwLock::new(State { keys: [None, None], current_key: 0, psk: Secret::default() }), + defrag: Mutex::new(RingBufferMap::new(random::xorshift64_random() as u32)), + })); + } + + PACKET_TYPE_BOB_EPHEMERAL_COUNTER_OFFER => { + // Bob (remote) --> Alice (local) + + if let Some(session) = session { + let mut pkt: NoiseXKBobEphemeralCounterOffer = new_packet_buffer(); + if assemble_fragments_into::(fragments, packet_buffer_as_bytes_mut(&mut pkt))? + != NOISE_XK_BOB_EPHEMERAL_COUNTER_OFFER_SIZE + { + return Err(Error::InvalidPacket); + } + if pkt.session_protocol_version != SESSION_PROTOCOL_VERSION { + return Err(Error::UnknownProtocolVersion); + } + + let mut offer = session.offer.lock().unwrap(); + match &*offer { + EphemeralOffer::Alice(noise_es, alice_e_secret, alice_hk_secret) => { + let bob_noise_e = P384PublicKey::from_bytes(&pkt.bob_noise_e).ok_or(Error::FailedAuthentication)?; + let noise_es_ee = Secret(hmac_sha512( + noise_es.as_bytes(), + alice_e_secret.agree(&bob_noise_e).ok_or(Error::FailedAuthentication)?.as_bytes(), + )); + let noise_es_ee_se = app.get_local_s_keypair().agree(&bob_noise_e).ok_or(Error::FailedAuthentication)?; + + let mut gcm = AesGcm::new( + &kbkdf512(noise_es_ee.as_bytes(), KBKDF_KEY_USAGE_LABEL_KEX_PAYLOAD_ENCRYPTION).as_bytes() + [..AES_KEY_SIZE], + false, + ); + gcm.reset_init_gcm(&SHA384::hash(&pkt.bob_noise_e)[..AES_GCM_NONCE_SIZE]); + gcm.crypt_in_place( + &mut packet_buffer_as_bytes_mut(&mut pkt)[NOISE_XK_BOB_EPHEMERAL_COUNTER_OFFER_ENCRYPTED_SECTION_START + ..NOISE_XK_BOB_EPHEMERAL_COUNTER_OFFER_ENCRYPTED_SECTION_END], + ); + if !gcm.finish_decrypt(&pkt.gcm_mac) { + return Err(Error::FailedAuthentication); + } + + let bob_session_id = SessionId::new_from_bytes(&pkt.bob_session_id).ok_or(Error::InvalidPacket)?; + let hk = pqc_kyber::decapsulate(&pkt.bob_hk_ciphertext, alice_hk_secret) + .map_err(|_| Error::FailedAuthentication)?; + + let alice_s_public_blob = app.get_local_s_public_blob(); + + let mut reply = [0u8; NOISE_MAX_HANDSHAKE_PACKET_SIZE]; + let mut reply_w = &mut reply[..]; + assert!(alice_s_public_blob.len() <= (u16::MAX as usize)); + let _ = reply_w.write_all(&(alice_s_public_blob.len() as u16).to_le_bytes()); + let _ = reply_w.write_all(alice_s_public_blob); + let _ = reply_w.write_all(&[0u8, 0u8]); // zero size meta-data, to be implemented later + } + _ => { + return Ok(ReceiveResult::Ignored); + } + } + } else { + return Err(Error::SessionNotEstablished); + } + } + + PACKET_TYPE_ALICE_STATIC_ACK => {} + + PACKET_TYPE_ALICE_REKEY_INIT => {} + + PACKET_TYPE_BOB_REKEY_ACK => {} + + _ => { + return Err(Error::InvalidPacket); + } + } + todo!() + /* // To greatly simplify logic handling key exchange packets, assemble these first. // Handling KEX packets isn't the fast path so the extra copying isn't significant. const KEX_BUF_LEN: usize = 4096; @@ -1081,6 +1272,7 @@ impl ReceiveContext { _ => return Err(Error::InvalidPacket), } + */ } } }