diff --git a/quicktap/src/noise/handshake/initiator.rs b/quicktap/src/noise/handshake/initiator.rs index e5e1737..24694c6 100644 --- a/quicktap/src/noise/handshake/initiator.rs +++ b/quicktap/src/noise/handshake/initiator.rs @@ -7,7 +7,7 @@ use crate::noise::handshake::{HANDSHAKE_INITIATOR_CHAIN_KEY, HANDSHAKE_INITIATOR 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::pki::{qcrypto_dh_ephemeral, qcrypto_dh_generate_ephemeral, qcrypto_dh_generate_longterm, 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 @@ -35,16 +35,17 @@ pub fn handshake_init_to(session: &mut HandshakeState) -> Result<[u8; 148], Nois 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(); + let eph_keypair = qcrypto_dh_generate_longterm(); session.ck = qcrypto_hkdf::<1>(&session.ck, eph_keypair.1.as_bytes())[0]; session.e_pub_i = eph_keypair.1; + session.e_priv_me = eph_keypair.0; 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()); + let ci_k_pair = qcrypto_hkdf::<2>(&session.ck, qcrypto_dh_longterm(&session.e_priv_me, &session.s_pub_r).as_bytes()); session.ck = ci_k_pair[0]; let k = ci_k_pair[1]; @@ -94,7 +95,7 @@ impl HandshakeInitiatorRaw { 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] }; + 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); @@ -168,7 +169,6 @@ pub fn handshake_init_from(session: &mut HandshakeState, packet: [u8; 148]) -> R // 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 { diff --git a/quicktap/src/noise/handshake/mod.rs b/quicktap/src/noise/handshake/mod.rs index 7972881..958deee 100644 --- a/quicktap/src/noise/handshake/mod.rs +++ b/quicktap/src/noise/handshake/mod.rs @@ -37,17 +37,20 @@ pub struct HandshakeState { pub ck: [u8; 32], pub e_pub_i: PublicKey, + pub e_pub_r: PublicKey, pub s_pub_i: PublicKey, pub s_pub_r: PublicKey, - pub e_priv_me: EphemeralSecret, + pub e_priv_me: StaticSecret, pub s_priv_me: StaticSecret, pub s_pub_them: PublicKey, pub i_i: u32, pub i_r: u32, + pub q: [u8; 32], + pub cookies: Vec } impl HandshakeState { @@ -65,8 +68,6 @@ impl Debug for HandshakeState { .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) diff --git a/quicktap/src/noise/handshake/response.rs b/quicktap/src/noise/handshake/response.rs index 7189050..93ae5d3 100644 --- a/quicktap/src/noise/handshake/response.rs +++ b/quicktap/src/noise/handshake/response.rs @@ -1,14 +1,116 @@ //! `Noise_IKpsk2` handshake response packet +use x25519_dalek::PublicKey; +use crate::noise::error::NoiseError; use crate::noise::handshake::{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::LABEL_MAC1; +use crate::qcrypto::pki::{qcrypto_dh_ephemeral, qcrypto_dh_generate_ephemeral, qcrypto_dh_generate_longterm, qcrypto_dh_longterm}; + +/// 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. +pub fn handshake_response_to(session: &mut HandshakeState) -> Result<[u8; 92], NoiseError> { + let eph_keypair = qcrypto_dh_generate_longterm(); + + let mut msg = HandshakeResponseRaw { + sender: [0u8; 4], + receiver: [0u8; 4], + ephemeral: [0u8; 32], + empty: [0u8; 16], + mac1: [0u8; 16], + mac2: [0u8; 16] + }; + + session.ck = qcrypto_hkdf::<1>(&session.ck, eph_keypair.1.as_bytes())[0]; + + msg.ephemeral = eph_keypair.1.to_bytes(); + + session.h = qcrypto_hash_twice(&session.h, &msg.ephemeral); + + session.ck = qcrypto_hkdf::<1>(&session.ck, qcrypto_dh_longterm(&eph_keypair.0, &session.e_pub_i).as_bytes())[0]; + session.ck = qcrypto_hkdf::<1>(&session.ck, qcrypto_dh_longterm(&eph_keypair.0, &session.s_pub_i).as_bytes())[0]; + + let cr_t_k = qcrypto_hkdf::<3>(&session.ck, &session.q); + session.ck = cr_t_k[0]; + let t = cr_t_k[1]; + let k = cr_t_k[2]; + + session.h = qcrypto_hash_twice(&session.h, &t); + + println!("{:?} {:?} {:?}", k, 0, session.h); + msg.empty = match qcrypto_aead(&k, 0, &[], &session.h) { + Ok(s) => s.try_into().unwrap(), + Err(e) => return Err(NoiseError::ChaCha20Error(e)) + }; + + session.h = qcrypto_hash_twice(&session.h, &msg.empty); + + Ok(msg.to_bytes(session)) +} + +/// Decrypts a handshake response packet using the current active handshake session. +/// # Errors +/// 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> { + let mut msg = HandshakeResponseRaw::from_bytes(packet); + + let e_pub_r = PublicKey::from(msg.ephemeral); + + let mut ck = qcrypto_hkdf::<1>(&session.ck, e_pub_r.as_bytes())[0]; + let mut h = qcrypto_hash_twice(&session.h, &msg.ephemeral); + + ck = qcrypto_hkdf::<1>(&ck, qcrypto_dh_longterm(&session.e_priv_me, &e_pub_r).as_bytes())[0]; + ck = qcrypto_hkdf::<1>(&ck, qcrypto_dh_longterm(&session.s_priv_me, &e_pub_r).as_bytes())[0]; + + let cr_t_k = qcrypto_hkdf::<3>(&ck, &session.q); + ck = cr_t_k[0]; + let t = cr_t_k[1]; + let k = cr_t_k[2]; + + h = qcrypto_hash_twice(&h, &t); + + println!("here"); + println!("{:?} {:?} {:?}", k, 0, h); + + let empty = match qcrypto_aead_decrypt(&k, 0, &msg.empty, &h) { + Ok(s) => s, + Err(e) => return Err(NoiseError::ChaCha20Error(e)) + }; + + if !empty.is_empty() { + return Err(NoiseError::PacketUnauthenticated) + } + + h = qcrypto_hash_twice(&h, &msg.empty); + + let mac1: [u8; 16] = qcrypto_mac(&qcrypto_hash_twice(LABEL_MAC1.as_bytes(), session.s_pub_i.as_bytes()), &packet[..60]); + let mac2 = if needs_cookie(session) { qcrypto_mac(&session.cookies[session.cookies.len() - 1].cookie, &packet[..76]) } else { [0u8; 16] }; + + if mac1 != msg.mac1 || mac2 != msg.mac2 { + return Err(NoiseError::PacketUnauthenticated) + } + + 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; 0 + 16], + mac1: [u8; 16], + mac2: [u8; 16] } impl HandshakeResponseRaw { fn to_bytes(&self, session: &mut HandshakeState) -> [u8; 92] { @@ -20,13 +122,13 @@ impl HandshakeResponseRaw { 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[..116]); + let mac1: [u8; 16] = qcrypto_mac(&qcrypto_hash_twice(LABEL_MAC1.as_bytes(), session.s_pub_i.as_bytes()), &output_array[..60]); - output_array[116..132].copy_from_slice(&mac1); + 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[..132]) } else { [0u8; 16] }; + let mac2 = if needs_cookie(session) { qcrypto_mac(&session.cookies[session.cookies.len() - 1].cookie, &output_array[..76]) } else { [0u8; 16] }; - output_array[132..148].copy_from_slice(&mac2); + output_array[76..92].copy_from_slice(&mac2); output_array } @@ -37,6 +139,8 @@ impl HandshakeResponseRaw { 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 a6018f6..1dd567d 100644 --- a/quicktap/src/noise/handshake/tests.rs +++ b/quicktap/src/noise/handshake/tests.rs @@ -1,7 +1,8 @@ use rand::rngs::OsRng; -use x25519_dalek::{EphemeralSecret, PublicKey}; +use x25519_dalek::{EphemeralSecret, PublicKey, StaticSecret}; 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::qcrypto::pki::qcrypto_dh_generate_longterm; #[test] @@ -13,26 +14,30 @@ fn noise_halfhandshake_test() { h: [0u8; 32], ck: [0u8; 32], e_pub_i: PublicKey::from([0u8; 32]), + e_pub_r: PublicKey::from([0u8; 32]), s_pub_i: PublicKey::from([0u8; 32]), s_pub_r: PublicKey::from([0u8; 32]), - e_priv_me: EphemeralSecret::new(OsRng), + e_priv_me: StaticSecret::new(OsRng), s_priv_me: alice_keypair.0, s_pub_them: bob_keypair.1, i_i: 0, i_r: 0, + q: [0u8; 32], cookies: vec![], }; let mut bob_session = HandshakeState { h: [0u8; 32], ck: [0u8; 32], e_pub_i: PublicKey::from([0u8; 32]), + e_pub_r: PublicKey::from([0u8; 32]), s_pub_i: PublicKey::from([0u8; 32]), s_pub_r: PublicKey::from([0u8; 32]), - e_priv_me: EphemeralSecret::new(OsRng), + e_priv_me: StaticSecret::new(OsRng), s_priv_me: bob_keypair.0, s_pub_them: alice_keypair.1, i_i: 0, i_r: 0, + q: [0u8; 32], cookies: vec![], }; @@ -42,5 +47,53 @@ fn noise_halfhandshake_test() { println!("{:?}", alice_session); println!("{:?}", bob_session); + assert!(alice_session.is_eq(&bob_session)); +} + +#[test] +fn noise_nocookie_handshake_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]), + e_pub_r: PublicKey::from([0u8; 32]), + s_pub_i: PublicKey::from([0u8; 32]), + s_pub_r: PublicKey::from([0u8; 32]), + e_priv_me: StaticSecret::new(OsRng), + s_priv_me: alice_keypair.0, + s_pub_them: bob_keypair.1, + i_i: 0, + i_r: 0, + q: [0u8; 32], + cookies: vec![], + }; + let mut bob_session = HandshakeState { + h: [0u8; 32], + ck: [0u8; 32], + e_pub_i: PublicKey::from([0u8; 32]), + e_pub_r: PublicKey::from([0u8; 32]), + s_pub_i: PublicKey::from([0u8; 32]), + s_pub_r: PublicKey::from([0u8; 32]), + e_priv_me: StaticSecret::new(OsRng), + s_priv_me: bob_keypair.0, + s_pub_them: alice_keypair.1, + i_i: 0, + i_r: 0, + q: [0u8; 32], + cookies: vec![], + }; + + let handshake_init = handshake_init_to(&mut alice_session).unwrap(); + handshake_init_from(&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(); + + println!("{:?}", alice_session); + println!("{:?}", bob_session); + assert!(alice_session.is_eq(&bob_session)); } \ No newline at end of file