[stack][ENTRY_UDP] process handshake initialization packets

This commit is contained in:
c0repwn3r 2023-01-13 09:20:41 -05:00
parent b07895b043
commit 4e73da252d
Signed by: core
GPG Key ID: FDBF740DADDCEECF
9 changed files with 92 additions and 55 deletions

View File

@ -15,6 +15,7 @@ rand = "0.8.5"
hmac = "0.12.1" hmac = "0.12.1"
chacha20poly1305 = "0.10.1" chacha20poly1305 = "0.10.1"
build-info = "0.0.29" build-info = "0.0.29"
log = "0.4.17"
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
tun = "0.5.4" tun = "0.5.4"

View File

@ -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 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 std::collections::HashMap;
use x25519_dalek::PublicKey; use x25519_dalek::{PublicKey, StaticSecret};
use crate::noise::handshake::HandshakeState; 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. /// A high-level struct for the configuration of a `WireGuard` device. This is the equivalent of `[Interface]` in wg-quick.
pub struct QTInterface<'a> { pub struct QTInterface<'a> {
peers: HashMap<PublicKey, QTPeer<'a>> /// A list of `QTPeer`s that this interface manages
pub peers: HashMap<PublicKey, QTPeer<'a>>,
/// The private key of the interface
pub private_key: StaticSecret
} }
impl<'a> QTInterface<'a> { impl<'a> QTInterface<'a> {
/// Create a new, blank `QTInterface` with no configuration /// Create a new, blank `QTInterface` with no configuration
pub fn new() -> Self { pub fn new(private_key: StaticSecret) -> Self {
QTInterface { QTInterface {
peers: HashMap::default(), 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. /// 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> { pub struct QTPeer<'a> {
handshake: Option<HandshakeState<'a>>, /// The currently active WireGuard handshake session with this peer, if any
handshake_in_flight: Option<HandshakeState<'a>>, pub handshake: Option<HandshakeState<'a>>,
public_key: PublicKey /// The in-flight WireGuard handshake with this peer, if any
pub handshake_in_flight: Option<HandshakeState<'a>>,
/// 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> { impl<'a> QTPeer<'a> {
/// Create a new, blank `QTPeer` with no configuration /// Create a new, blank `QTPeer` with no configuration
@ -32,6 +40,7 @@ impl<'a> QTPeer<'a> {
handshake: None, handshake: None,
handshake_in_flight: None, handshake_in_flight: None,
public_key: key, public_key: key,
q: [0u8; 32]
} }
} }
} }

View File

@ -8,13 +8,16 @@ use std::fmt::{Display, Formatter};
pub enum DriverError { pub enum DriverError {
/// An invalid packet type has been received on the interface, and quicktap does not know how to process it /// 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 /// 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 Error for DriverError {}
impl Display for DriverError { impl Display for DriverError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self { 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")
} }
} }
} }

View File

@ -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. /// 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. /// **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 /// # 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. /// 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]; let mut packet_id = [0u8; 1];
match socket.peek(&mut packet_id) { match socket.peek(&mut packet_id) {
Ok(_) => (), Ok(_) => (),
@ -60,24 +59,3 @@ pub fn qtap_socket_round(socket: &mut UdpSocket, interface: &QTInterface) -> Res
Err(e) => Err(SocketRoundResult::GenericAnyError(e)) 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<dyn Error>> {
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(())
}

View File

@ -27,6 +27,22 @@ pub const HANDSHAKE_INITIATOR_CHAIN_KEY_HASH: [u8; 32] = [
147, 232, 183, 14, 225, 156, 101, 186, 7, 158, 243, 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 /// Represents a cookie we got from the other peer
#[derive(Debug)] #[derive(Debug)]
pub struct Cookie { pub struct Cookie {
@ -66,7 +82,9 @@ pub struct HandshakeState<'a> {
pub we_are_initiator: bool, pub we_are_initiator: bool,
pub bitfield: ShiftWindow pub bitfield: ShiftWindow,
pub mode: HandshakeMode
} }
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
@ -97,7 +115,7 @@ impl<'a> HandshakeState<'a> {
/// Create a new handshake state representing a brand-new handshake. /// 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. /// 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 { Self {
h: [0u8; 32], h: [0u8; 32],
ck: [0u8; 32], ck: [0u8; 32],
@ -107,17 +125,18 @@ impl<'a> HandshakeState<'a> {
s_pub_r: PublicKey::from([0u8; 32]), s_pub_r: PublicKey::from([0u8; 32]),
e_priv_me: StaticSecret::new(OsRng), e_priv_me: StaticSecret::new(OsRng),
s_priv_me: private_key, s_priv_me: private_key,
s_pub_them: other_pubkey, s_pub_them: PublicKey::from([0u8; 32]),
i_i: 0, i_i: 0,
i_r: 0, i_r: 0,
q: pre_shared_key.unwrap_or([0u8; 32]), q: [0u8; 32],
cookies: vec![], cookies: vec![],
t_send: [0u8; 32], t_send: [0u8; 32],
t_recv: [0u8; 32], t_recv: [0u8; 32],
n_send: 1, n_send: 1,
n_recv: 1, n_recv: 1,
we_are_initiator: false, we_are_initiator: false,
bitfield: ShiftWindow::new() bitfield: ShiftWindow::new(),
mode: HandshakeMode::Ready
} }
} }
} }

View File

@ -10,8 +10,10 @@ fn noise_halfhandshake_test() {
let alice_keypair = qcrypto_dh_generate_longterm(); let alice_keypair = qcrypto_dh_generate_longterm();
let bob_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 alice_session = HandshakeState::new(&alice_keypair.0);
let mut bob_session = HandshakeState::new(&bob_keypair.0, alice_keypair.1, None); 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(); let handshake_init = generate_handshake_init(&mut alice_session).unwrap();
parse_handshake_init(&mut bob_session, handshake_init).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 alice_keypair = qcrypto_dh_generate_longterm();
let bob_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 alice_session = HandshakeState::new(&alice_keypair.0);
let mut bob_session = HandshakeState::new(&bob_keypair.0, alice_keypair.1, None); 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(); let handshake_init = generate_handshake_init(&mut alice_session).unwrap();
parse_handshake_init(&mut bob_session, handshake_init).unwrap(); parse_handshake_init(&mut bob_session, handshake_init).unwrap();

View File

@ -12,8 +12,10 @@ pub fn noise_transport_test() {
let alice_keypair = qcrypto_dh_generate_longterm(); let alice_keypair = qcrypto_dh_generate_longterm();
let bob_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 alice_session = HandshakeState::new(&alice_keypair.0);
let mut bob_session = HandshakeState::new(&bob_keypair.0, alice_keypair.1, None); 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(); let handshake_init = generate_handshake_init(&mut alice_session).unwrap();
parse_handshake_init(&mut bob_session, handshake_init).unwrap(); parse_handshake_init(&mut bob_session, handshake_init).unwrap();

View File

@ -2,38 +2,59 @@
use std::error::Error; use std::error::Error;
use std::net::SocketAddr; use std::net::SocketAddr;
use log::{warn};
use crate::device::QTInterface; use crate::device::QTInterface;
use crate::drivers::error::DriverError; use crate::drivers::error::DriverError;
use crate::noise::handshake::{HandshakeMode, HandshakeState};
use crate::noise::handshake::initiator::parse_handshake_init;
use crate::stack::{PacketProcessingStage}; use crate::stack::{PacketProcessingStage};
/// The ENTRY stage of the packet processing stack for UDP packets /// The ENTRY stage of the packet processing stack for UDP packets
pub struct UDPPacketEntryStage {} pub struct UDPPacketEntryStage {}
impl PacketProcessingStage for UDPPacketEntryStage { impl PacketProcessingStage for UDPPacketEntryStage {
fn process_packet(pkt: Vec<u8>, from: &SocketAddr, interface: &QTInterface) -> Result<(), Box<dyn Error>> { #[allow(clippy::unwrap_used)] // the arrays are fixed size, with known size values
fn process_packet<'a>(pkt: Vec<u8>, from: &SocketAddr, interface: &'a mut QTInterface<'a>) -> Result<(), Box<dyn Error>> {
// packet types are determined by the first byte of the packet // 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 // if it is not a known packet type, return an error and drop the packet
match pkt.first().ok_or(DriverError::InvalidPacketTypeRecvOnInterface)? { match pkt.first().ok_or(DriverError::InvalidPacketTypeRecvOnInterface)? {
1 => Self::handle_new_handshake(pkt, from, interface), 1 => Self::handle_new_handshake(pkt.try_into().unwrap(), from, interface),
2 => Self::handle_handshake_response(pkt, from, interface), 2 => Self::handle_handshake_response(pkt.try_into().unwrap(), from, interface),
3 => Self::handle_handshake_cookie_reply(pkt, from, interface), 3 => Self::handle_handshake_cookie_reply(pkt.try_into().unwrap(), from, interface),
4 => Self::handle_data_packet(pkt, from, interface), 4 => Self::handle_data_packet(pkt.try_into().unwrap(), from, interface),
_ => Err(DriverError::InvalidPacketTypeRecvOnInterface.into()) _ => Err(DriverError::InvalidPacketTypeRecvOnInterface.into())
} }
} }
} }
impl UDPPacketEntryStage { impl UDPPacketEntryStage {
fn handle_new_handshake(pkt: Vec<u8>, from: &SocketAddr, interface: &QTInterface) -> Result<(), Box<dyn Error>> { fn handle_new_handshake<'a>(pkt: [u8; 148], from: &SocketAddr, interface: &'a mut QTInterface<'a>) -> Result<(), Box<dyn Error>> {
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<u8>, from: &SocketAddr, interface: &mut QTInterface) -> Result<(), Box<dyn Error>> {
unimplemented!(); unimplemented!();
} }
fn handle_handshake_response(pkt: Vec<u8>, from: &SocketAddr, interface: &QTInterface) -> Result<(), Box<dyn Error>> { fn handle_handshake_cookie_reply(pkt: Vec<u8>, from: &SocketAddr, interface: &mut QTInterface) -> Result<(), Box<dyn Error>> {
unimplemented!(); unimplemented!();
} }
fn handle_handshake_cookie_reply(pkt: Vec<u8>, from: &SocketAddr, interface: &QTInterface) -> Result<(), Box<dyn Error>> { fn handle_data_packet(pkt: Vec<u8>, from: &SocketAddr, interface: &mut QTInterface) -> Result<(), Box<dyn Error>> {
unimplemented!();
}
fn handle_data_packet(pkt: Vec<u8>, from: &SocketAddr, interface: &QTInterface) -> Result<(), Box<dyn Error>> {
unimplemented!(); unimplemented!();
} }
} }

View File

@ -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. /// 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 /// # Errors
/// This function will error if an error occurs /// This function will error if an error occurs
fn process_packet(pkt: Vec<u8>, from: &SocketAddr, interface: &QTInterface) -> Result<(), Box<dyn Error>> where Self: Sized; fn process_packet<'a>(pkt: Vec<u8>, from: &SocketAddr, interface: &'a mut QTInterface<'a>) -> Result<(), Box<dyn Error>> where Self: Sized;
} }