[noise] refactor, actually use rfc6479 in data deencapsulation, check for handshake chain relavency

This commit is contained in:
c0repwn3r 2023-01-12 10:25:29 -05:00
parent e1093cab1b
commit 45bcd98f08
Signed by: core
GPG Key ID: FDBF740DADDCEECF
8 changed files with 125 additions and 107 deletions

View File

@ -10,6 +10,7 @@
// This is an annoyance // This is an annoyance
#![allow(clippy::must_use_candidate)] #![allow(clippy::must_use_candidate)]
#![allow(clippy::module_name_repetitions)]
pub use cidr; pub use cidr;

View File

@ -53,14 +53,18 @@ pub fn deencapsulate(state: &mut HandshakeState, packet: &[u8]) -> Result<Vec<u8
return Err(NoiseError::PacketParseError(NoisePacketParseError::WrongIndex(index_me as usize, usize::from_le_bytes(packet[4..8].try_into().unwrap())))) return Err(NoiseError::PacketParseError(NoisePacketParseError::WrongIndex(index_me as usize, usize::from_le_bytes(packet[4..8].try_into().unwrap()))))
} }
let counter = u64::from_le_bytes(packet[8..16].try_into().unwrap()); let counter = u64::from_le_bytes(packet[8..16].try_into().unwrap());
if state.n_recv == counter {
if !state.bitfield.check_replay_window(counter) {
// this packet is not okay! replayed or too old
return Err(NoiseError::PacketReplayed)
} }
// TODO: Check if this counter is okay, sliding windows and stuff. Don't feel like implementing it fully right now
let packet = match qcrypto_aead_decrypt(&state.t_recv, counter, &packet[16..], &[]) { let packet = match qcrypto_aead_decrypt(&state.t_recv, counter, &packet[16..], &[]) {
Ok(p) => p, Ok(p) => p,
Err(e) => return Err(NoiseError::ChaCha20Error(e)) Err(e) => return Err(NoiseError::ChaCha20Error(e))
}; };
state.bitfield.update_replay_window(counter);
state.n_recv = counter; state.n_recv = counter;
Ok(unpad_packet(&packet)) Ok(unpad_packet(&packet))

View File

@ -13,6 +13,10 @@ pub enum NoiseError {
ChaCha20Error(chacha20poly1305::Error), ChaCha20Error(chacha20poly1305::Error),
/// Represents that the packet had a missing or incorrect cookie MAC. /// Represents that the packet had a missing or incorrect cookie MAC.
PacketUnauthenticated, 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 Error for NoiseError {}
impl Display for NoiseError { impl Display for NoiseError {
@ -20,7 +24,9 @@ impl Display for NoiseError {
match &self { match &self {
Self::PacketParseError(err) => write!(f, "{}", err), Self::PacketParseError(err) => write!(f, "{}", err),
Self::ChaCha20Error(error) => write!(f, "Encryption error: {}", error), 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")
} }
} }
} }

View File

@ -11,6 +11,48 @@ use crate::qcrypto::hashes::{qcrypto_hash_twice, qcrypto_mac};
use crate::qcrypto::hkdf::qcrypto_hkdf; use crate::qcrypto::hkdf::qcrypto_hkdf;
use crate::qcrypto::pki::{qcrypto_dh_generate_longterm, qcrypto_dh_longterm}; 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 /// Generate a handshake initiator packet and encrypt it using the given session state, starting a new handshake state
/// # Errors /// # Errors
/// This function will error if encryption was unsuccessful /// 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. /// While containing unwraps, this function will never panic.
#[allow(clippy::module_name_repetitions)] #[allow(clippy::module_name_repetitions)]
#[allow(clippy::unwrap_used)] // Safe because it is only used for type conversions in known safe ways #[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.we_are_initiator = true;
session.s_pub_i = PublicKey::from(session.s_priv_me); session.s_pub_i = PublicKey::from(session.s_priv_me);
session.s_pub_r = session.s_pub_them; 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)) 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 /// Parse a handshake initiator packet and encrypt it using the given session state, updating the session state with decrypted and authenticated values
/// # Errors /// # Errors
/// This function will error if decryption was unsuccessful /// This function will error if decryption was unsuccessful
@ -125,7 +124,7 @@ impl HandshakeInitiatorRaw {
/// While containing unwraps, this function will never panic. /// While containing unwraps, this function will never panic.
#[allow(clippy::module_name_repetitions)] #[allow(clippy::module_name_repetitions)]
#[allow(clippy::unwrap_used)] // Only used for type conversions in known safe ways #[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_i = session.s_pub_them;
let s_pub_r = PublicKey::from(session.s_priv_me); let s_pub_r = PublicKey::from(session.s_priv_me);

View File

@ -4,6 +4,7 @@ use std::fmt::{Debug, Formatter};
use rand::rngs::OsRng; use rand::rngs::OsRng;
use tai64::Tai64N; use tai64::Tai64N;
use x25519_dalek::{PublicKey, StaticSecret}; use x25519_dalek::{PublicKey, StaticSecret};
use crate::noise::rfc6479::ShiftWindow;
use crate::qcrypto::hkdf::qcrypto_hkdf; use crate::qcrypto::hkdf::qcrypto_hkdf;
use crate::qcrypto::timestamp; use crate::qcrypto::timestamp;
@ -63,7 +64,9 @@ pub struct HandshakeState<'a> {
pub n_send: u64, pub n_send: u64,
pub n_recv: u64, pub n_recv: u64,
pub we_are_initiator: bool pub we_are_initiator: bool,
pub bitfield: ShiftWindow
} }
impl<'a> HandshakeState<'a> { impl<'a> HandshakeState<'a> {
/// Determines if the state variables of this `HandshakeState` are the same as another /// 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_send: 0,
n_recv: 0, n_recv: 0,
we_are_initiator: false, we_are_initiator: false,
bitfield: ShiftWindow::new()
} }
} }
} }

View File

@ -10,13 +10,55 @@ use crate::qcrypto::hkdf::qcrypto_hkdf;
use crate::qcrypto::LABEL_MAC1; use crate::qcrypto::LABEL_MAC1;
use crate::qcrypto::pki::{qcrypto_dh_generate_longterm, qcrypto_dh_longterm}; 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. /// Creates a handshake response packet using the current active handshake session.
/// # Errors /// # Errors
/// This function will error if an encryption step is unsuccessful /// This function will error if an encryption step is unsuccessful
/// # Panics /// # Panics
/// This function, while containing unwraps, will never panic. /// This function, while containing unwraps, will never panic.
#[allow(clippy::unwrap_used)] // Used for known safe type conversions only #[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 eph_keypair = qcrypto_dh_generate_longterm();
let mut msg = HandshakeResponseRaw { 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 /// This function will error if a decryption step is unsuccessful
/// # Panics /// # Panics
/// This function, while containing unwraps, will never panic. /// 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 msg = HandshakeResponseRaw::from_bytes(packet);
let e_pub_r = PublicKey::from(msg.ephemeral); 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) 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.e_pub_r = e_pub_r;
session.ck = ck; session.ck = ck;
session.h = h; session.h = h;
Ok(()) 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()
}
}
} }

View File

@ -1,8 +1,8 @@
#![allow(clippy::unwrap_used)] // this is a test harness, we want to panic #![allow(clippy::unwrap_used)] // this is a test harness, we want to panic
use crate::noise::handshake::HandshakeState; use crate::noise::handshake::HandshakeState;
use crate::noise::handshake::initiator::{handshake_init_from, handshake_init_to}; use crate::noise::handshake::initiator::{parse_handshake_init, generate_handshake_init};
use crate::noise::handshake::response::{handshake_response_from, handshake_response_to}; use crate::noise::handshake::response::{parse_handshake_response, generate_handshake_response};
use crate::qcrypto::pki::qcrypto_dh_generate_longterm; use crate::qcrypto::pki::qcrypto_dh_generate_longterm;
#[test] #[test]
@ -13,8 +13,8 @@ fn noise_halfhandshake_test() {
let mut alice_session = HandshakeState::new(&alice_keypair.0, bob_keypair.1, None); 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 mut bob_session = HandshakeState::new(&bob_keypair.0, alice_keypair.1, None);
let handshake_init = handshake_init_to(&mut alice_session).unwrap(); let handshake_init = generate_handshake_init(&mut alice_session).unwrap();
handshake_init_from(&mut bob_session, handshake_init).unwrap(); parse_handshake_init(&mut bob_session, handshake_init).unwrap();
println!("{:?}", alice_session); println!("{:?}", alice_session);
println!("{:?}", bob_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 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 mut bob_session = HandshakeState::new(&bob_keypair.0, alice_keypair.1, None);
let handshake_init = handshake_init_to(&mut alice_session).unwrap(); let handshake_init = generate_handshake_init(&mut alice_session).unwrap();
handshake_init_from(&mut bob_session, handshake_init).unwrap(); parse_handshake_init(&mut bob_session, handshake_init).unwrap();
let handshake_response = handshake_response_to(&mut bob_session).unwrap(); let handshake_response = generate_handshake_response(&mut bob_session).unwrap();
handshake_response_from(&mut alice_session, handshake_response).unwrap(); parse_handshake_response(&mut alice_session, handshake_response).unwrap();
println!("{:?}", alice_session); println!("{:?}", alice_session);
println!("{:?}", bob_session); println!("{:?}", bob_session);

View File

@ -3,8 +3,8 @@
use crate::noise::data::{deencapsulate, encapsulate}; use crate::noise::data::{deencapsulate, encapsulate};
use crate::noise::handshake::HandshakeState; use crate::noise::handshake::HandshakeState;
use crate::noise::handshake::initiator::{handshake_init_from, handshake_init_to}; use crate::noise::handshake::initiator::{parse_handshake_init, generate_handshake_init};
use crate::noise::handshake::response::{handshake_response_from, handshake_response_to}; use crate::noise::handshake::response::{parse_handshake_response, generate_handshake_response};
use crate::qcrypto::pki::qcrypto_dh_generate_longterm; use crate::qcrypto::pki::qcrypto_dh_generate_longterm;
#[test] #[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 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 mut bob_session = HandshakeState::new(&bob_keypair.0, alice_keypair.1, None);
let handshake_init = handshake_init_to(&mut alice_session).unwrap(); let handshake_init = generate_handshake_init(&mut alice_session).unwrap();
handshake_init_from(&mut bob_session, handshake_init).unwrap(); parse_handshake_init(&mut bob_session, handshake_init).unwrap();
let handshake_response = handshake_response_to(&mut bob_session).unwrap(); let handshake_response = generate_handshake_response(&mut bob_session).unwrap();
handshake_response_from(&mut alice_session, handshake_response).unwrap(); parse_handshake_response(&mut alice_session, handshake_response).unwrap();
alice_session.derive_transport(); alice_session.derive_transport();
bob_session.derive_transport(); bob_session.derive_transport();