diff --git a/quicktap/Cargo.toml b/quicktap/Cargo.toml index e378e67..5c903ef 100644 --- a/quicktap/Cargo.toml +++ b/quicktap/Cargo.toml @@ -15,6 +15,7 @@ rand = "0.8.5" hmac = "0.12.1" chacha20poly1305 = "0.10.1" build-info = "0.0.29" +log = "0.4.17" [target.'cfg(unix)'.dependencies] tun = "0.5.4" diff --git a/quicktap/src/device/mod.rs b/quicktap/src/device/mod.rs index a364239..2fdd0f4 100644 --- a/quicktap/src/device/mod.rs +++ b/quicktap/src/device/mod.rs @@ -2,28 +2,36 @@ //! 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 x25519_dalek::{PublicKey, StaticSecret}; 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> + /// A list of `QTPeer`s that this interface manages + pub peers: HashMap>, + /// The private key of the interface + pub private_key: StaticSecret } impl<'a> QTInterface<'a> { /// Create a new, blank `QTInterface` with no configuration - pub fn new() -> Self { + pub fn new(private_key: StaticSecret) -> Self { QTInterface { peers: HashMap::default(), + private_key } } } /// 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 + /// The currently active WireGuard handshake session with this peer, if any + pub handshake: Option>, + /// The in-flight WireGuard handshake with this peer, if any + pub handshake_in_flight: Option>, + /// The public key of this peer + pub public_key: PublicKey, + /// The optional pre-shared key with this peer. Set to all zero if there is none. + pub q: [u8; 32] } impl<'a> QTPeer<'a> { /// Create a new, blank `QTPeer` with no configuration @@ -32,6 +40,7 @@ impl<'a> QTPeer<'a> { handshake: None, handshake_in_flight: None, public_key: key, + q: [0u8; 32] } } } \ No newline at end of file diff --git a/quicktap/src/drivers/error.rs b/quicktap/src/drivers/error.rs index 46804be..f98de51 100644 --- a/quicktap/src/drivers/error.rs +++ b/quicktap/src/drivers/error.rs @@ -8,13 +8,16 @@ use std::fmt::{Display, Formatter}; pub enum DriverError { /// An invalid packet type has been received on the interface, and quicktap does not know how to process it /// This is raised in the PREENTRY stage of packet processing - InvalidPacketTypeRecvOnInterface + InvalidPacketTypeRecvOnInterface, + /// A correctly authenticated handshake packet was received, but we do not recognize the peer + UnrecognizedValidHandshake } impl Error for DriverError {} impl Display for DriverError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - Self::InvalidPacketTypeRecvOnInterface => write!(f, "Invalid packet type received on interface") + Self::InvalidPacketTypeRecvOnInterface => write!(f, "Invalid packet type received on interface"), + Self::UnrecognizedValidHandshake => write!(f, "Handshake packet valid, but from unknown peer") } } } \ No newline at end of file diff --git a/quicktap/src/drivers/udp_listener.rs b/quicktap/src/drivers/udp_listener.rs index 22b9a26..cddbe15 100644 --- a/quicktap/src/drivers/udp_listener.rs +++ b/quicktap/src/drivers/udp_listener.rs @@ -27,10 +27,9 @@ pub enum SocketRoundResult { /// Run a socket update round on the (nonblocking!) UDP socket provided. Checks for any packets that may be present and send them off to get processed. /// **Warning!** This only runs *one* update round - therefore only one packet will be processed if any are present. -/// 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, interface: &QTInterface) -> Result<(), SocketRoundResult> { +pub fn qtap_socket_round<'a>(socket: &mut UdpSocket, interface: &'a mut QTInterface<'a>) -> Result<(), SocketRoundResult> { let mut packet_id = [0u8; 1]; match socket.peek(&mut packet_id) { Ok(_) => (), @@ -59,25 +58,4 @@ pub fn qtap_socket_round(socket: &mut UdpSocket, interface: &QTInterface) -> Res Ok(_) => Ok(()), Err(e) => Err(SocketRoundResult::GenericAnyError(e)) } -} - -/// Run several socket update rounds on the (nonblocking!) UDP socket provided. Will continually call `qtap_socket_round` in a loop until `io::WouldBlock` is returned. -/// **Warning!** This runs as many socket rounds are needed to clear the UDP datagram queue. If there is a long queue, this function will take a very long time. -/// 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, interface: &QTInterface) -> Result<(), Box> { - loop { - match qtap_socket_round(socket, interface) { - Ok(_) => (), - Err(e) => { - match e { - SocketRoundResult::IoError(e) if matches!(e.kind(), io::ErrorKind::WouldBlock) => break, - SocketRoundResult::GenericAnyError(e) => return Err(e), - SocketRoundResult::IoError(e) => return Err(e.into()) - } - } - } - } - Ok(()) } \ No newline at end of file diff --git a/quicktap/src/noise/handshake/mod.rs b/quicktap/src/noise/handshake/mod.rs index b1eb0be..63d26d9 100644 --- a/quicktap/src/noise/handshake/mod.rs +++ b/quicktap/src/noise/handshake/mod.rs @@ -27,6 +27,22 @@ pub const HANDSHAKE_INITIATOR_CHAIN_KEY_HASH: [u8; 32] = [ 147, 232, 183, 14, 225, 156, 101, 186, 7, 158, 243, ]; +/// Represents what mode the handshake state machine currently is in. +pub enum HandshakeMode { + /// No state is currently present; an init has not been sent or received. + Ready, + /// We have received an init from the other peer, and need to send the ack packet. + InitReceived, + /// We have sent an init to the other peer, and are waiting for their ack packet + InitSent, + /// We have received the ack packet from the other peer, and are ready to derive data keys and begin transport. + AckReceived, + /// We have sent the ack packet to the other peer, and are ready to derive data keys and begin transport. + AckSent, + /// We have derived data keys and are ready to send data. + Data +} + /// Represents a cookie we got from the other peer #[derive(Debug)] pub struct Cookie { @@ -66,7 +82,9 @@ pub struct HandshakeState<'a> { pub we_are_initiator: bool, - pub bitfield: ShiftWindow + pub bitfield: ShiftWindow, + + pub mode: HandshakeMode } impl<'a> HandshakeState<'a> { /// Determines if the state variables of this `HandshakeState` are the same as another @@ -97,7 +115,7 @@ impl<'a> HandshakeState<'a> { /// Create a new handshake state representing a brand-new handshake. /// This function initializes the important values with their appropriate initialization vectors, and zeroes out all other values. - pub fn new(private_key: &'a StaticSecret, other_pubkey: PublicKey, pre_shared_key: Option<[u8; 32]>) -> Self { + pub fn new(private_key: &'a StaticSecret) -> Self { Self { h: [0u8; 32], ck: [0u8; 32], @@ -107,17 +125,18 @@ impl<'a> HandshakeState<'a> { s_pub_r: PublicKey::from([0u8; 32]), e_priv_me: StaticSecret::new(OsRng), s_priv_me: private_key, - s_pub_them: other_pubkey, + s_pub_them: PublicKey::from([0u8; 32]), i_i: 0, i_r: 0, - q: pre_shared_key.unwrap_or([0u8; 32]), + q: [0u8; 32], cookies: vec![], t_send: [0u8; 32], t_recv: [0u8; 32], n_send: 1, n_recv: 1, we_are_initiator: false, - bitfield: ShiftWindow::new() + bitfield: ShiftWindow::new(), + mode: HandshakeMode::Ready } } } diff --git a/quicktap/src/noise/handshake/tests.rs b/quicktap/src/noise/handshake/tests.rs index 1e1c815..91afdc1 100644 --- a/quicktap/src/noise/handshake/tests.rs +++ b/quicktap/src/noise/handshake/tests.rs @@ -10,8 +10,10 @@ fn noise_halfhandshake_test() { let alice_keypair = qcrypto_dh_generate_longterm(); let bob_keypair = qcrypto_dh_generate_longterm(); - 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 alice_session = HandshakeState::new(&alice_keypair.0); + alice_session.s_pub_them = bob_keypair.1; + let mut bob_session = HandshakeState::new(&bob_keypair.0); + bob_session.s_pub_them = alice_keypair.1; let handshake_init = generate_handshake_init(&mut alice_session).unwrap(); parse_handshake_init(&mut bob_session, handshake_init).unwrap(); @@ -27,8 +29,10 @@ fn noise_nocookie_handshake_test() { let alice_keypair = qcrypto_dh_generate_longterm(); let bob_keypair = qcrypto_dh_generate_longterm(); - 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 alice_session = HandshakeState::new(&alice_keypair.0); + alice_session.s_pub_them = bob_keypair.1; + let mut bob_session = HandshakeState::new(&bob_keypair.0); + bob_session.s_pub_them = alice_keypair.1; let handshake_init = generate_handshake_init(&mut alice_session).unwrap(); parse_handshake_init(&mut bob_session, handshake_init).unwrap(); diff --git a/quicktap/src/noise/tests.rs b/quicktap/src/noise/tests.rs index ce92331..1794542 100644 --- a/quicktap/src/noise/tests.rs +++ b/quicktap/src/noise/tests.rs @@ -12,8 +12,10 @@ pub fn noise_transport_test() { let alice_keypair = qcrypto_dh_generate_longterm(); let bob_keypair = qcrypto_dh_generate_longterm(); - 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 alice_session = HandshakeState::new(&alice_keypair.0); + alice_session.s_pub_them = bob_keypair.1; + let mut bob_session = HandshakeState::new(&bob_keypair.0); + bob_session.s_pub_them = alice_keypair.1; let handshake_init = generate_handshake_init(&mut alice_session).unwrap(); parse_handshake_init(&mut bob_session, handshake_init).unwrap(); diff --git a/quicktap/src/stack/entry_udp.rs b/quicktap/src/stack/entry_udp.rs index f8481fe..aa3ffaa 100644 --- a/quicktap/src/stack/entry_udp.rs +++ b/quicktap/src/stack/entry_udp.rs @@ -2,38 +2,59 @@ use std::error::Error; use std::net::SocketAddr; +use log::{warn}; use crate::device::QTInterface; use crate::drivers::error::DriverError; +use crate::noise::handshake::{HandshakeMode, HandshakeState}; +use crate::noise::handshake::initiator::parse_handshake_init; 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> { + #[allow(clippy::unwrap_used)] // the arrays are fixed size, with known size values + fn process_packet<'a>(pkt: Vec, from: &SocketAddr, interface: &'a mut QTInterface<'a>) -> Result<(), Box> { // packet types are determined by the first byte of the packet // if it is not a known packet type, return an error and drop the packet match pkt.first().ok_or(DriverError::InvalidPacketTypeRecvOnInterface)? { - 1 => Self::handle_new_handshake(pkt, from, interface), - 2 => Self::handle_handshake_response(pkt, from, interface), - 3 => Self::handle_handshake_cookie_reply(pkt, from, interface), - 4 => Self::handle_data_packet(pkt, from, interface), + 1 => Self::handle_new_handshake(pkt.try_into().unwrap(), from, interface), + 2 => Self::handle_handshake_response(pkt.try_into().unwrap(), from, interface), + 3 => Self::handle_handshake_cookie_reply(pkt.try_into().unwrap(), from, interface), + 4 => Self::handle_data_packet(pkt.try_into().unwrap(), from, interface), _ => Err(DriverError::InvalidPacketTypeRecvOnInterface.into()) } } } impl UDPPacketEntryStage { - fn handle_new_handshake(pkt: Vec, from: &SocketAddr, interface: &QTInterface) -> Result<(), Box> { + fn handle_new_handshake<'a>(pkt: [u8; 148], from: &SocketAddr, interface: &'a mut QTInterface<'a>) -> Result<(), Box> { + let mut new_state = HandshakeState::new(&interface.private_key); + parse_handshake_init(&mut new_state, pkt)?; // we have initialized the handshake + // now we need to update keying values, and possibly reject the handshake if the peer is unrecognized + if !interface.peers.contains_key(&new_state.s_pub_them) { + // this peer is unrecognized. ignore it and drop the packet + warn!("[stack/ENTRY_UDP] stack received handshake initialization packet from unrecognized peer {:?}", new_state.s_pub_them); + return Err(DriverError::UnrecognizedValidHandshake.into()) + } + let peer = interface.peers.get_mut(&new_state.s_pub_them).ok_or(DriverError::UnrecognizedValidHandshake)?; + // update keying + new_state.q = peer.q; + + // update state machine + new_state.mode = HandshakeMode::InitReceived; + + peer.handshake_in_flight = Some(new_state); + + Ok(()) + } + fn handle_handshake_response(pkt: Vec, from: &SocketAddr, interface: &mut QTInterface) -> Result<(), Box> { unimplemented!(); } - fn handle_handshake_response(pkt: Vec, from: &SocketAddr, interface: &QTInterface) -> Result<(), Box> { + fn handle_handshake_cookie_reply(pkt: Vec, from: &SocketAddr, interface: &mut QTInterface) -> Result<(), Box> { unimplemented!(); } - fn handle_handshake_cookie_reply(pkt: Vec, from: &SocketAddr, interface: &QTInterface) -> Result<(), Box> { - unimplemented!(); - } - fn handle_data_packet(pkt: Vec, from: &SocketAddr, interface: &QTInterface) -> Result<(), Box> { + fn handle_data_packet(pkt: Vec, from: &SocketAddr, interface: &mut 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 4dd3463..fd53488 100644 --- a/quicktap/src/stack/mod.rs +++ b/quicktap/src/stack/mod.rs @@ -36,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, interface: &QTInterface) -> Result<(), Box> where Self: Sized; + fn process_packet<'a>(pkt: Vec, from: &SocketAddr, interface: &'a mut QTInterface<'a>) -> Result<(), Box> where Self: Sized; } \ No newline at end of file