[stack][ENTRY_UDP] process handshake initialization packets
This commit is contained in:
parent
b07895b043
commit
4e73da252d
|
@ -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"
|
||||
|
|
|
@ -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<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> {
|
||||
/// 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<HandshakeState<'a>>,
|
||||
handshake_in_flight: Option<HandshakeState<'a>>,
|
||||
public_key: PublicKey
|
||||
/// The currently active WireGuard handshake session with this peer, if any
|
||||
pub handshake: Option<HandshakeState<'a>>,
|
||||
/// 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> {
|
||||
/// 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]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<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(())
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<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
|
||||
// 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<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!();
|
||||
}
|
||||
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!();
|
||||
}
|
||||
fn handle_handshake_cookie_reply(pkt: Vec<u8>, from: &SocketAddr, interface: &QTInterface) -> Result<(), Box<dyn Error>> {
|
||||
unimplemented!();
|
||||
}
|
||||
fn handle_data_packet(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!();
|
||||
}
|
||||
}
|
|
@ -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<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;
|
||||
}
|
Loading…
Reference in New Issue