diff --git a/zssp/src/zssp.rs b/zssp/src/zssp.rs index 7a29452a4..cca39eb40 100644 --- a/zssp/src/zssp.rs +++ b/zssp/src/zssp.rs @@ -202,7 +202,7 @@ impl std::fmt::Debug for Error { } /// Result generated by the packet receive function, with possible payloads. -pub enum ReceiveResult<'a, H: Host> { +pub enum ReceiveResult<'a, H: ApplicationLayer> { /// Packet is valid, no action needs to be taken. Ok, @@ -266,7 +266,7 @@ impl From for u64 { /// This holds the data structures used to defragment incoming packets that are not associated with an /// existing session, which would be new attempts to create sessions. Typically one of these is associated /// with a single listen socket, local bound port, or other inbound endpoint. -pub struct ReceiveContext { +pub struct ReceiveContext { initial_offer_defrag: Mutex, 1024, 128>>, incoming_init_header_check_cipher: Aes, } @@ -275,9 +275,9 @@ pub struct ReceiveContext { /// /// Templating the session on this trait lets the code here be almost entirely transport, OS, /// and use case independent. -pub trait Host: Sized { +pub trait ApplicationLayer: Sized { /// Arbitrary opaque object associated with a session, such as a connection state object. - type AssociatedObject; + type SessionUserData; /// Arbitrary object that dereferences to the session, such as Arc>. type SessionRef: Deref>; @@ -298,7 +298,7 @@ pub trait Host: Sized { /// /// This must contain a NIST P-384 public key but can contain other information. In ZeroTier this /// is a byte serialized identity. It could just be a naked NIST P-384 key if that's all you need. - fn get_local_s_public(&self) -> &[u8]; + fn get_local_s_public_raw(&self) -> &[u8]; /// Get SHA384(this host's static public key blob). /// @@ -308,20 +308,20 @@ pub trait Host: Sized { /// Get a reference to this hosts' static public key's NIST P-384 secret key pair. /// /// This must return the NIST P-384 public key that is contained within the static public key blob. - fn get_local_s_keypair_p384(&self) -> &P384KeyPair; + fn get_local_s_keypair(&self) -> &P384KeyPair; /// Extract the NIST P-384 ECC public key component from a static public key blob or return None on failure. /// /// This is called to parse the static public key blob from the other end and extract its NIST P-384 public /// key. SECURITY NOTE: the information supplied here is from the wire so care must be taken to parse it /// safely and fail on any error or corruption. - fn extract_p384_static(static_public: &[u8]) -> Option; + fn extract_s_public_from_raw(static_public: &[u8]) -> Option; /// Look up a local session by local session ID or return None if not found. - fn session_lookup(&self, local_session_id: SessionId) -> Option; + fn lookup_session(&self, local_session_id: SessionId) -> Option; /// Rate limit and check an attempted new session (called before accept_new_session). - fn check_new_session_attempt(&self, rc: &ReceiveContext, remote_address: &Self::RemoteAddress) -> bool; + fn check_new_session(&self, rc: &ReceiveContext, remote_address: &Self::RemoteAddress) -> bool; /// Check whether a new session should be accepted. /// @@ -334,74 +334,74 @@ pub trait Host: Sized { remote_address: &Self::RemoteAddress, remote_static_public: &[u8], remote_metadata: &[u8], - ) -> Option<(SessionId, Secret<64>, Self::AssociatedObject)>; + ) -> Option<(SessionId, Secret<64>, Self::SessionUserData)>; } /// ZSSP bi-directional packet transport channel. -pub struct Session { +pub struct Session { /// This side's session ID (unique on this side) pub id: SessionId, /// An arbitrary object associated with session (type defined in Host trait) - pub associated_object: H::AssociatedObject, + pub user_data: Layer::SessionUserData, send_counter: Counter, // Outgoing packet counter and nonce state psk: Secret<64>, // Arbitrary PSK provided by external code - ss: Secret<48>, // Static raw shared ECDH NIST P-384 key + noise_ss: Secret<48>, // Static raw shared ECDH NIST P-384 key header_check_cipher: Aes, // Cipher used for header MAC (fragmentation) state: RwLock, // Mutable parts of state (other than defrag buffers) remote_s_public_hash: [u8; 48], // SHA384(remote static public key blob) - remote_s_public_p384: [u8; P384_PUBLIC_KEY_SIZE], // Remote NIST P-384 static public key + remote_s_public_raw: [u8; P384_PUBLIC_KEY_SIZE], // Remote NIST P-384 static public key - defrag: Mutex, 8, 8>>, + defrag: Mutex, 8, 8>>, } struct SessionMutableState { remote_session_id: Option, // The other side's 48-bit session ID - keys: [Option; KEY_HISTORY_SIZE], // Buffers to store current, next, and last active key - key_ptr: usize, // Pointer used for keys[] circular buffer + session_keys: [Option; KEY_HISTORY_SIZE], // Buffers to store current, next, and last active key + cur_session_key_idx: usize, // Pointer used for keys[] circular buffer offer: Option>, // Most recent ephemeral offer sent to remote last_remote_offer: i64, // Time of most recent ephemeral offer (ms) } -impl Session { +impl Session { /// Create a new session and send an initial key offer message to the other end. /// /// * `host` - Interface to application using ZSSP - /// * `local_session_id` - ID for this side of the session, must be locally unique - /// * `remote_s_public` - Remote side's public key/identity + /// * `local_session_id` - ID for this side (Alice) of the session, must be locally unique + /// * `remote_s_public_raw` - Remote side's (Bob's) public key/identity /// * `offer_metadata` - Arbitrary meta-data to send with key offer (empty if none) /// * `psk` - Arbitrary pre-shared key to include as initial key material (use all zero secret if none) - /// * `associated_object` - Arbitrary object to put into session - /// * `mtu` - Physical wire MTU + /// * `user_data` - Arbitrary object to put into session + /// * `mtu` - Physical wire maximum transmition unit /// * `current_time` - Current monotonic time in milliseconds pub fn new( - host: &H, + host: &Layer, mut send: SendFunction, local_session_id: SessionId, - remote_s_public: &[u8], + remote_s_public_raw: &[u8], offer_metadata: &[u8], psk: &Secret<64>, - associated_object: H::AssociatedObject, + user_data: Layer::SessionUserData, mtu: usize, current_time: i64, ) -> Result { - if let Some(remote_s_public_p384) = H::extract_p384_static(remote_s_public) { - if let Some(ss) = host.get_local_s_keypair_p384().agree(&remote_s_public_p384) { + if let Some(remote_s_public_p384) = Layer::extract_s_public_from_raw(remote_s_public_raw) { + if let Some(noise_ss) = host.get_local_s_keypair().agree(&remote_s_public_p384) { let send_counter = Counter::new(); - let remote_s_public_hash = SHA384::hash(remote_s_public); + let remote_s_public_hash = SHA384::hash(remote_s_public_raw); let header_check_cipher = - Aes::new(kbkdf512(ss.as_bytes(), KBKDF_KEY_USAGE_LABEL_HEADER_CHECK).first_n::()); + Aes::new(kbkdf512(noise_ss.as_bytes(), KBKDF_KEY_USAGE_LABEL_HEADER_CHECK).first_n::()); if let Ok(offer) = send_ephemeral_offer( &mut send, send_counter.next(), local_session_id, None, - host.get_local_s_public(), + host.get_local_s_public_raw(), offer_metadata, &remote_s_public_p384, &remote_s_public_hash, - &ss, + &noise_ss, None, None, mtu, @@ -409,20 +409,20 @@ impl Session { ) { return Ok(Self { id: local_session_id, - associated_object, + user_data, send_counter, psk: psk.clone(), - ss, + noise_ss, header_check_cipher, state: RwLock::new(SessionMutableState { remote_session_id: None, - keys: [None, None, None], - key_ptr: 0, + session_keys: [None, None, None], + cur_session_key_idx: 0, offer: Some(offer), last_remote_offer: i64::MIN, }), remote_s_public_hash, - remote_s_public_p384: remote_s_public_p384.as_bytes().clone(), + remote_s_public_raw: remote_s_public_p384.as_bytes().clone(), defrag: Mutex::new(RingBufferMap::new(random::xorshift64_random() as u32)), }); } @@ -446,7 +446,7 @@ impl Session { debug_assert!(mtu_buffer.len() >= MIN_TRANSPORT_MTU); let state = self.state.read().unwrap(); if let Some(remote_session_id) = state.remote_session_id { - if let Some(key) = state.keys[state.key_ptr].as_ref() { + if let Some(sym_key) = state.session_keys[state.cur_session_key_idx].as_ref() { // Total size of the armored packet we are going to send (may end up being fragmented) let mut packet_len = data.len() + HEADER_SIZE + AES_GCM_TAG_SIZE; @@ -465,7 +465,7 @@ impl Session { // Get an initialized AES-GCM cipher and re-initialize with a 96-bit IV built from remote session ID, // packet type, and counter. - let mut c = key.get_send_cipher(counter)?; + let mut c = sym_key.get_send_cipher(counter)?; c.reset_init_gcm(CanonicalHeader::make(remote_session_id, PACKET_TYPE_DATA, counter.to_u32()).as_bytes()); // Send first N-1 fragments of N total fragments. @@ -500,7 +500,7 @@ impl Session { send(&mut mtu_buffer[..packet_len]); // Check reusable AES-GCM instance back into pool. - key.return_send_cipher(c); + sym_key.return_send_cipher(c); return Ok(()); } else { @@ -515,7 +515,7 @@ impl Session { /// Check whether this session is established. pub fn established(&self) -> bool { let state = self.state.read().unwrap(); - state.remote_session_id.is_some() && state.keys[state.key_ptr].is_some() + state.remote_session_id.is_some() && state.session_keys[state.cur_session_key_idx].is_some() } /// Get information about this session's security state. @@ -524,7 +524,7 @@ impl Session { /// and whether Kyber1024 was used. None is returned if the session isn't established. pub fn status(&self) -> Option<([u8; 16], i64, u64, bool)> { let state = self.state.read().unwrap(); - if let Some(key) = state.keys[state.key_ptr].as_ref() { + if let Some(key) = state.session_keys[state.cur_session_key_idx].as_ref() { Some((key.secret_fingerprint, key.establish_time, key.ratchet_count, key.jedi)) } else { None @@ -541,7 +541,7 @@ impl Session { /// * `force_rekey` - Re-key the session now regardless of key aging (still subject to rate limiting) pub fn service( &self, - host: &H, + host: &Layer, mut send: SendFunction, offer_metadata: &[u8], mtu: usize, @@ -550,26 +550,26 @@ impl Session { ) { let state = self.state.read().unwrap(); if (force_rekey - || state.keys[state.key_ptr] + || state.session_keys[state.cur_session_key_idx] .as_ref() .map_or(true, |key| key.lifetime.should_rekey(self.send_counter.previous(), current_time))) && state .offer .as_ref() - .map_or(true, |o| (current_time - o.creation_time) > H::REKEY_RATE_LIMIT_MS) + .map_or(true, |o| (current_time - o.creation_time) > Layer::REKEY_RATE_LIMIT_MS) { - if let Some(remote_s_public_p384) = P384PublicKey::from_bytes(&self.remote_s_public_p384) { + if let Some(remote_s_public) = P384PublicKey::from_bytes(&self.remote_s_public_raw) { if let Ok(offer) = send_ephemeral_offer( &mut send, self.send_counter.next(), self.id, state.remote_session_id, - host.get_local_s_public(), + host.get_local_s_public_raw(), offer_metadata, - &remote_s_public_p384, + &remote_s_public, &self.remote_s_public_hash, - &self.ss, - state.keys[state.key_ptr].as_ref(), + &self.noise_ss, + state.session_keys[state.cur_session_key_idx].as_ref(), if state.remote_session_id.is_some() { Some(&self.header_check_cipher) } else { @@ -586,8 +586,8 @@ impl Session { } } -impl ReceiveContext { - pub fn new(host: &H) -> Self { +impl ReceiveContext { + pub fn new(host: &Layer) -> Self { Self { initial_offer_defrag: Mutex::new(RingBufferMap::new(random::xorshift64_random() as u32)), incoming_init_header_check_cipher: Aes::new( @@ -607,14 +607,14 @@ impl ReceiveContext { #[inline] pub fn receive<'a, SendFunction: FnMut(&mut [u8])>( &self, - host: &H, - remote_address: &H::RemoteAddress, + host: &Layer, + remote_address: &Layer::RemoteAddress, mut send: SendFunction, data_buf: &'a mut [u8], - incoming_packet_buf: H::IncomingPacketBuffer, + incoming_packet_buf: Layer::IncomingPacketBuffer, mtu: usize, current_time: i64, - ) -> Result, Error> { + ) -> Result, Error> { let incoming_packet = incoming_packet_buf.as_ref(); if incoming_packet.len() < MIN_PACKET_SIZE { unlikely_branch(); @@ -629,7 +629,7 @@ impl ReceiveContext { if let Some(local_session_id) = SessionId::new_from_u64(u64::from_le(memory::load_raw(&incoming_packet[8..16])) & 0xffffffffffffu64) { - if let Some(session) = host.session_lookup(local_session_id) { + if let Some(session) = host.lookup_session(local_session_id) { if verify_header_check_code(incoming_packet, &session.header_check_cipher) { let canonical_header = CanonicalHeader::make(local_session_id, packet_type, counter); if fragment_count > 1 { @@ -730,17 +730,17 @@ impl ReceiveContext { #[inline] fn receive_complete<'a, SendFunction: FnMut(&mut [u8])>( &self, - host: &H, - remote_address: &H::RemoteAddress, + host: &Layer, + remote_address: &Layer::RemoteAddress, send: &mut SendFunction, data_buf: &'a mut [u8], canonical_header_bytes: &[u8; 12], - fragments: &[H::IncomingPacketBuffer], + fragments: &[Layer::IncomingPacketBuffer], packet_type: u8, - session: Option, + session: Option, mtu: usize, current_time: i64, - ) -> Result, Error> { + ) -> Result, Error> { debug_assert!(fragments.len() >= 1); // The first 'if' below should capture both DATA and NOP but not other types. Sanity check this. @@ -751,9 +751,9 @@ impl ReceiveContext { if let Some(session) = session { let state = session.state.read().unwrap(); for p in 0..KEY_HISTORY_SIZE { - let key_ptr = (state.key_ptr + p) % KEY_HISTORY_SIZE; - if let Some(key) = state.keys[key_ptr].as_ref() { - let mut c = key.get_receive_cipher(); + let key_idx = (state.cur_session_key_idx + p) % KEY_HISTORY_SIZE; + if let Some(session_key) = state.session_keys[key_idx].as_ref() { + let mut c = session_key.get_receive_cipher(); c.reset_init_gcm(canonical_header_bytes); let mut data_len = 0; @@ -766,7 +766,7 @@ impl ReceiveContext { data_len += f.len() - HEADER_SIZE; if data_len > data_buf.len() { unlikely_branch(); - key.return_receive_cipher(c); + session_key.return_receive_cipher(c); return Err(Error::DataBufferTooSmall); } c.crypt(&f[HEADER_SIZE..], &mut data_buf[current_frag_data_start..data_len]); @@ -782,7 +782,7 @@ impl ReceiveContext { data_len += last_fragment.len() - (HEADER_SIZE + AES_GCM_TAG_SIZE); if data_len > data_buf.len() { unlikely_branch(); - key.return_receive_cipher(c); + session_key.return_receive_cipher(c); return Err(Error::DataBufferTooSmall); } c.crypt( @@ -791,21 +791,21 @@ impl ReceiveContext { ); let aead_authentication_ok = c.finish_decrypt(&last_fragment[(last_fragment.len() - AES_GCM_TAG_SIZE)..]); - key.return_receive_cipher(c); + session_key.return_receive_cipher(c); if aead_authentication_ok { // Select this key as the new default if it's newer than the current key. if p > 0 - && state.keys[state.key_ptr] + && state.session_keys[state.cur_session_key_idx] .as_ref() - .map_or(true, |old| old.establish_counter < key.establish_counter) + .map_or(true, |old| old.establish_counter < session_key.establish_counter) { drop(state); let mut state = session.state.write().unwrap(); - state.key_ptr = key_ptr; + state.cur_session_key_idx = key_idx; for i in 0..KEY_HISTORY_SIZE { - if i != key_ptr { - if let Some(old_key) = state.keys[key_ptr].as_ref() { + if i != key_idx { + if let Some(old_key) = state.session_keys[key_idx].as_ref() { // Release pooled cipher memory from old keys. old_key.receive_cipher_pool.lock().unwrap().clear(); old_key.send_cipher_pool.lock().unwrap().clear(); @@ -882,27 +882,27 @@ impl ReceiveContext { // Check rate limits. if let Some(session) = session.as_ref() { - if (current_time - session.state.read().unwrap().last_remote_offer) < H::REKEY_RATE_LIMIT_MS { + if (current_time - session.state.read().unwrap().last_remote_offer) < Layer::REKEY_RATE_LIMIT_MS { return Err(Error::RateLimited); } } else { - if !host.check_new_session_attempt(self, remote_address) { + if !host.check_new_session(self, remote_address) { return Err(Error::RateLimited); } } // Key agreement: alice (remote) ephemeral NIST P-384 <> local static NIST P-384 - let (alice_e0_public, e0s) = + let (alice_e_public, noise_es) = P384PublicKey::from_bytes(&kex_packet[(HEADER_SIZE + 1)..(HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE)]) - .and_then(|pk| host.get_local_s_keypair_p384().agree(&pk).map(move |s| (pk, s))) + .and_then(|pk| host.get_local_s_keypair().agree(&pk).map(move |s| (pk, s))) .ok_or(Error::FailedAuthentication)?; - // Initial key derivation from starting point, mixing in alice's ephemeral public and the e0s. - let mut key = Secret(hmac_sha512(&hmac_sha512(&INITIAL_KEY, alice_e0_public.as_bytes()), e0s.as_bytes())); + // Initial key derivation from starting point, mixing in alice's ephemeral public and the es. + let es_key = Secret(hmac_sha512(&hmac_sha512(&INITIAL_KEY, alice_e_public.as_bytes()), noise_es.as_bytes())); // Decrypt the encrypted part of the packet payload and authenticate the above key exchange via AES-GCM auth. let mut c = AesGcm::new( - kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_AES_GCM_ALICE_TO_BOB).first_n::(), + kbkdf512(es_key.as_bytes(), KBKDF_KEY_USAGE_LABEL_AES_GCM_ALICE_TO_BOB).first_n::(), false, ); c.reset_init_gcm(canonical_header_bytes); @@ -912,7 +912,7 @@ impl ReceiveContext { } // Parse payload and get alice's session ID, alice's public blob, metadata, and (if present) Alice's Kyber1024 public. - let (offer_id, alice_session_id, alice_s_public, alice_metadata, alice_e1_public, alice_ratchet_key_fingerprint) = + let (offer_id, alice_session_id, alice_s_public_raw, alice_metadata, alice_e1_public_raw, alice_ratchet_key_fingerprint) = parse_key_offer_after_header(&kex_packet[(HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE)..kex_packet_len], packet_type)?; // We either have a session, in which case they should have supplied a ratchet key fingerprint, or @@ -922,21 +922,22 @@ impl ReceiveContext { } // Extract alice's static NIST P-384 public key from her public blob. - let alice_s_public_p384 = H::extract_p384_static(alice_s_public).ok_or(Error::InvalidPacket)?; + let alice_s_public = Layer::extract_s_public_from_raw(alice_s_public_raw).ok_or(Error::InvalidPacket)?; // Key agreement: both sides' static P-384 keys. - let ss = host - .get_local_s_keypair_p384() - .agree(&alice_s_public_p384) + let noise_ss = host + .get_local_s_keypair() + .agree(&alice_s_public) .ok_or(Error::FailedAuthentication)?; // Mix result of 'ss' agreement into master key. - key = Secret(hmac_sha512(key.as_bytes(), ss.as_bytes())); + let ss_key = Secret(hmac_sha512(es_key.as_bytes(), noise_ss.as_bytes())); + drop(es_key); // Authenticate entire packet with HMAC-SHA384, verifying alice's identity via 'ss' secret that was // just mixed into the key. if !hmac_sha384_2( - kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_HMAC).first_n::<48>(), + kbkdf512(ss_key.as_bytes(), KBKDF_KEY_USAGE_LABEL_HMAC).first_n::<48>(), canonical_header_bytes, &kex_packet_saved_ciphertext[HEADER_SIZE..aes_gcm_tag_end], ) @@ -951,7 +952,7 @@ impl ReceiveContext { // then create new sessions. let (new_session, ratchet_key, ratchet_count) = if let Some(session) = session.as_ref() { // Existing session identity must match the one in this offer. - if !session.remote_s_public_hash.eq(&SHA384::hash(&alice_s_public)) { + if !session.remote_s_public_hash.eq(&SHA384::hash(&alice_s_public_raw)) { return Err(Error::FailedAuthentication); } @@ -960,7 +961,7 @@ impl ReceiveContext { let mut ratchet_key = None; let mut ratchet_count = 0; let state = session.state.read().unwrap(); - for k in state.keys.iter() { + for k in state.session_keys.iter() { if let Some(k) = k.as_ref() { if secret_fingerprint(k.ratchet_key.as_bytes())[..16].eq(alice_ratchet_key_fingerprint) { ratchet_key = Some(k.ratchet_key.clone()); @@ -976,28 +977,28 @@ impl ReceiveContext { (None, ratchet_key, ratchet_count) } else { if let Some((new_session_id, psk, associated_object)) = - host.accept_new_session(self, remote_address, alice_s_public, alice_metadata) + host.accept_new_session(self, remote_address, alice_s_public_raw, alice_metadata) { let header_check_cipher = Aes::new( - kbkdf512(ss.as_bytes(), KBKDF_KEY_USAGE_LABEL_HEADER_CHECK).first_n::(), + kbkdf512(noise_ss.as_bytes(), KBKDF_KEY_USAGE_LABEL_HEADER_CHECK).first_n::(), ); ( - Some(Session:: { + Some(Session:: { id: new_session_id, - associated_object, + user_data: associated_object, send_counter: Counter::new(), psk, - ss, + noise_ss, header_check_cipher, state: RwLock::new(SessionMutableState { remote_session_id: Some(alice_session_id), - keys: [None, None, None], - key_ptr: 0, + session_keys: [None, None, None], + cur_session_key_idx: 0, offer: None, last_remote_offer: current_time, }), - remote_s_public_hash: SHA384::hash(&alice_s_public), - remote_s_public_p384: alice_s_public_p384.as_bytes().clone(), + remote_s_public_hash: SHA384::hash(&alice_s_public_raw), + remote_s_public_raw: alice_s_public.as_bytes().clone(), defrag: Mutex::new(RingBufferMap::new(random::xorshift64_random() as u32)), }), None, @@ -1013,33 +1014,34 @@ impl ReceiveContext { let session = existing_session.as_ref().map_or_else(|| new_session.as_ref().unwrap(), |s| &*s); // Generate our ephemeral NIST P-384 key pair. - let bob_e0_keypair = P384KeyPair::generate(); + let bob_e_keypair = P384KeyPair::generate(); // Key agreement: both sides' ephemeral P-384 public keys. - let e0e0 = bob_e0_keypair.agree(&alice_e0_public).ok_or(Error::FailedAuthentication)?; + let noise_ee = bob_e_keypair.agree(&alice_e_public).ok_or(Error::FailedAuthentication)?; // Key agreement: bob (local) static NIST P-384, alice (remote) ephemeral P-384. - let se0 = bob_e0_keypair.agree(&alice_s_public_p384).ok_or(Error::FailedAuthentication)?; + let noise_se = bob_e_keypair.agree(&alice_s_public).ok_or(Error::FailedAuthentication)?; - // Mix in the psk, the key to this point, our ephemeral public, e0e0, and se0, completing Noise_IK. + // Mix in the psk, the key to this point, our ephemeral public, ee, and se, completing Noise_IK. // // FIPS note: the order of HMAC parameters are flipped here from the usual Noise HMAC(key, X). That's because // NIST/FIPS allows HKDF with HMAC(salt, key) and salt is allowed to be anything. This way if the PSK is not // FIPS compliant the compliance of the entire key derivation is not invalidated. Both inputs are secrets of // fixed size so this shouldn't matter cryptographically. - key = Secret(hmac_sha512( + let noise_ik_key = Secret(hmac_sha512( session.psk.as_bytes(), &hmac_sha512( - &hmac_sha512(&hmac_sha512(key.as_bytes(), bob_e0_keypair.public_key_bytes()), e0e0.as_bytes()), - se0.as_bytes(), + &hmac_sha512(&hmac_sha512(ss_key.as_bytes(), bob_e_keypair.public_key_bytes()), noise_ee.as_bytes()), + noise_se.as_bytes(), ), )); + drop(ss_key); // At this point we've completed Noise_IK key derivation with NIST P-384 ECDH, but now for hybrid and ratcheting... // Generate a Kyber encapsulated ciphertext if Kyber is enabled and the other side sent us a public key. - let (bob_e1_public, e1e1) = if JEDI && alice_e1_public.len() > 0 { - if let Ok((bob_e1_public, e1e1)) = pqc_kyber::encapsulate(alice_e1_public, &mut random::SecureRandom::default()) { + let (bob_e1_public, e1e1) = if JEDI && alice_e1_public_raw.len() > 0 { + if let Ok((bob_e1_public, e1e1)) = pqc_kyber::encapsulate(alice_e1_public_raw, &mut random::SecureRandom::default()) { (Some(bob_e1_public), Some(Secret(e1e1))) } else { return Err(Error::FailedAuthentication); @@ -1055,7 +1057,7 @@ impl ReceiveContext { let mut rp = &mut reply_buf[HEADER_SIZE..]; rp.write_all(&[SESSION_PROTOCOL_VERSION])?; - rp.write_all(bob_e0_keypair.public_key_bytes())?; + rp.write_all(bob_e_keypair.public_key_bytes())?; rp.write_all(&offer_id)?; rp.write_all(&session.id.0.to_le_bytes()[..SESSION_ID_SIZE])?; @@ -1090,7 +1092,7 @@ impl ReceiveContext { // Encrypt reply packet using final Noise_IK key BEFORE mixing hybrid or ratcheting, since the other side // must decrypt before doing these things. let mut c = AesGcm::new( - kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE).first_n::(), + kbkdf512(noise_ik_key.as_bytes(), KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE).first_n::(), true, ); c.reset_init_gcm(reply_canonical_header.as_bytes()); @@ -1100,11 +1102,12 @@ impl ReceiveContext { reply_len += AES_GCM_TAG_SIZE; // Mix ratchet key from previous session key (if any) and Kyber1024 hybrid shared key (if any). + let mut session_key = noise_ik_key; if let Some(ratchet_key) = ratchet_key { - key = Secret(hmac_sha512(ratchet_key.as_bytes(), key.as_bytes())); + session_key = Secret(hmac_sha512(ratchet_key.as_bytes(), session_key.as_bytes())); } if let Some(e1e1) = e1e1.as_ref() { - key = Secret(hmac_sha512(e1e1.as_bytes(), key.as_bytes())); + session_key = Secret(hmac_sha512(e1e1.as_bytes(), session_key.as_bytes())); } // Authenticate packet using HMAC-SHA384 with final key. Note that while the final key now has the Kyber secret @@ -1112,19 +1115,19 @@ impl ReceiveContext { // associated with the remote identity. An attacker who can break NIST P-384 (and has the psk) could MITM the // Kyber exchange, but you'd need a not-yet-existing quantum computer for that. let hmac = hmac_sha384_2( - kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_HMAC).first_n::<48>(), + kbkdf512(session_key.as_bytes(), KBKDF_KEY_USAGE_LABEL_HMAC).first_n::<48>(), reply_canonical_header.as_bytes(), &reply_buf[HEADER_SIZE..reply_len], ); reply_buf[reply_len..(reply_len + HMAC_SIZE)].copy_from_slice(&hmac); reply_len += HMAC_SIZE; - let key = SessionKey::new(key, Role::Bob, current_time, reply_counter, ratchet_count + 1, e1e1.is_some()); + let session_key = SessionKey::new(session_key, Role::Bob, current_time, reply_counter, ratchet_count + 1, e1e1.is_some()); let mut state = session.state.write().unwrap(); let _ = state.remote_session_id.replace(alice_session_id); - let next_key_ptr = (state.key_ptr + 1) % KEY_HISTORY_SIZE; - let _ = state.keys[next_key_ptr].replace(key); + let next_key_ptr = (state.cur_session_key_idx + 1) % KEY_HISTORY_SIZE; + let _ = state.session_keys[next_key_ptr].replace(session_key); drop(state); // Bob now has final key state for this exchange. Yay! Now reply to Alice so she can construct it. @@ -1150,25 +1153,25 @@ impl ReceiveContext { if let Some(session) = session { let state = session.state.read().unwrap(); if let Some(offer) = state.offer.as_ref() { - let (bob_e0_public, e0e0) = + let (bob_e_public, noise_ee) = P384PublicKey::from_bytes(&kex_packet[(HEADER_SIZE + 1)..(HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE)]) - .and_then(|pk| offer.alice_e0_keypair.agree(&pk).map(move |s| (pk, s))) + .and_then(|pk| offer.alice_e_keypair.agree(&pk).map(move |s| (pk, s))) .ok_or(Error::FailedAuthentication)?; - let se0 = host - .get_local_s_keypair_p384() - .agree(&bob_e0_public) + let noise_se = host + .get_local_s_keypair() + .agree(&bob_e_public) .ok_or(Error::FailedAuthentication)?; - let mut key = Secret(hmac_sha512( + let mut noise_ik_key = Secret(hmac_sha512( session.psk.as_bytes(), &hmac_sha512( - &hmac_sha512(&hmac_sha512(offer.key.as_bytes(), bob_e0_public.as_bytes()), e0e0.as_bytes()), - se0.as_bytes(), + &hmac_sha512(&hmac_sha512(offer.ss_key.as_bytes(), bob_e_public.as_bytes()), noise_ee.as_bytes()), + noise_se.as_bytes(), ), )); let mut c = AesGcm::new( - kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE).first_n::(), + kbkdf512(noise_ik_key.as_bytes(), KBKDF_KEY_USAGE_LABEL_AES_GCM_BOB_TO_ALICE).first_n::(), false, ); c.reset_init_gcm(canonical_header_bytes); @@ -1179,7 +1182,7 @@ impl ReceiveContext { // Alice has now completed Noise_IK with NIST P-384 and verified with GCM auth, but now for hybrid... - let (offer_id, bob_session_id, _, _, bob_e1_public, bob_ratchet_key_id) = parse_key_offer_after_header( + let (offer_id, bob_session_id, _, _, bob_e1_public_raw, bob_ratchet_key_id) = parse_key_offer_after_header( &kex_packet[(HEADER_SIZE + 1 + P384_PUBLIC_KEY_SIZE)..kex_packet_len], packet_type, )?; @@ -1188,8 +1191,8 @@ impl ReceiveContext { return Ok(ReceiveResult::Ignored); } - let e1e1 = if JEDI && bob_e1_public.len() > 0 && offer.alice_e1_keypair.is_some() { - if let Ok(e1e1) = pqc_kyber::decapsulate(bob_e1_public, &offer.alice_e1_keypair.as_ref().unwrap().secret) { + let e1e1 = if JEDI && bob_e1_public_raw.len() > 0 && offer.alice_e1_keypair.is_some() { + if let Ok(e1e1) = pqc_kyber::decapsulate(bob_e1_public_raw, &offer.alice_e1_keypair.as_ref().unwrap().secret) { Some(Secret(e1e1)) } else { return Err(Error::FailedAuthentication); @@ -1199,16 +1202,17 @@ impl ReceiveContext { }; let mut ratchet_count = 0; + let mut session_key = noise_ik_key; if bob_ratchet_key_id.is_some() && offer.ratchet_key.is_some() { - key = Secret(hmac_sha512(offer.ratchet_key.as_ref().unwrap().as_bytes(), key.as_bytes())); + session_key = Secret(hmac_sha512(offer.ratchet_key.as_ref().unwrap().as_bytes(), session_key.as_bytes())); ratchet_count = offer.ratchet_count; } if let Some(e1e1) = e1e1.as_ref() { - key = Secret(hmac_sha512(e1e1.as_bytes(), key.as_bytes())); + session_key = Secret(hmac_sha512(e1e1.as_bytes(), session_key.as_bytes())); } if !hmac_sha384_2( - kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_HMAC).first_n::<48>(), + kbkdf512(session_key.as_bytes(), KBKDF_KEY_USAGE_LABEL_HMAC).first_n::<48>(), canonical_header_bytes, &kex_packet_saved_ciphertext[HEADER_SIZE..aes_gcm_tag_end], ) @@ -1220,7 +1224,7 @@ impl ReceiveContext { // Alice has now completed and validated the full hybrid exchange. let counter = session.send_counter.next(); - let key = SessionKey::new(key, Role::Alice, current_time, counter, ratchet_count + 1, e1e1.is_some()); + let session_key = SessionKey::new(session_key, Role::Alice, current_time, counter, ratchet_count + 1, e1e1.is_some()); let mut reply_buf = [0_u8; HEADER_SIZE + AES_GCM_TAG_SIZE]; create_packet_header( @@ -1232,10 +1236,10 @@ impl ReceiveContext { counter, )?; - let mut c = key.get_send_cipher(counter)?; + let mut c = session_key.get_send_cipher(counter)?; c.reset_init_gcm(CanonicalHeader::make(bob_session_id.into(), PACKET_TYPE_NOP, counter.to_u32()).as_bytes()); reply_buf[HEADER_SIZE..].copy_from_slice(&c.finish_encrypt()); - key.return_send_cipher(c); + session_key.return_send_cipher(c); set_header_check_code(&mut reply_buf, &session.header_check_cipher); send(&mut reply_buf); @@ -1243,8 +1247,8 @@ impl ReceiveContext { drop(state); let mut state = session.state.write().unwrap(); let _ = state.remote_session_id.replace(bob_session_id); - let next_key_ptr = (state.key_ptr + 1) % KEY_HISTORY_SIZE; - let _ = state.keys[next_key_ptr].replace(key); + let next_key_idx = (state.cur_session_key_idx + 1) % KEY_HISTORY_SIZE; + let _ = state.session_keys[next_key_idx].replace(session_key); let _ = state.offer.take(); return Ok(ReceiveResult::Ok); @@ -1336,8 +1340,8 @@ struct EphemeralOffer { creation_time: i64, // Local time when offer was created ratchet_count: u64, // Ratchet count starting at zero for initial offer ratchet_key: Option>, // Ratchet key from previous offer - key: Secret<64>, // Shared secret in-progress, at state after offer sent - alice_e0_keypair: P384KeyPair, // NIST P-384 key pair (Noise ephemeral key for Alice) + ss_key: Secret<64>, // Shared secret in-progress, at state after offer sent + alice_e_keypair: P384KeyPair, // NIST P-384 key pair (Noise ephemeral key for Alice) alice_e1_keypair: Option, // Kyber1024 key pair (agreement result mixed post-Noise) } @@ -1358,10 +1362,10 @@ fn send_ephemeral_offer( current_time: i64, ) -> Result, Error> { // Generate a NIST P-384 pair. - let alice_e0_keypair = P384KeyPair::generate(); + let alice_e_keypair = P384KeyPair::generate(); // Perform key agreement with the other side's static P-384 public key. - let e0s = alice_e0_keypair.agree(bob_s_public_p384).ok_or(Error::InvalidPacket)?; + let noise_es = alice_e_keypair.agree(bob_s_public_p384).ok_or(Error::InvalidPacket)?; // Generate a Kyber1024 pair if enabled. let alice_e1_keypair = if JEDI { @@ -1387,7 +1391,7 @@ fn send_ephemeral_offer( let mut p = &mut packet_buf[HEADER_SIZE..]; p.write_all(&[SESSION_PROTOCOL_VERSION])?; - p.write_all(alice_e0_keypair.public_key_bytes())?; + p.write_all(alice_e_keypair.public_key_bytes())?; p.write_all(&id)?; p.write_all(&alice_session_id.0.to_le_bytes()[..SESSION_ID_SIZE])?; @@ -1412,9 +1416,9 @@ fn send_ephemeral_offer( }; // Create ephemeral agreement secret. - let key = Secret(hmac_sha512( - &hmac_sha512(&INITIAL_KEY, alice_e0_keypair.public_key_bytes()), - e0s.as_bytes(), + let es_key = Secret(hmac_sha512( + &hmac_sha512(&INITIAL_KEY, alice_e_keypair.public_key_bytes()), + noise_es.as_bytes(), )); let bob_session_id = bob_session_id.unwrap_or(SessionId::NIL); @@ -1425,7 +1429,7 @@ fn send_ephemeral_offer( // Encrypt packet and attach AES-GCM tag. let gcm_tag = { let mut c = AesGcm::new( - kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_AES_GCM_ALICE_TO_BOB).first_n::(), + kbkdf512(es_key.as_bytes(), KBKDF_KEY_USAGE_LABEL_AES_GCM_ALICE_TO_BOB).first_n::(), true, ); c.reset_init_gcm(canonical_header.as_bytes()); @@ -1436,11 +1440,12 @@ fn send_ephemeral_offer( packet_len += AES_GCM_TAG_SIZE; // Mix in static secret. - let key = Secret(hmac_sha512(key.as_bytes(), ss.as_bytes())); + let ss_key = Secret(hmac_sha512(es_key.as_bytes(), ss.as_bytes())); + drop(es_key); // HMAC packet using static + ephemeral key. let hmac = hmac_sha384_2( - kbkdf512(key.as_bytes(), KBKDF_KEY_USAGE_LABEL_HMAC).first_n::<48>(), + kbkdf512(ss_key.as_bytes(), KBKDF_KEY_USAGE_LABEL_HMAC).first_n::<48>(), canonical_header.as_bytes(), &packet_buf[HEADER_SIZE..packet_len], ); @@ -1468,8 +1473,8 @@ fn send_ephemeral_offer( creation_time: current_time, ratchet_count, ratchet_key, - key, - alice_e0_keypair, + ss_key, + alice_e_keypair, alice_e1_keypair, })) } @@ -1807,15 +1812,15 @@ mod tests { } } - impl Host for Box { - type AssociatedObject = u32; + impl ApplicationLayer for Box { + type SessionUserData = u32; type SessionRef = Arc>>; type IncomingPacketBuffer = Vec; type RemoteAddress = u32; const REKEY_RATE_LIMIT_MS: i64 = 0; - fn get_local_s_public(&self) -> &[u8] { + fn get_local_s_public_raw(&self) -> &[u8] { self.local_s.public_key_bytes() } @@ -1823,15 +1828,15 @@ mod tests { &self.local_s_hash } - fn get_local_s_keypair_p384(&self) -> &P384KeyPair { + fn get_local_s_keypair(&self) -> &P384KeyPair { &self.local_s } - fn extract_p384_static(static_public: &[u8]) -> Option { + fn extract_s_public_from_raw(static_public: &[u8]) -> Option { P384PublicKey::from_bytes(static_public) } - fn session_lookup(&self, local_session_id: SessionId) -> Option { + fn lookup_session(&self, local_session_id: SessionId) -> Option { self.session.lock().unwrap().as_ref().and_then(|s| { if s.id == local_session_id { Some(s.clone()) @@ -1841,7 +1846,7 @@ mod tests { }) } - fn check_new_session_attempt(&self, _: &ReceiveContext, _: &Self::RemoteAddress) -> bool { + fn check_new_session(&self, _: &ReceiveContext, _: &Self::RemoteAddress) -> bool { true } @@ -1851,7 +1856,7 @@ mod tests { _: &u32, _: &[u8], _: &[u8], - ) -> Option<(SessionId, Secret<64>, Self::AssociatedObject)> { + ) -> Option<(SessionId, Secret<64>, Self::SessionUserData)> { loop { let mut new_id = self.session_id_counter.lock().unwrap(); *new_id += 1;