From 78fb32b253e6548a66fdcb42772e83aa1da332b1 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Thu, 12 Jan 2023 13:30:11 -0500 Subject: [PATCH] [noise][stack] fixup initiator to increase security and allow it to initialize HandshakeStates even when we dont know the other parties public key prior to initialization --- quicktap/src/device/mod.rs | 37 ++++++++++++++++++++++- quicktap/src/drivers/udp_listener.rs | 11 ++++--- quicktap/src/noise/handshake/initiator.rs | 9 +++--- quicktap/src/stack/entry.rs | 14 --------- quicktap/src/stack/entry_udp.rs | 15 +++++++++ quicktap/src/stack/mod.rs | 5 +-- 6 files changed, 64 insertions(+), 27 deletions(-) delete mode 100644 quicktap/src/stack/entry.rs create mode 100644 quicktap/src/stack/entry_udp.rs diff --git a/quicktap/src/device/mod.rs b/quicktap/src/device/mod.rs index 32b9ee5..a364239 100644 --- a/quicktap/src/device/mod.rs +++ b/quicktap/src/device/mod.rs @@ -1,2 +1,37 @@ //! A high-level implementation of the `WireGuard` protocol; does everything the kernel module does except for interfacing with the networking interfaces. -//! Use the appropriate glue layer in `quicktap::drivers` to provide the tun/tap interface, and the mechanism in `quicktap::drivers::udp_listener` for working with the `UdpSocket`. \ No newline at end of file +//! Use the appropriate glue layer in `quicktap::drivers` to provide the tun/tap interface, and the mechanism in `quicktap::drivers::udp_listener` for working with the `UdpSocket`. + +use std::collections::HashMap; +use x25519_dalek::PublicKey; +use crate::noise::handshake::HandshakeState; + +#[derive(Default)] +/// A high-level struct for the configuration of a `WireGuard` device. This is the equivalent of `[Interface]` in wg-quick. +pub struct QTInterface<'a> { + peers: HashMap> +} +impl<'a> QTInterface<'a> { + /// Create a new, blank `QTInterface` with no configuration + pub fn new() -> Self { + QTInterface { + peers: HashMap::default(), + } + } +} + +/// A high-level struct for the current state with another peer. This is the equivalent of `[Peer]` in wg-quick, with additional tunneling information added. +pub struct QTPeer<'a> { + handshake: Option>, + handshake_in_flight: Option>, + public_key: PublicKey +} +impl<'a> QTPeer<'a> { + /// Create a new, blank `QTPeer` with no configuration + pub fn new(key: PublicKey) -> Self { + QTPeer { + handshake: None, + handshake_in_flight: None, + public_key: key, + } + } +} \ No newline at end of file diff --git a/quicktap/src/drivers/udp_listener.rs b/quicktap/src/drivers/udp_listener.rs index 388763a..22b9a26 100644 --- a/quicktap/src/drivers/udp_listener.rs +++ b/quicktap/src/drivers/udp_listener.rs @@ -3,8 +3,9 @@ use std::error::Error; use std::io; use std::net::UdpSocket; +use crate::device::QTInterface; use crate::drivers::error::DriverError; -use crate::stack::entry::PacketEntryStage; +use crate::stack::entry_udp::UDPPacketEntryStage; use crate::stack::PacketProcessingStage; /// The maximum amount of bytes that can be taken up by a Handshake Initiation packet. @@ -29,7 +30,7 @@ pub enum SocketRoundResult { /// If you're writing a frontend, this is probably not what you want. Use `qtap_socket_rounds_until_empty` instead. /// # Errors /// This function will error if a socket read fails or the processing of the packet fails. See `PacketEntryStage::process_packet()` for more details on packet processing. -pub fn qtap_socket_round(socket: &mut UdpSocket) -> Result<(), SocketRoundResult> { +pub fn qtap_socket_round(socket: &mut UdpSocket, interface: &QTInterface) -> Result<(), SocketRoundResult> { let mut packet_id = [0u8; 1]; match socket.peek(&mut packet_id) { Ok(_) => (), @@ -54,7 +55,7 @@ pub fn qtap_socket_round(socket: &mut UdpSocket) -> Result<(), SocketRoundResult let packet = packet_buf[..read_bytes].to_vec(); - match PacketEntryStage::process_packet(packet, &from) { + match UDPPacketEntryStage::process_packet(packet, &from, interface) { Ok(_) => Ok(()), Err(e) => Err(SocketRoundResult::GenericAnyError(e)) } @@ -65,9 +66,9 @@ pub fn qtap_socket_round(socket: &mut UdpSocket) -> Result<(), SocketRoundResult /// If you only want to run one update, use `qtap_socket_round` instead. /// # Errors /// See `qtap_socket_round` -pub fn qtap_socket_rounds_until_empty(socket: &mut UdpSocket) -> Result<(), Box> { +pub fn qtap_socket_rounds_until_empty(socket: &mut UdpSocket, interface: &QTInterface) -> Result<(), Box> { loop { - match qtap_socket_round(socket) { + match qtap_socket_round(socket, interface) { Ok(_) => (), Err(e) => { match e { diff --git a/quicktap/src/noise/handshake/initiator.rs b/quicktap/src/noise/handshake/initiator.rs index 0e5f0a2..c759c73 100644 --- a/quicktap/src/noise/handshake/initiator.rs +++ b/quicktap/src/noise/handshake/initiator.rs @@ -117,7 +117,7 @@ pub fn generate_handshake_init(session: &mut HandshakeState) -> Result<[u8; 148] Ok(msg.to_bytes(session)) } -/// 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 decrypt 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 @@ -125,7 +125,6 @@ pub fn generate_handshake_init(session: &mut HandshakeState) -> Result<[u8; 148] #[allow(clippy::module_name_repetitions)] #[allow(clippy::unwrap_used)] // Only used for type conversions in known safe ways 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); let msg = HandshakeInitiatorRaw::from_bytes(packet); @@ -151,14 +150,14 @@ pub fn parse_handshake_init(session: &mut HandshakeState, packet: [u8; 148]) -> // 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) { + let 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 ci_k_pair = qcrypto_hkdf::<2>(&ck, qcrypto_dh_longterm(session.s_priv_me, &s_pub_i).as_bytes()); let ck = ci_k_pair[0]; let k = ci_k_pair[1]; @@ -172,7 +171,7 @@ pub fn parse_handshake_init(session: &mut HandshakeState, packet: [u8; 148]) -> // 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 mac1: [u8; 16] = qcrypto_mac(&qcrypto_hash_twice(LABEL_MAC1.as_bytes(), 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/stack/entry.rs b/quicktap/src/stack/entry.rs deleted file mode 100644 index 569ef10..0000000 --- a/quicktap/src/stack/entry.rs +++ /dev/null @@ -1,14 +0,0 @@ -//! The ENTRY stage of the packet processing stack. Provides a `PacketEntryStage` which implements `PacketProcessingStage`. - -use std::error::Error; -use std::net::SocketAddr; -use crate::stack::{PacketProcessingStage}; - -/// The ENTRY stage of the packet processing stack -pub struct PacketEntryStage {} - -impl PacketProcessingStage for PacketEntryStage { - fn process_packet(pkt: Vec, from: &SocketAddr) -> Result<(), Box> { - Ok(()) - } -} \ No newline at end of file diff --git a/quicktap/src/stack/entry_udp.rs b/quicktap/src/stack/entry_udp.rs new file mode 100644 index 0000000..5687456 --- /dev/null +++ b/quicktap/src/stack/entry_udp.rs @@ -0,0 +1,15 @@ +//! The ENTRY stage of the packet processing stack. Provides a `PacketEntryStage` which implements `PacketProcessingStage`. + +use std::error::Error; +use std::net::SocketAddr; +use crate::device::QTInterface; +use crate::stack::{PacketProcessingStage}; + +/// The ENTRY stage of the packet processing stack for UDP packets +pub struct UDPPacketEntryStage {} + +impl PacketProcessingStage for UDPPacketEntryStage { + fn process_packet(pkt: Vec, from: &SocketAddr, interface: &QTInterface) -> Result<(), Box> { + unimplemented!(); + } +} \ No newline at end of file diff --git a/quicktap/src/stack/mod.rs b/quicktap/src/stack/mod.rs index f7839f6..4dd3463 100644 --- a/quicktap/src/stack/mod.rs +++ b/quicktap/src/stack/mod.rs @@ -26,8 +26,9 @@ use std::error::Error; use std::net::SocketAddr; +use crate::device::QTInterface; -pub mod entry; +pub mod entry_udp; /// The methods and functions that need to be implemented by a stage in the packet processor pub trait PacketProcessingStage { @@ -35,5 +36,5 @@ pub trait PacketProcessingStage { /// For example, if you are the CRYPT layer, and are processing a tun packet, assume that the ENTRY and ROUTING stack stages have already been completed and you are being given the result of that processing. /// # Errors /// This function will error if an error occurs - fn process_packet(pkt: Vec, from: &SocketAddr) -> Result<(), Box> where Self: Sized; + fn process_packet(pkt: Vec, from: &SocketAddr, interface: &QTInterface) -> Result<(), Box> where Self: Sized; } \ No newline at end of file