From 45bcd98f08e5d9964b90dff03c5f6e991976d0e9 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Thu, 12 Jan 2023 10:25:29 -0500 Subject: [PATCH] [noise] refactor, actually use rfc6479 in data deencapsulation, check for handshake chain relavency --- quicktap/src/lib.rs | 1 + quicktap/src/noise/data.rs | 8 +- quicktap/src/noise/error.rs | 8 +- quicktap/src/noise/handshake/initiator.rs | 89 +++++++++++----------- quicktap/src/noise/handshake/mod.rs | 6 +- quicktap/src/noise/handshake/response.rs | 92 ++++++++++++----------- quicktap/src/noise/handshake/tests.rs | 16 ++-- quicktap/src/noise/tests.rs | 12 +-- 8 files changed, 125 insertions(+), 107 deletions(-) diff --git a/quicktap/src/lib.rs b/quicktap/src/lib.rs index 07ed6c0..ee0f20c 100644 --- a/quicktap/src/lib.rs +++ b/quicktap/src/lib.rs @@ -10,6 +10,7 @@ // This is an annoyance #![allow(clippy::must_use_candidate)] +#![allow(clippy::module_name_repetitions)] pub use cidr; diff --git a/quicktap/src/noise/data.rs b/quicktap/src/noise/data.rs index 499c7e1..5c901f1 100644 --- a/quicktap/src/noise/data.rs +++ b/quicktap/src/noise/data.rs @@ -53,14 +53,18 @@ pub fn deencapsulate(state: &mut HandshakeState, packet: &[u8]) -> Result p, Err(e) => return Err(NoiseError::ChaCha20Error(e)) }; + + state.bitfield.update_replay_window(counter); state.n_recv = counter; Ok(unpad_packet(&packet)) diff --git a/quicktap/src/noise/error.rs b/quicktap/src/noise/error.rs index af34821..80ab9d6 100644 --- a/quicktap/src/noise/error.rs +++ b/quicktap/src/noise/error.rs @@ -13,6 +13,10 @@ pub enum NoiseError { ChaCha20Error(chacha20poly1305::Error), /// Represents that the packet had a missing or incorrect cookie MAC. PacketUnauthenticated, + /// Represents that the packet had the wrong i_i value + UnrelatedHandshakePacket, + /// Represents that the packet has been replayed and should be dropped + PacketReplayed } impl Error for NoiseError {} impl Display for NoiseError { @@ -20,7 +24,9 @@ impl Display for NoiseError { match &self { Self::PacketParseError(err) => write!(f, "{}", err), Self::ChaCha20Error(error) => write!(f, "Encryption error: {}", error), - Self::PacketUnauthenticated => write!(f, "Unauthenticated packet") + Self::PacketUnauthenticated => write!(f, "Unauthenticated packet"), + Self::UnrelatedHandshakePacket => write!(f, "Unrelated handshake packet"), + Self::PacketReplayed => write!(f, "Packet was replayed") } } } diff --git a/quicktap/src/noise/handshake/initiator.rs b/quicktap/src/noise/handshake/initiator.rs index c76ce08..0e5f0a2 100644 --- a/quicktap/src/noise/handshake/initiator.rs +++ b/quicktap/src/noise/handshake/initiator.rs @@ -11,6 +11,48 @@ use crate::qcrypto::hashes::{qcrypto_hash_twice, qcrypto_mac}; use crate::qcrypto::hkdf::qcrypto_hkdf; use crate::qcrypto::pki::{qcrypto_dh_generate_longterm, qcrypto_dh_longterm}; +struct HandshakeInitiatorRaw { + sender: [u8; 4], + ephemeral: [u8; 32], + static_pub: [u8; 32 + 16], + timestamp: [u8; 12 + 16], + mac1: [u8; 16], + mac2: [u8; 16] +} +impl HandshakeInitiatorRaw { + fn to_bytes(&self, session: &HandshakeState) -> [u8; 148] { + let mut output = [0u8; 148]; + + output[0] = 1u8; + output[4..8].copy_from_slice(&self.sender); + output[8..40].copy_from_slice(&self.ephemeral); + output[40..88].copy_from_slice(&self.static_pub); + output[88..116].copy_from_slice(&self.timestamp); + + let mac1: [u8; 16] = qcrypto_mac(&qcrypto_hash_twice(LABEL_MAC1.as_bytes(), session.s_pub_i.as_bytes()), &output[..116]); + + output[116..132].copy_from_slice(&mac1); + + let mac2 = if needs_cookie(session) { qcrypto_mac(&session.cookies[session.cookies.len() - 1].cookie, &output[..132]) } else { [0u8; 16] }; + + output[132..148].copy_from_slice(&mac2); + + output + } + + #[allow(clippy::unwrap_used)] // Only used for type conversions in known safe ways + fn from_bytes(bytes: [u8; 148]) -> Self { + Self { + sender: bytes[4..8].try_into().unwrap(), + ephemeral: bytes[8..40].try_into().unwrap(), + static_pub: bytes[40..88].try_into().unwrap(), + timestamp: bytes[88..116].try_into().unwrap(), + mac1: bytes[116..132].try_into().unwrap(), + mac2: bytes[132..148].try_into().unwrap() + } + } +} + /// Generate a handshake initiator packet and encrypt it using the given session state, starting a new handshake state /// # Errors /// This function will error if encryption was unsuccessful @@ -18,7 +60,7 @@ use crate::qcrypto::pki::{qcrypto_dh_generate_longterm, qcrypto_dh_longterm}; /// While containing unwraps, this function will never panic. #[allow(clippy::module_name_repetitions)] #[allow(clippy::unwrap_used)] // Safe because it is only used for type conversions in known safe ways -pub fn handshake_init_to(session: &mut HandshakeState) -> Result<[u8; 148], NoiseError> { +pub fn generate_handshake_init(session: &mut HandshakeState) -> Result<[u8; 148], NoiseError> { session.we_are_initiator = true; session.s_pub_i = PublicKey::from(session.s_priv_me); session.s_pub_r = session.s_pub_them; @@ -75,49 +117,6 @@ pub fn handshake_init_to(session: &mut HandshakeState) -> Result<[u8; 148], Nois Ok(msg.to_bytes(session)) } - -struct HandshakeInitiatorRaw { - sender: [u8; 4], - ephemeral: [u8; 32], - static_pub: [u8; 32 + 16], - timestamp: [u8; 12 + 16], - mac1: [u8; 16], - mac2: [u8; 16] -} -impl HandshakeInitiatorRaw { - fn to_bytes(&self, session: &HandshakeState) -> [u8; 148] { - let mut output = [0u8; 148]; - - output[0] = 1u8; - output[4..8].copy_from_slice(&self.sender); - output[8..40].copy_from_slice(&self.ephemeral); - output[40..88].copy_from_slice(&self.static_pub); - output[88..116].copy_from_slice(&self.timestamp); - - let mac1: [u8; 16] = qcrypto_mac(&qcrypto_hash_twice(LABEL_MAC1.as_bytes(), session.s_pub_i.as_bytes()), &output[..116]); - - output[116..132].copy_from_slice(&mac1); - - let mac2 = if needs_cookie(session) { qcrypto_mac(&session.cookies[session.cookies.len() - 1].cookie, &output[..132]) } else { [0u8; 16] }; - - output[132..148].copy_from_slice(&mac2); - - output - } - - #[allow(clippy::unwrap_used)] // Only used for type conversions in known safe ways - fn from_bytes(bytes: [u8; 148]) -> Self { - Self { - sender: bytes[4..8].try_into().unwrap(), - ephemeral: bytes[8..40].try_into().unwrap(), - static_pub: bytes[40..88].try_into().unwrap(), - timestamp: bytes[88..116].try_into().unwrap(), - mac1: bytes[116..132].try_into().unwrap(), - mac2: bytes[132..148].try_into().unwrap() - } - } -} - /// Parse a handshake initiator packet and encrypt it using the given session state, updating the session state with decrypted and authenticated values /// # Errors /// This function will error if decryption was unsuccessful @@ -125,7 +124,7 @@ impl HandshakeInitiatorRaw { /// While containing unwraps, this function will never panic. #[allow(clippy::module_name_repetitions)] #[allow(clippy::unwrap_used)] // Only used for type conversions in known safe ways -pub fn handshake_init_from(session: &mut HandshakeState, packet: [u8; 148]) -> Result<(), NoiseError> { +pub fn parse_handshake_init(session: &mut HandshakeState, packet: [u8; 148]) -> Result<(), NoiseError> { let s_pub_i = session.s_pub_them; let s_pub_r = PublicKey::from(session.s_priv_me); diff --git a/quicktap/src/noise/handshake/mod.rs b/quicktap/src/noise/handshake/mod.rs index 26fa284..dac6963 100644 --- a/quicktap/src/noise/handshake/mod.rs +++ b/quicktap/src/noise/handshake/mod.rs @@ -4,6 +4,7 @@ use std::fmt::{Debug, Formatter}; use rand::rngs::OsRng; use tai64::Tai64N; use x25519_dalek::{PublicKey, StaticSecret}; +use crate::noise::rfc6479::ShiftWindow; use crate::qcrypto::hkdf::qcrypto_hkdf; use crate::qcrypto::timestamp; @@ -63,7 +64,9 @@ pub struct HandshakeState<'a> { pub n_send: u64, pub n_recv: u64, - pub we_are_initiator: bool + pub we_are_initiator: bool, + + pub bitfield: ShiftWindow } impl<'a> HandshakeState<'a> { /// Determines if the state variables of this `HandshakeState` are the same as another @@ -114,6 +117,7 @@ impl<'a> HandshakeState<'a> { n_send: 0, n_recv: 0, we_are_initiator: false, + bitfield: ShiftWindow::new() } } } diff --git a/quicktap/src/noise/handshake/response.rs b/quicktap/src/noise/handshake/response.rs index c3e9e6a..b18d3c2 100644 --- a/quicktap/src/noise/handshake/response.rs +++ b/quicktap/src/noise/handshake/response.rs @@ -10,13 +10,55 @@ use crate::qcrypto::hkdf::qcrypto_hkdf; use crate::qcrypto::LABEL_MAC1; use crate::qcrypto::pki::{qcrypto_dh_generate_longterm, qcrypto_dh_longterm}; +struct HandshakeResponseRaw { + sender: [u8; 4], + receiver: [u8; 4], + ephemeral: [u8; 32], + empty: [u8; 16], + mac1: [u8; 16], + mac2: [u8; 16] +} +impl HandshakeResponseRaw { + fn to_bytes(&self, session: &mut HandshakeState) -> [u8; 92] { + let mut output_array = [0u8; 92]; + + output_array[0] = 2u8; + output_array[4..8].copy_from_slice(&self.sender); + output_array[8..12].copy_from_slice(&self.receiver); + output_array[12..44].copy_from_slice(&self.ephemeral); + output_array[44..60].copy_from_slice(&self.empty); + + let mac1: [u8; 16] = qcrypto_mac(&qcrypto_hash_twice(LABEL_MAC1.as_bytes(), session.s_pub_i.as_bytes()), &output_array[..60]); + + output_array[60..76].copy_from_slice(&mac1); + + let mac2 = if needs_cookie(session) { qcrypto_mac(&session.cookies[session.cookies.len() - 1].cookie, &output_array[..76]) } else { [0u8; 16] }; + + output_array[76..92].copy_from_slice(&mac2); + + output_array + } + + #[allow(clippy::unwrap_used)] // Only used for type conversions in known safe ways + fn from_bytes(packet: [u8; 92]) -> Self { + Self { + sender: packet[4..8].try_into().unwrap(), + receiver: packet[8..12].try_into().unwrap(), + ephemeral: packet[12..44].try_into().unwrap(), + empty: packet[44..60].try_into().unwrap(), + mac1: packet[60..76].try_into().unwrap(), + mac2: packet[76..92].try_into().unwrap() + } + } +} + /// Creates a handshake response packet using the current active handshake session. /// # Errors /// This function will error if an encryption step is unsuccessful /// # Panics /// This function, while containing unwraps, will never panic. #[allow(clippy::unwrap_used)] // Used for known safe type conversions only -pub fn handshake_response_to(session: &mut HandshakeState) -> Result<[u8; 92], NoiseError> { +pub fn generate_handshake_response(session: &mut HandshakeState) -> Result<[u8; 92], NoiseError> { let eph_keypair = qcrypto_dh_generate_longterm(); let mut msg = HandshakeResponseRaw { @@ -60,7 +102,7 @@ pub fn handshake_response_to(session: &mut HandshakeState) -> Result<[u8; 92], N /// This function will error if a decryption step is unsuccessful /// # Panics /// This function, while containing unwraps, will never panic. -pub fn handshake_response_from(session: &mut HandshakeState, packet: [u8; 92]) -> Result<(), NoiseError> { +pub fn parse_handshake_response(session: &mut HandshakeState, packet: [u8; 92]) -> Result<(), NoiseError> { let msg = HandshakeResponseRaw::from_bytes(packet); let e_pub_r = PublicKey::from(msg.ephemeral); @@ -99,51 +141,13 @@ pub fn handshake_response_from(session: &mut HandshakeState, packet: [u8; 92]) - return Err(NoiseError::PacketUnauthenticated) } + if msg.receiver != session.i_i.to_le_bytes() { + return Err(NoiseError::UnrelatedHandshakePacket) + } + session.e_pub_r = e_pub_r; session.ck = ck; session.h = h; Ok(()) -} - -struct HandshakeResponseRaw { - sender: [u8; 4], - receiver: [u8; 4], - ephemeral: [u8; 32], - empty: [u8; 16], - mac1: [u8; 16], - mac2: [u8; 16] -} -impl HandshakeResponseRaw { - fn to_bytes(&self, session: &mut HandshakeState) -> [u8; 92] { - let mut output_array = [0u8; 92]; - - output_array[0] = 2u8; - output_array[4..8].copy_from_slice(&self.sender); - output_array[8..12].copy_from_slice(&self.receiver); - output_array[12..44].copy_from_slice(&self.ephemeral); - output_array[44..60].copy_from_slice(&self.empty); - - let mac1: [u8; 16] = qcrypto_mac(&qcrypto_hash_twice(LABEL_MAC1.as_bytes(), session.s_pub_i.as_bytes()), &output_array[..60]); - - output_array[60..76].copy_from_slice(&mac1); - - let mac2 = if needs_cookie(session) { qcrypto_mac(&session.cookies[session.cookies.len() - 1].cookie, &output_array[..76]) } else { [0u8; 16] }; - - output_array[76..92].copy_from_slice(&mac2); - - output_array - } - - #[allow(clippy::unwrap_used)] // Only used for type conversions in known safe ways - fn from_bytes(packet: [u8; 92]) -> Self { - Self { - sender: packet[4..8].try_into().unwrap(), - receiver: packet[8..12].try_into().unwrap(), - ephemeral: packet[12..44].try_into().unwrap(), - empty: packet[44..60].try_into().unwrap(), - mac1: packet[60..76].try_into().unwrap(), - mac2: packet[76..92].try_into().unwrap() - } - } } \ No newline at end of file diff --git a/quicktap/src/noise/handshake/tests.rs b/quicktap/src/noise/handshake/tests.rs index 036f26b..1e1c815 100644 --- a/quicktap/src/noise/handshake/tests.rs +++ b/quicktap/src/noise/handshake/tests.rs @@ -1,8 +1,8 @@ #![allow(clippy::unwrap_used)] // this is a test harness, we want to panic use crate::noise::handshake::HandshakeState; -use crate::noise::handshake::initiator::{handshake_init_from, handshake_init_to}; -use crate::noise::handshake::response::{handshake_response_from, handshake_response_to}; +use crate::noise::handshake::initiator::{parse_handshake_init, generate_handshake_init}; +use crate::noise::handshake::response::{parse_handshake_response, generate_handshake_response}; use crate::qcrypto::pki::qcrypto_dh_generate_longterm; #[test] @@ -13,8 +13,8 @@ fn noise_halfhandshake_test() { let mut alice_session = HandshakeState::new(&alice_keypair.0, bob_keypair.1, None); let mut bob_session = HandshakeState::new(&bob_keypair.0, alice_keypair.1, None); - let handshake_init = handshake_init_to(&mut alice_session).unwrap(); - handshake_init_from(&mut bob_session, handshake_init).unwrap(); + let handshake_init = generate_handshake_init(&mut alice_session).unwrap(); + parse_handshake_init(&mut bob_session, handshake_init).unwrap(); println!("{:?}", alice_session); println!("{:?}", bob_session); @@ -30,11 +30,11 @@ fn noise_nocookie_handshake_test() { let mut alice_session = HandshakeState::new(&alice_keypair.0, bob_keypair.1, None); let mut bob_session = HandshakeState::new(&bob_keypair.0, alice_keypair.1, None); - let handshake_init = handshake_init_to(&mut alice_session).unwrap(); - handshake_init_from(&mut bob_session, handshake_init).unwrap(); + let handshake_init = generate_handshake_init(&mut alice_session).unwrap(); + parse_handshake_init(&mut bob_session, handshake_init).unwrap(); - let handshake_response = handshake_response_to(&mut bob_session).unwrap(); - handshake_response_from(&mut alice_session, handshake_response).unwrap(); + let handshake_response = generate_handshake_response(&mut bob_session).unwrap(); + parse_handshake_response(&mut alice_session, handshake_response).unwrap(); println!("{:?}", alice_session); println!("{:?}", bob_session); diff --git a/quicktap/src/noise/tests.rs b/quicktap/src/noise/tests.rs index c9f7a35..ce92331 100644 --- a/quicktap/src/noise/tests.rs +++ b/quicktap/src/noise/tests.rs @@ -3,8 +3,8 @@ use crate::noise::data::{deencapsulate, encapsulate}; use crate::noise::handshake::HandshakeState; -use crate::noise::handshake::initiator::{handshake_init_from, handshake_init_to}; -use crate::noise::handshake::response::{handshake_response_from, handshake_response_to}; +use crate::noise::handshake::initiator::{parse_handshake_init, generate_handshake_init}; +use crate::noise::handshake::response::{parse_handshake_response, generate_handshake_response}; use crate::qcrypto::pki::qcrypto_dh_generate_longterm; #[test] @@ -15,11 +15,11 @@ pub fn noise_transport_test() { let mut alice_session = HandshakeState::new(&alice_keypair.0, bob_keypair.1, None); let mut bob_session = HandshakeState::new(&bob_keypair.0, alice_keypair.1, None); - let handshake_init = handshake_init_to(&mut alice_session).unwrap(); - handshake_init_from(&mut bob_session, handshake_init).unwrap(); + let handshake_init = generate_handshake_init(&mut alice_session).unwrap(); + parse_handshake_init(&mut bob_session, handshake_init).unwrap(); - let handshake_response = handshake_response_to(&mut bob_session).unwrap(); - handshake_response_from(&mut alice_session, handshake_response).unwrap(); + let handshake_response = generate_handshake_response(&mut bob_session).unwrap(); + parse_handshake_response(&mut alice_session, handshake_response).unwrap(); alice_session.derive_transport(); bob_session.derive_transport();