diff --git a/quicktap/src/noise/handshake/initiator.rs b/quicktap/src/noise/handshake/initiator.rs new file mode 100644 index 0000000..e5e1737 --- /dev/null +++ b/quicktap/src/noise/handshake/initiator.rs @@ -0,0 +1,186 @@ +//! `Noise_IKpsk2` handshake initiator packets + +use rand::{Rng, thread_rng}; +use x25519_dalek::{PublicKey}; +use crate::noise::error::NoiseError; +use crate::noise::handshake::{HANDSHAKE_INITIATOR_CHAIN_KEY, HANDSHAKE_INITIATOR_CHAIN_KEY_HASH, HandshakeState, needs_cookie}; +use crate::qcrypto::aead::{qcrypto_aead, qcrypto_aead_decrypt}; +use crate::qcrypto::hashes::{qcrypto_hash_twice, qcrypto_mac}; +use crate::qcrypto::hkdf::qcrypto_hkdf; +use crate::qcrypto::pki::{qcrypto_dh_ephemeral, qcrypto_dh_generate_ephemeral, qcrypto_dh_longterm}; +use crate::qcrypto::{LABEL_MAC1, timestamp}; + +/// 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 +/// # Panics +/// While containing unwraps, this function will never panic. +#[allow(clippy::module_name_repetitions)] +pub fn handshake_init_to(session: &mut HandshakeState) -> Result<[u8; 148], NoiseError> { + session.s_pub_i = PublicKey::from(&session.s_priv_me); + session.s_pub_r = session.s_pub_them; + + session.i_i = thread_rng().gen(); + + let mut msg = HandshakeInitiatorRaw { + sender: session.i_i.to_le_bytes(), + ephemeral: [0u8; 32], + static_pub: [0u8; 32 + 16], + timestamp: [0u8; 12 + 16], + mac1: [0u8; 16], + mac2: [0u8; 16] + }; + + session.ck = HANDSHAKE_INITIATOR_CHAIN_KEY; + session.h = HANDSHAKE_INITIATOR_CHAIN_KEY_HASH; + session.h = qcrypto_hash_twice(&session.h, session.s_pub_r.as_bytes()); + + let eph_keypair = qcrypto_dh_generate_ephemeral(); + + session.ck = qcrypto_hkdf::<1>(&session.ck, eph_keypair.1.as_bytes())[0]; + session.e_pub_i = eph_keypair.1; + + msg.ephemeral = eph_keypair.1.to_bytes(); + + session.h = qcrypto_hash_twice(&session.h, &msg.ephemeral); + + let ci_k_pair = qcrypto_hkdf::<2>(&session.ck, qcrypto_dh_ephemeral(eph_keypair.0, &session.s_pub_r).as_bytes()); + session.ck = ci_k_pair[0]; + let k = ci_k_pair[1]; + + // This unwrap is safe because the output length is a known constant with these inputs + msg.static_pub = match qcrypto_aead(&k, 0, session.s_pub_i.as_bytes(), &session.h) { + Ok(s) => s, + Err(e) => return Err(NoiseError::ChaCha20Error(e)) + }.try_into().unwrap(); + + session.h = qcrypto_hash_twice(&session.h, &msg.static_pub); + + let ci_k_pair = qcrypto_hkdf::<2>(&session.ck, qcrypto_dh_longterm(&session.s_priv_me, &session.s_pub_r).as_bytes()); + session.ck = ci_k_pair[0]; + let k = ci_k_pair[1]; + + // This unwrap is safe because the output length is a known constant with these inputs + msg.timestamp = match qcrypto_aead(&k, 0, ×tamp().to_bytes(), &session.h) { + Ok(s) => s, + Err(e) => return Err(NoiseError::ChaCha20Error(e)) + }.try_into().unwrap(); + + session.h = qcrypto_hash_twice(&session.h, &msg.timestamp); + + 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, &packet[..132]) } else { [0u8; 16] }; + + output[132..148].copy_from_slice(&mac2); + + output + } + + 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 +/// # Panics +/// While containing unwraps, this function will never panic. +#[allow(clippy::module_name_repetitions)] +pub fn handshake_init_from(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); + + let msg = HandshakeInitiatorRaw::from_bytes(packet); + + let i_i = u32::from_le_bytes(msg.sender); + + let ck = HANDSHAKE_INITIATOR_CHAIN_KEY; + let h = HANDSHAKE_INITIATOR_CHAIN_KEY_HASH; + let h = qcrypto_hash_twice(&h, s_pub_r.as_bytes()); + + let ephemeral_public = msg.ephemeral; + let eph_pub = PublicKey::from(ephemeral_public); + let e_pub_i = eph_pub; + + let ck = qcrypto_hkdf::<1>(&ck, eph_pub.as_bytes())[0]; + + let h = qcrypto_hash_twice(&h, &msg.ephemeral); + + let ci_k_pair = qcrypto_hkdf::<2>(&ck, qcrypto_dh_longterm(&session.s_priv_me, &eph_pub).as_bytes()); + let ck = ci_k_pair[0]; + let k = ci_k_pair[1]; + + // This unwrap is safe because the output length is a known constant with these inputs + + + session.s_pub_i = PublicKey::from( as TryInto<[u8; 32]>>::try_into(match qcrypto_aead_decrypt(&k, 0, &msg.static_pub, &h) { + Ok(s) => s, + Err(e) => return Err(NoiseError::ChaCha20Error(e)) + }).unwrap()); + + let h = qcrypto_hash_twice(&h, &msg.static_pub); + + let ci_k_pair = qcrypto_hkdf::<2>(&ck, qcrypto_dh_longterm(&session.s_priv_me, &session.s_pub_i).as_bytes()); + let ck = ci_k_pair[0]; + let k = ci_k_pair[1]; + + // This unwrap is safe because the output length is a known constant with these inputs + let _sent_timestamp: [u8; 12] = match qcrypto_aead_decrypt(&k, 0, &msg.timestamp, &h) { + Ok(s) => s, + Err(e) => return Err(NoiseError::ChaCha20Error(e)) + }.try_into().unwrap(); + + let h = qcrypto_hash_twice(&h, &msg.timestamp); + + // we need to check mac1 and mac2 + + let mac1: [u8; 16] = qcrypto_mac(&qcrypto_hash_twice(LABEL_MAC1.as_bytes(), session.s_pub_i.as_bytes()), &packet[..116]); + + let mac2 = if needs_cookie(session) { qcrypto_mac(&session.cookies[session.cookies.len() - 1].cookie, &packet[..132]) } else { [0u8; 16] }; + + if mac1 != msg.mac1 || mac2 != msg.mac2 { + return Err(NoiseError::PacketUnauthenticated) + } + + session.h = h; + session.ck = ck; + session.e_pub_i = e_pub_i; + session.s_pub_i = s_pub_i; + session.s_pub_r = s_pub_r; + session.i_i = i_i; + + Ok(()) +} \ No newline at end of file diff --git a/quicktap/src/noise/handshake/mod.rs b/quicktap/src/noise/handshake/mod.rs new file mode 100644 index 0000000..7972881 --- /dev/null +++ b/quicktap/src/noise/handshake/mod.rs @@ -0,0 +1,86 @@ +//! `Noise_IKpsk2` handshake, specifically the way WireGuard defines it +use std::fmt::{Debug, Formatter}; +use tai64::Tai64N; +use x25519_dalek::{EphemeralSecret, PublicKey, StaticSecret}; +use crate::qcrypto::timestamp; + +pub mod initiator; +pub mod response; + +#[cfg(test)] +pub mod tests; + +/// The Blake2s hash of the construction +pub const HANDSHAKE_INITIATOR_CHAIN_KEY: [u8; 32] = [ + 96, 226, 109, 174, 243, 39, 239, 192, 46, 195, 53, 226, 160, 37, 210, 208, 22, 235, 66, 6, 248, + 114, 119, 245, 45, 56, 209, 152, 139, 120, 205, 54, +]; + +/// The hashed chaining key +pub const HANDSHAKE_INITIATOR_CHAIN_KEY_HASH: [u8; 32] = [ + 34, 17, 179, 97, 8, 26, 197, 102, 105, 18, 67, 219, 69, 138, 213, 50, 45, 156, 108, 102, 34, + 147, 232, 183, 14, 225, 156, 101, 186, 7, 158, 243, +]; + +/// Represents a cookie we got from the other peer +#[derive(Debug)] +pub struct Cookie { + time: Tai64N, + cookie: [u8; 16] +} + +/// Represents the internal handshake state. This does not really need to be messed with by outside users +#[allow(missing_docs)] +#[allow(clippy::module_name_repetitions)] +pub struct HandshakeState { + pub h: [u8; 32], + pub ck: [u8; 32], + + pub e_pub_i: PublicKey, + + pub s_pub_i: PublicKey, + pub s_pub_r: PublicKey, + + pub e_priv_me: EphemeralSecret, + pub s_priv_me: StaticSecret, + pub s_pub_them: PublicKey, + + pub i_i: u32, + pub i_r: u32, + + pub cookies: Vec +} +impl HandshakeState { + /// Determines if the state variables of this `HandshakeState` are the same as another + #[allow(clippy::suspicious_operation_groupings)] + pub fn is_eq(&self, other: &HandshakeState) -> bool { + self.h == other.h && self.ck == other.ck && self.e_pub_i == other.e_pub_i && self.s_pub_i == other.s_pub_i && self.s_pub_r == other.s_pub_r && self.i_i == other.i_i && self.i_r == other.i_r + } +} +impl Debug for HandshakeState { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("HandshakeState") + .field("h", &self.h) + .field("ck", &self.ck) + .field("e_pub_i", &self.e_pub_i) + .field("s_pub_i", &self.s_pub_i) + .field("s_pub_r", &self.s_pub_r) + .field("e_priv_me", &"") + .field("s_priv_me", &"") + .field("s_pub_them", &self.s_pub_them) + .field("i_i", &self.i_i) + .field("i_r", &self.i_r) + .field("cookies", &self.cookies).finish() + } +} + +/// Determines if a cookie MAC needs to be added to the packet being sent +pub fn needs_cookie(session: &HandshakeState) -> bool { + if !session.cookies.is_empty() { + let past_cookie_timeout = session.cookies[session.cookies.len()-1].time.duration_since(×tamp()).map_or(true, |t| t.as_secs() >= 120); + if !past_cookie_timeout { + return true; + } + } + false +} \ No newline at end of file diff --git a/quicktap/src/noise/handshake/response.rs b/quicktap/src/noise/handshake/response.rs new file mode 100644 index 0000000..f323277 --- /dev/null +++ b/quicktap/src/noise/handshake/response.rs @@ -0,0 +1 @@ +//! `Noise_IKpsk2` handshake response packet diff --git a/quicktap/src/noise/handshake/tests.rs b/quicktap/src/noise/handshake/tests.rs new file mode 100644 index 0000000..a6018f6 --- /dev/null +++ b/quicktap/src/noise/handshake/tests.rs @@ -0,0 +1,46 @@ +use rand::rngs::OsRng; +use x25519_dalek::{EphemeralSecret, PublicKey}; +use crate::noise::handshake::HandshakeState; +use crate::noise::handshake::initiator::{handshake_init_from, handshake_init_to}; +use crate::qcrypto::pki::qcrypto_dh_generate_longterm; + +#[test] +fn noise_halfhandshake_test() { + let alice_keypair = qcrypto_dh_generate_longterm(); + let bob_keypair = qcrypto_dh_generate_longterm(); + + let mut alice_session = HandshakeState { + h: [0u8; 32], + ck: [0u8; 32], + e_pub_i: PublicKey::from([0u8; 32]), + s_pub_i: PublicKey::from([0u8; 32]), + s_pub_r: PublicKey::from([0u8; 32]), + e_priv_me: EphemeralSecret::new(OsRng), + s_priv_me: alice_keypair.0, + s_pub_them: bob_keypair.1, + i_i: 0, + i_r: 0, + cookies: vec![], + }; + let mut bob_session = HandshakeState { + h: [0u8; 32], + ck: [0u8; 32], + e_pub_i: PublicKey::from([0u8; 32]), + s_pub_i: PublicKey::from([0u8; 32]), + s_pub_r: PublicKey::from([0u8; 32]), + e_priv_me: EphemeralSecret::new(OsRng), + s_priv_me: bob_keypair.0, + s_pub_them: alice_keypair.1, + i_i: 0, + i_r: 0, + cookies: vec![], + }; + + let handshake_init = handshake_init_to(&mut alice_session).unwrap(); + handshake_init_from(&mut bob_session, handshake_init).unwrap(); + + println!("{:?}", alice_session); + println!("{:?}", bob_session); + + assert!(alice_session.is_eq(&bob_session)); +} \ No newline at end of file diff --git a/quicktap/src/noise/handshake_init.rs b/quicktap/src/noise/handshake_init.rs deleted file mode 100644 index 1dea804..0000000 --- a/quicktap/src/noise/handshake_init.rs +++ /dev/null @@ -1,261 +0,0 @@ -//! `Noise_IKpsk2` handshake initiator packets - -use std::fmt::{Debug, Formatter}; -use std::mem; -use rand::{Rng, thread_rng}; -use tai64::Tai64N; -use x25519_dalek::{EphemeralSecret, PublicKey, StaticSecret}; -use crate::noise::error::NoiseError; -use crate::qcrypto::aead::{qcrypto_aead, qcrypto_aead_decrypt}; -use crate::qcrypto::hashes::{qcrypto_hash_twice, qcrypto_mac}; -use crate::qcrypto::hkdf::qcrypto_hkdf; -use crate::qcrypto::pki::{qcrypto_dh_ephemeral, qcrypto_dh_generate_ephemeral, qcrypto_dh_longterm}; -use crate::qcrypto::{LABEL_MAC1, timestamp}; - -/// The Blake2s hash of the construction -pub const HANDSHAKE_INITIATOR_CHAIN_KEY: [u8; 32] = [ - 96, 226, 109, 174, 243, 39, 239, 192, 46, 195, 53, 226, 160, 37, 210, 208, 22, 235, 66, 6, 248, - 114, 119, 245, 45, 56, 209, 152, 139, 120, 205, 54, -]; - -/// The hashed chaining key -pub const HANDSHAKE_INITIATOR_CHAIN_KEY_HASH: [u8; 32] = [ - 34, 17, 179, 97, 8, 26, 197, 102, 105, 18, 67, 219, 69, 138, 213, 50, 45, 156, 108, 102, 34, - 147, 232, 183, 14, 225, 156, 101, 186, 7, 158, 243, -]; - -/// Represents a cookie we got from the other peer -#[derive(Debug)] -pub struct Cookie { - time: Tai64N, - cookie: [u8; 16] -} - -/// Represents the internal handshake state. This does not really need to be messed with by outside users -#[allow(missing_docs)] -#[allow(clippy::module_name_repetitions)] -pub struct HandshakeState { - pub h_i: [u8; 32], - pub c_i: [u8; 32], - - pub e_pub_i: PublicKey, - - pub s_pub_i: PublicKey, - pub s_pub_r: PublicKey, - - pub e_priv_me: EphemeralSecret, - pub s_priv_me: StaticSecret, - pub s_pub_them: PublicKey, - - pub i_i: u32, - pub i_r: u32, - - pub cookies: Vec -} -impl HandshakeState { - /// Determines if the state variables of this `HandshakeState` are the same as another - pub fn is_eq(&self, other: &HandshakeState) -> bool { - self.h_i == other.h_i && self.c_i == other.c_i && self.e_pub_i == other.e_pub_i && self.s_pub_i == other.s_pub_i && self.s_pub_r == other.s_pub_r && self.i_i == other.i_i && self.i_r == other.i_r - } -} -impl Debug for HandshakeState { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("HandshakeState") - .field("h_i", &self.h_i) - .field("c_i", &self.c_i) - .field("e_pub_i", &self.e_pub_i) - .field("s_pub_i", &self.s_pub_i) - .field("s_pub_r", &self.s_pub_r) - .field("e_priv_me", &"") - .field("s_priv_me", &"") - .field("s_pub_them", &self.s_pub_them) - .field("i_i", &self.i_i) - .field("i_r", &self.i_r) - .field("cookies", &self.cookies).finish() - } -} - -/// 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 -/// # Panics -/// While containing unwraps, this function will never panic. -#[allow(clippy::module_name_repetitions)] -pub fn handshake_init_to(session: &mut HandshakeState) -> Result<[u8; 148], NoiseError> { - session.s_pub_i = PublicKey::from(&session.s_priv_me); - session.s_pub_r = session.s_pub_them; - - session.i_i = thread_rng().gen(); - - let mut msg = HandshakeInitiatorRaw { - sender: session.i_i.to_le_bytes(), - ephemeral: [0u8; 32], - static_pub: [0u8; 32 + 16], - timestamp: [0u8; 12 + 16], - mac1: [0u8; 16], - mac2: [0u8; 16] - }; - - session.c_i = HANDSHAKE_INITIATOR_CHAIN_KEY; - session.h_i = HANDSHAKE_INITIATOR_CHAIN_KEY_HASH; - session.h_i = qcrypto_hash_twice(&session.h_i, session.s_pub_r.as_bytes()); - - let eph_keypair = qcrypto_dh_generate_ephemeral(); - - session.c_i = qcrypto_hkdf::<1>(&session.c_i, eph_keypair.1.as_bytes())[0]; - session.e_pub_i = eph_keypair.1; - - msg.ephemeral = eph_keypair.1.to_bytes(); - - session.h_i = qcrypto_hash_twice(&session.h_i, &msg.ephemeral); - - let ci_k_pair = qcrypto_hkdf::<2>(&session.c_i, qcrypto_dh_ephemeral(eph_keypair.0, &session.s_pub_r).as_bytes()); - session.c_i = ci_k_pair[0]; - let k = ci_k_pair[1]; - - // This unwrap is safe because the output length is a known constant with these inputs - msg.static_pub = match qcrypto_aead(&k, 0, session.s_pub_i.as_bytes(), &session.h_i) { - Ok(s) => s, - Err(e) => return Err(NoiseError::ChaCha20Error(e)) - }.try_into().unwrap(); - - session.h_i = qcrypto_hash_twice(&session.h_i, &msg.static_pub); - - let ci_k_pair = qcrypto_hkdf::<2>(&session.c_i, qcrypto_dh_longterm(&session.s_priv_me, &session.s_pub_r).as_bytes()); - session.c_i = ci_k_pair[0]; - let k = ci_k_pair[1]; - - // This unwrap is safe because the output length is a known constant with these inputs - msg.timestamp = match qcrypto_aead(&k, 0, ×tamp().to_bytes(), &session.h_i) { - Ok(s) => s, - Err(e) => return Err(NoiseError::ChaCha20Error(e)) - }.try_into().unwrap(); - - session.h_i = qcrypto_hash_twice(&session.h_i, &msg.timestamp); - - 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 mut mac2 = [0u8; 16]; - - if !session.cookies.is_empty() { - let past_cookie_timeout = session.cookies[session.cookies.len()-1].time.duration_since(×tamp()).map_or(true, |t| t.as_secs() >= 120); - if !past_cookie_timeout { - mac2 = qcrypto_mac(&session.cookies[session.cookies.len()-1].cookie, &output[..132]); - } - } - - output[132..148].copy_from_slice(&mac2); - - output - } - - 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 -/// # Panics -/// While containing unwraps, this function will never panic. -#[allow(clippy::module_name_repetitions)] -pub fn handshake_init_from(session_orig: &mut HandshakeState, packet: [u8; 148]) -> Result<(), NoiseError> { - let mut session = session_orig.clone(); - session.s_pub_i = session.s_pub_them; - session.s_pub_r = PublicKey::from(&session.s_priv_me); - - let mut msg = HandshakeInitiatorRaw::from_bytes(packet); - - session.i_i = u32::from_le_bytes(msg.sender); - - session.c_i = HANDSHAKE_INITIATOR_CHAIN_KEY; - session.h_i = HANDSHAKE_INITIATOR_CHAIN_KEY_HASH; - session.h_i = qcrypto_hash_twice(&session.h_i, session.s_pub_r.as_bytes()); - - let ephemeral_public = msg.ephemeral; - let eph_pub = PublicKey::from(ephemeral_public); - session.e_pub_i = eph_pub; - - session.c_i = qcrypto_hkdf::<1>(&session.c_i, eph_pub.as_bytes())[0]; - - session.h_i = qcrypto_hash_twice(&session.h_i, &msg.ephemeral); - - let ci_k_pair = qcrypto_hkdf::<2>(&session.c_i, qcrypto_dh_longterm(&session.s_priv_me, &eph_pub).as_bytes()); - session.c_i = ci_k_pair[0]; - let k = ci_k_pair[1]; - - // This unwrap is safe because the output length is a known constant with these inputs - - - session.s_pub_i = PublicKey::from( as TryInto<[u8; 32]>>::try_into(match qcrypto_aead_decrypt(&k, 0, &msg.static_pub, &session.h_i) { - Ok(s) => s, - Err(e) => return Err(NoiseError::ChaCha20Error(e)) - }).unwrap()); - - session.h_i = qcrypto_hash_twice(&session.h_i, &msg.static_pub); - - let ci_k_pair = qcrypto_hkdf::<2>(&session.c_i, qcrypto_dh_longterm(&session.s_priv_me, &session.s_pub_i).as_bytes()); - session.c_i = ci_k_pair[0]; - let k = ci_k_pair[1]; - - // This unwrap is safe because the output length is a known constant with these inputs - let sent_timestamp: [u8; 12] = match qcrypto_aead_decrypt(&k, 0, &msg.timestamp, &session.h_i) { - Ok(s) => s, - Err(e) => return Err(NoiseError::ChaCha20Error(e)) - }.try_into().unwrap(); - - session.h_i = qcrypto_hash_twice(&session.h_i, &msg.timestamp); - - // we need to check mac1 and mac2 - - let mac1: [u8; 16] = qcrypto_mac(&qcrypto_hash_twice(LABEL_MAC1.as_bytes(), session.s_pub_i.as_bytes()), &packet[..116]); - - let mut mac2 = [0u8; 16]; - - if !session.cookies.is_empty() { - let past_cookie_timeout = session.cookies[session.cookies.len()-1].time.duration_since(×tamp()).map_or(true, |t| t.as_secs() >= 120); - if !past_cookie_timeout { - mac2 = qcrypto_mac(&session.cookies[session.cookies.len() - 1].cookie, &packet[..132]); - } - } - - if mac1 != msg.mac1 || mac2 != msg.mac2 { - return Err(NoiseError::PacketUnauthenticated) - } - - mem::swap(session_orig, &mut session); - - Ok(()) -} \ No newline at end of file diff --git a/quicktap/src/noise/mod.rs b/quicktap/src/noise/mod.rs index 26b8674..ecac1d8 100644 --- a/quicktap/src/noise/mod.rs +++ b/quicktap/src/noise/mod.rs @@ -1,4 +1,4 @@ //! Contains structs and functions for serializing and deserializing different packets in the Noise_IKpsk2 handshake and data frames -pub mod handshake_init; +pub mod handshake; pub mod error; \ No newline at end of file diff --git a/quicktap/src/qcrypto/tests.rs b/quicktap/src/qcrypto/tests.rs index 620c8f4..0ff5e02 100644 --- a/quicktap/src/qcrypto/tests.rs +++ b/quicktap/src/qcrypto/tests.rs @@ -1,7 +1,5 @@ use hex_lit::hex; -use rand::rngs::OsRng; -use x25519_dalek::{EphemeralSecret, PublicKey, StaticSecret}; -use crate::noise::handshake_init::{handshake_init_from, handshake_init_to, HandshakeState}; +use x25519_dalek::{PublicKey}; use crate::qcrypto::aead::{qcrypto_aead, qcrypto_aead_decrypt, qcrypto_xaead, qcrypto_xaead_decrypt}; use crate::qcrypto::{CONSTURCTION, IDENTIFIER}; use crate::qcrypto::hashes::{qcrypto_hash, qcrypto_hash_twice, qcrypto_hmac, qcrypto_mac}; @@ -58,45 +56,4 @@ fn qcrypto_xaead_test() { fn qcrypto_hkdf_test() { let derived = qcrypto_hkdf::<1>(&[0u8; 32], &[0u8; 32]); assert_eq!(derived, [hex!("1090894613df8aef670b0b867e222daebc0d3e436cdddbc16c65855ab93cc91a")]); -} - -#[test] -fn noise_halfhandshake_test() { - let alice_keypair = qcrypto_dh_generate_longterm(); - let bob_keypair = qcrypto_dh_generate_longterm(); - - let mut alice_session = HandshakeState { - h_i: [0u8; 32], - c_i: [0u8; 32], - e_pub_i: PublicKey::from([0u8; 32]), - s_pub_i: PublicKey::from([0u8; 32]), - s_pub_r: PublicKey::from([0u8; 32]), - e_priv_me: EphemeralSecret::new(OsRng), - s_priv_me: alice_keypair.0, - s_pub_them: bob_keypair.1, - i_i: 0, - i_r: 0, - cookies: vec![], - }; - let mut bob_session = HandshakeState { - h_i: [0u8; 32], - c_i: [0u8; 32], - e_pub_i: PublicKey::from([0u8; 32]), - s_pub_i: PublicKey::from([0u8; 32]), - s_pub_r: PublicKey::from([0u8; 32]), - e_priv_me: EphemeralSecret::new(OsRng), - s_priv_me: bob_keypair.0, - s_pub_them: alice_keypair.1, - i_i: 0, - i_r: 0, - cookies: vec![], - }; - - let handshake_init = handshake_init_to(&mut alice_session).unwrap(); - handshake_init_from(&mut bob_session, handshake_init).unwrap(); - - println!("{:?}", alice_session); - println!("{:?}", bob_session); - - assert!(alice_session.is_eq(&bob_session)); } \ No newline at end of file