Compare commits

...

10 Commits

30 changed files with 1521 additions and 200 deletions

View File

@ -7,3 +7,7 @@ edition = "2021"
[dependencies] [dependencies]
quicktap = { path = "../quicktap", version = "0.1.0" } quicktap = { path = "../quicktap", version = "0.1.0" }
clap = { version = "4.0.29", features = ["derive"]}
daemonize = "0.4.1"
log = "0.4.17"
simple_logger = "4.0.0"

20
quicktap-cli/README.md Normal file
View File

@ -0,0 +1,20 @@
# Quicktap CLI
This is a WireGuard xplatform implementation for using [Quicktap Engine](../quicktap/README.md) with as many platforms as possible.
Here's a support table for the CLI:
| | IPv4 | IPv6 |
|---------|---------------|---------------|
| Linux | ✓ | ✓ |
| NetBSD | ⚠<sup></sup> | x<sup></sup> |
| MacOS | x<sup></sup> | x<sup></sup> |
| Windows | x<sup></sup> | x<sup></sup> |
Key:
- ✓ - Fully supported
- ⚠ - Partially supported
- x - Not supported
- ‡ - Support planned
- † - Unmaintained
Help is wanted! Improve existing implementations or pick out an unmaintained or unsupported one and help the project get better.

9
quicktap-cli/src/cli.rs Normal file
View File

@ -0,0 +1,9 @@
use clap::{ArgAction, Parser};
#[derive(Parser)]
#[command(author = "The Quicktap Maintainers", version, about = "A userspace WireGuard implementation written in Rust", long_about = None)]
pub struct Cli {
#[arg(short, long, action = ArgAction::SetTrue)]
pub foreground: bool,
pub interface_name: String
}

View File

@ -0,0 +1,45 @@
use std::error::Error;
use std::io;
use std::os::unix::net::SocketAddr;
use std::os::unix::net::{UnixListener, UnixStream};
pub enum PacketThreadCommand {
UpdateConfig(UpdateConfigCommand),
Stop
}
pub struct UpdateConfigCommand {}
pub struct CommandClient {
stream: UnixStream,
addr: SocketAddr,
current_command: CommandType
}
pub enum CommandType {
WaitingForCommand,
Read,
Write
}
pub fn command_handler(listener: UnixListener) -> Result<(), Box<dyn Error>> {
listener.set_nonblocking(true)?;
let mut clients: Vec<CommandClient> = vec![];
match listener.accept() {
Ok((stream, addr)) => {
clients.push(CommandClient { stream, addr, current_command: CommandType::WaitingForCommand })
},
Err(e) if matches!(e.kind(), io::ErrorKind::WouldBlock) => {
// ignore.
},
Err(e) => {
// okay, real error, knock it up
return Err(e.into())
}
}
// uh lets play ignorey ignorey for now
Ok(())
}

View File

@ -1,15 +1,132 @@
use std::net::{Ipv4Addr};
use quicktap::cidr::{IpInet, Ipv4Inet}; use std::fs::File;
use std::os::unix::net::{UnixListener};
use std::sync::mpsc::channel;
use std::thread::{spawn};
use clap::Parser;
use daemonize::Daemonize;
use log::{error, info};
use quicktap::drivers::tun::TunDevice; use quicktap::drivers::tun::TunDevice;
use quicktap::drivers::tungeneric::{GenericDriver, GenericInterface}; use quicktap::drivers::tungeneric::{GenericDriver, GenericInterface};
use crate::cli::Cli;
use crate::command::{command_handler, PacketThreadCommand};
use crate::pproc::packet_thread;
pub mod cli;
pub mod pproc;
pub mod command;
fn main() { fn main() {
let mut device = TunDevice::new(&GenericInterface { simple_logger::init().unwrap();
addresses: vec![IpInet::V4(Ipv4Inet::new(Ipv4Addr::new(10, 19, 2, 2), 16).unwrap())],
mtu: None, let args = Cli::parse();
name: "ela1".to_string(),
}).unwrap(); info!("Hello, world! {}", quicktap::version());
loop { info!("Starting up");
println!("{:?}", device.read().unwrap().header)
if !args.foreground {
info!("Forking to background");
let stdout = match File::create("/var/log/e4.log") {
Ok(f) => f,
Err(e) => {
error!("Failed to open stdout file: {}", e);
std::process::exit(8);
}
};
let stderr = match File::create("/var/log/e4.err") {
Ok(f) => f,
Err(e) => {
error!("Failed to open stderr file: {}", e);
std::process::exit(9);
}
};
let daemon_opts = Daemonize::new()
.stdout(stdout)
.stderr(stderr);
match daemon_opts.start() {
Ok(_) => info!("Forked to background successfully"),
Err(e) => {
error!("Failed to daemonize quicktap-cli worker process: {}", e);
std::process::exit(1);
}
}
} }
info!("Creating unconfigured device");
let device = match TunDevice::new(&GenericInterface {
addresses: vec![],
mtu: None,
name: args.interface_name.clone(),
}) {
Ok(d) => d,
Err(e) => {
error!("Failed to create interface: {}", e);
std::process::exit(2);
}
};
info!("Spawning packet processor thread");
let (tx, rx) = channel::<PacketThreadCommand>();
let pproc_thread = spawn(|| {
match packet_thread(device, rx) {
Ok(_) => (),
Err(e) => {
error!("Error in pproc_thread: {}", e);
}
}
});
info!("Creating command listener");
match UnixListener::bind(format!("/var/run/{}.sock", args.interface_name)) {
Ok(l) => {
// handle l
match command_handler(l) {
Ok(_) => (),
Err(e) => error!("Error in command handler: {}", e)
}
},
Err(e) => {
error!("Error creating command listener {}: {}", format!("/var/run/{}.sock", args.interface_name), e);
}
};
match tx.send(PacketThreadCommand::Stop) {
Ok(_) => (),
Err(e) => {
error!("Error sending stop command to packet thread: {}", e);
std::process::exit(6);
}
}
info!("Waiting for packet thread to stop");
match pproc_thread.join() {
Ok(_) => (),
Err(e) => {
error!("Error joining thread: {:?}", e);
std::process::exit(5);
}
}
info!("Cleaning up");
match std::fs::remove_file(format!("/var/run/{}.sock", args.interface_name)) {
Ok(_) => (),
Err(e) => {
error!("Failed to remove existing socket: {}", e);
std::process::exit(7);
}
}
info!("Cya!");
} }

45
quicktap-cli/src/pproc.rs Normal file
View File

@ -0,0 +1,45 @@
use std::error::Error;
use std::io;
use std::sync::mpsc::Receiver;
use log::{error, info};
use quicktap::drivers::tun::TunDevice;
use quicktap::drivers::tungeneric::GenericDriver;
use crate::command::PacketThreadCommand;
pub fn packet_thread(mut device: TunDevice, rx: Receiver<PacketThreadCommand>) -> Result<(), Box<dyn Error>> {
loop {
// Check command channel
let command = match rx.try_recv() {
Ok(d) => Some(d),
Err(e) if matches!(e, std::sync::mpsc::TryRecvError::Empty) => None,
Err(e) => return Err(e.into())
};
if let Some(cmd) = command {
if let PacketThreadCommand::Stop = cmd {
info!("packet thread: recieved command to stop by control process");
return Ok(())
}
}
let packet = match device.read() {
Ok(p) => Some(p),
Err(e) => match e.kind() {
io::ErrorKind::WouldBlock => None,
_ => {
error!("Error while reading packets from interface: {}", e);
return Err(e.into());
}
}
};
if let Some(p) = packet {
info!("recv_packet_on_tun {:?}", p.header);
// parse and route
}
}
}

View File

@ -14,6 +14,8 @@ x25519-dalek = "2.0.0-pre.1"
rand = "0.8.5" 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"
log = "0.4.17"
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
tun = "0.5.4" tun = "0.5.4"
@ -21,3 +23,6 @@ tun = "0.5.4"
[dev-dependencies] [dev-dependencies]
hex_lit = "0.1.1" hex_lit = "0.1.1"
hex = "0.4.3" hex = "0.4.3"
[build-dependencies]
build-info-build = "0.0.29"

View File

@ -1,4 +1,4 @@
# Quicktap # Quicktap Engine
Quicktap is a (hopefully) standard-compliant Rust implementation of the [WireGuard protocol](https://wireguard.org). Quicktap is a (hopefully) standard-compliant Rust implementation of the [WireGuard protocol](https://wireguard.org).
It was designed to be a simpler alternative to Cloudflare's [boringtun](https://github.com/cloudflare/boringtun). It was designed to be a simpler alternative to Cloudflare's [boringtun](https://github.com/cloudflare/boringtun).

3
quicktap/build.rs Normal file
View File

@ -0,0 +1,3 @@
fn main() {
build_info_build::build_script();
}

View File

@ -0,0 +1,46 @@
//! 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`.
use std::collections::HashMap;
use x25519_dalek::{PublicKey, StaticSecret};
use crate::noise::handshake::HandshakeState;
/// A high-level struct for the configuration of a `WireGuard` device. This is the equivalent of `[Interface]` in wg-quick.
pub struct QTInterface<'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(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> {
/// 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
pub fn new(key: PublicKey) -> Self {
QTPeer {
handshake: None,
handshake_in_flight: None,
public_key: key,
q: [0u8; 32]
}
}
}

View File

@ -0,0 +1,23 @@
//! Defines the error type for the glue layer
use std::error::Error;
use std::fmt::{Display, Formatter};
#[derive(Debug)]
#[allow(clippy::module_name_repetitions)]
/// Represents an error in the driver (glue) portion of quicktap
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,
/// 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::UnrecognizedValidHandshake => write!(f, "Handshake packet valid, but from unknown peer")
}
}
}

View File

@ -3,8 +3,10 @@
use std::error::Error; use std::error::Error;
use std::io; use std::io;
use std::io::{Read, Write}; use std::io::{Read, Write};
use etherparse::IpHeader; use etherparse::IpHeader;
use tun::platform::Device; use tun::platform::Device;
use crate::drivers::tungeneric::{GenericDriver, GenericInterface, TunPacket}; use crate::drivers::tungeneric::{GenericDriver, GenericInterface, TunPacket};
#[allow(clippy::module_name_repetitions)] #[allow(clippy::module_name_repetitions)]
@ -31,15 +33,18 @@ impl GenericDriver for TunDevice {
if let Some(mtu) = config.mtu { if let Some(mtu) = config.mtu {
device_config.mtu(mtu); device_config.mtu(mtu);
} }
let device = tun::create(&device_config)?;
device.set_nonblock()?;
Ok(Self { Ok(Self {
device: tun::create(&device_config)?, device,
read_buf: [0u8; 4096], read_buf: [0u8; 4096],
read_offset: 0, read_offset: 0,
packet_buf: [0u8; 4096], packet_buf: [0u8; 4096],
}) })
} }
fn read(&mut self) -> Result<TunPacket, Box<dyn Error>> { fn read(&mut self) -> Result<TunPacket, io::Error> {
self.packet_buf = [0u8; 4096]; self.packet_buf = [0u8; 4096];
let _ = self.device.read(&mut self.packet_buf)?; let _ = self.device.read(&mut self.packet_buf)?;
let ip_header = IpHeader::from_slice(&self.packet_buf); let ip_header = IpHeader::from_slice(&self.packet_buf);
@ -53,7 +58,7 @@ impl GenericDriver for TunDevice {
}) })
} }
Err(io::Error::new(io::ErrorKind::WouldBlock, "No packets detected yet").into()) Err(io::Error::new(io::ErrorKind::WouldBlock, "No packets detected yet"))
} }
fn clear(&mut self) { fn clear(&mut self) {
@ -61,8 +66,9 @@ impl GenericDriver for TunDevice {
self.read_offset = 0; self.read_offset = 0;
} }
fn write(&mut self, packet: TunPacket) -> Result<(), Box<dyn Error>> { fn write(&mut self, packet: TunPacket) -> Result<(), io::Error> {
let _ = self.device.write(&packet.packet)?; let _ = self.device.write(&packet.packet)?;
Ok(()) Ok(())
} }
} }

View File

@ -5,3 +5,9 @@
pub mod tun; // Tun/tap drivers for Linux pub mod tun; // Tun/tap drivers for Linux
pub mod tungeneric; pub mod tungeneric;
pub mod udp_listener;
pub mod error;
#[cfg(test)]
pub mod tests;

View File

@ -0,0 +1,11 @@
#![allow(clippy::unwrap_used)] // we want to panic, this is a test file
#![allow(clippy::missing_errors_doc)]
#![allow(clippy::missing_panics_doc)]
#![allow(clippy::missing_safety_doc)]
use crate::drivers::error::DriverError;
#[cfg(test)]
pub fn error_tests() {
assert_eq!(format!("{}", DriverError::InvalidPacketTypeRecvOnInterface), "Invalid packet type received on interface".to_string());
}

View File

@ -1,8 +1,10 @@
//! Generic traits and modules to represent a tun/tap device //! Generic traits and modules to represent a tun/tap device
use std::error::Error; use std::error::Error;
use std::io;
use cidr::IpInet; use cidr::IpInet;
use etherparse::{IpHeader}; use etherparse::IpHeader;
#[derive(Debug)] #[derive(Debug)]
/// A parsed packet from an interface /// A parsed packet from an interface
@ -24,7 +26,7 @@ pub trait GenericDriver {
/// Upon finding a successful packet inside the internal buffer, or the .clear() method being called, the internal buffer will be cleared. /// Upon finding a successful packet inside the internal buffer, or the .clear() method being called, the internal buffer will be cleared.
/// # Errors /// # Errors
/// This function will return `WouldBlock` if a packet could not be read. This function may error if there is an error reading the interface. /// This function will return `WouldBlock` if a packet could not be read. This function may error if there is an error reading the interface.
fn read(&mut self) -> Result<TunPacket, Box<dyn Error>>; fn read(&mut self) -> Result<TunPacket, io::Error>;
/// Clear any internal state of this interface. /// Clear any internal state of this interface.
fn clear(&mut self); fn clear(&mut self);
@ -32,7 +34,7 @@ pub trait GenericDriver {
/// Attempt to write a packet to the interface. /// Attempt to write a packet to the interface.
/// # Errors /// # Errors
/// This function will error if an error occured writing to the device. /// This function will error if an error occured writing to the device.
fn write(&mut self, packet: TunPacket) -> Result<(), Box<dyn Error>>; fn write(&mut self, packet: TunPacket) -> Result<(), io::Error>;
} }
/// Generic interface config /// Generic interface config

View File

@ -0,0 +1,61 @@
//! The logic for the WireGuard UDP socket.
use std::error::Error;
use std::io;
use std::net::UdpSocket;
use crate::device::QTInterface;
use crate::drivers::error::DriverError;
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.
pub const MSG1_MAX_BYTES: usize = 148;
/// The maximum amount of bytes that can be taken up by a Handshake Response packet.
pub const MSG2_MAX_BYTES: usize = 92;
/// The maximum amount of bytes that can be taken up by a data packet.
pub const MSGDATA_MAX_BYTES: usize = 65567;
/// The maximum amount of bytes that can be taken up by a cookie reply packet.
pub const MSGCOOKIE_MAX_BYTES: usize = 64;
/// Represents the result of a socket processing round
pub enum SocketRoundResult {
/// Represents that an io-specific error was received
IoError(io::Error),
/// Represents that a non-io-specific error was received
GenericAnyError(Box<dyn Error>)
}
/// 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.
/// # 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<'a>(socket: &mut UdpSocket, interface: &'a mut QTInterface<'a>) -> Result<(), SocketRoundResult> {
let mut packet_id = [0u8; 1];
match socket.peek(&mut packet_id) {
Ok(_) => (),
Err(e) => return Err(SocketRoundResult::IoError(e))
}; // Intentionally peek into a 1-byte vector. This will NOT remove the datagram from the queue.
// We WANT to discard the rest of the packet; right now we only care about the first byte so we know how much memory we need to allocate for the buffer.
// The packet type tells us this.
let buf_size = match packet_id[0] {
1 => MSG1_MAX_BYTES,
2 => MSG2_MAX_BYTES,
4 => MSGDATA_MAX_BYTES,
3 => MSGCOOKIE_MAX_BYTES,
_ => return Err(SocketRoundResult::GenericAnyError(DriverError::InvalidPacketTypeRecvOnInterface.into()))
};
let mut packet_buf = vec![0u8; buf_size];
let (read_bytes, from) = match socket.recv_from(&mut packet_buf) {
Ok(r) => r,
Err(e) => return Err(SocketRoundResult::IoError(e))
};
let packet = packet_buf[..read_bytes].to_vec();
match UDPPacketEntryStage::process_packet(packet, &from, interface) {
Ok(_) => Ok(()),
Err(e) => Err(SocketRoundResult::GenericAnyError(e))
}
}

View File

@ -10,9 +10,20 @@
// 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;
pub mod drivers; pub mod drivers;
pub mod qcrypto; pub mod qcrypto;
pub mod noise; pub mod noise;
pub mod stack;
pub mod device;
#[cfg(test)]
pub mod tests;
/// Gets the compile-time versioning information for the engine build.
pub const fn version() -> &'static str {
build_info::format!("quicktap engine v{} ({}) built at {} with {}", $.crate_info.version, $.version_control.unwrap().git().unwrap().commit_short_id, $.timestamp, $.compiler)
}

View File

@ -18,8 +18,8 @@ fn unpad_packet(p: &[u8]) -> Vec<u8> {
/// This function will error if the encryption step is unsuccessful. /// This function will error if the encryption step is unsuccessful.
pub fn encapsulate(state: &mut HandshakeState, packet: &Vec<u8>) -> Result<Vec<u8>, NoiseError> { pub fn encapsulate(state: &mut HandshakeState, packet: &Vec<u8>) -> Result<Vec<u8>, NoiseError> {
let packet = pad_packet(packet); let packet = pad_packet(packet);
let counter = state.n_send; let counter = state.n_send + 1;
let packet_data = match qcrypto_aead(&state.t_send, state.n_send, &packet, &[]) { let packet_data = match qcrypto_aead(&state.t_send, state.n_send + 1, &packet, &[]) {
Ok(d) => d, Ok(d) => d,
Err(e) => return Err(NoiseError::ChaCha20Error(e)) Err(e) => return Err(NoiseError::ChaCha20Error(e))
}; };
@ -32,6 +32,8 @@ pub fn encapsulate(state: &mut HandshakeState, packet: &Vec<u8>) -> Result<Vec<u
res[8..16].copy_from_slice(&counter.to_le_bytes()); res[8..16].copy_from_slice(&counter.to_le_bytes());
res[16..16+packet_data.len()].copy_from_slice(&packet_data); res[16..16+packet_data.len()].copy_from_slice(&packet_data);
state.n_send += 1;
Ok(res) Ok(res)
} }
@ -53,14 +55,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,58 +117,14 @@ pub fn handshake_init_to(session: &mut HandshakeState) -> Result<[u8; 148], Nois
Ok(msg.to_bytes(session)) Ok(msg.to_bytes(session))
} }
/// Parse a handshake initiator packet and decrypt it using the given session state, updating the session state with decrypted and authenticated values
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
/// # Errors /// # Errors
/// This function will error if decryption was unsuccessful /// This function will error if decryption was unsuccessful
/// # Panics /// # Panics
/// 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_r = PublicKey::from(session.s_priv_me); let s_pub_r = PublicKey::from(session.s_priv_me);
let msg = HandshakeInitiatorRaw::from_bytes(packet); let msg = HandshakeInitiatorRaw::from_bytes(packet);
@ -152,14 +150,14 @@ pub fn handshake_init_from(session: &mut HandshakeState, packet: [u8; 148]) -> R
// This unwrap is safe because the output length is a known constant with these inputs // This unwrap is safe because the output length is a known constant with these inputs
session.s_pub_i = PublicKey::from(<Vec<u8> as TryInto<[u8; 32]>>::try_into(match qcrypto_aead_decrypt(&k, 0, &msg.static_pub, &h) { let s_pub_i = PublicKey::from(<Vec<u8> as TryInto<[u8; 32]>>::try_into(match qcrypto_aead_decrypt(&k, 0, &msg.static_pub, &h) {
Ok(s) => s, Ok(s) => s,
Err(e) => return Err(NoiseError::ChaCha20Error(e)) Err(e) => return Err(NoiseError::ChaCha20Error(e))
}).unwrap()); }).unwrap());
let h = qcrypto_hash_twice(&h, &msg.static_pub); 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 ck = ci_k_pair[0];
let k = ci_k_pair[1]; let k = ci_k_pair[1];
@ -173,7 +171,7 @@ pub fn handshake_init_from(session: &mut HandshakeState, packet: [u8; 148]) -> R
// we need to check mac1 and mac2 // 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] }; 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 { if mac1 != msg.mac1 || mac2 != msg.mac2 {

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;
@ -26,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 {
@ -63,7 +80,11 @@ 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,
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
@ -94,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],
@ -104,16 +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: 0, n_send: 1,
n_recv: 0, n_recv: 1,
we_are_initiator: false, we_are_initiator: false,
bitfield: ShiftWindow::new(),
mode: HandshakeMode::Ready
} }
} }
} }

View File

@ -10,102 +10,6 @@ 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};
/// Creates a handshake response packet using the current active handshake session.
/// # Errors
/// This function will error if an encryption step is unsuccessful
/// # Panics
/// This function, while containing unwraps, will never panic.
#[allow(clippy::unwrap_used)] // Used for known safe type conversions only
pub fn handshake_response_to(session: &mut HandshakeState) -> Result<[u8; 92], NoiseError> {
let eph_keypair = qcrypto_dh_generate_longterm();
let mut msg = HandshakeResponseRaw {
sender: [0u8; 4],
receiver: [0u8; 4],
ephemeral: [0u8; 32],
empty: [0u8; 16],
mac1: [0u8; 16],
mac2: [0u8; 16]
};
session.ck = qcrypto_hkdf::<1>(&session.ck, eph_keypair.1.as_bytes())[0];
msg.ephemeral = eph_keypair.1.to_bytes();
session.h = qcrypto_hash_twice(&session.h, &msg.ephemeral);
session.ck = qcrypto_hkdf::<1>(&session.ck, qcrypto_dh_longterm(&eph_keypair.0, &session.e_pub_i).as_bytes())[0];
session.ck = qcrypto_hkdf::<1>(&session.ck, qcrypto_dh_longterm(&eph_keypair.0, &session.s_pub_i).as_bytes())[0];
let cr_t_k = qcrypto_hkdf::<3>(&session.ck, &session.q);
session.ck = cr_t_k[0];
let t = cr_t_k[1];
let k = cr_t_k[2];
session.h = qcrypto_hash_twice(&session.h, &t);
println!("{:?} {:?} {:?}", k, 0, session.h);
msg.empty = match qcrypto_aead(&k, 0, &[], &session.h) {
Ok(s) => s.try_into().unwrap(),
Err(e) => return Err(NoiseError::ChaCha20Error(e))
};
session.h = qcrypto_hash_twice(&session.h, &msg.empty);
Ok(msg.to_bytes(session))
}
/// Decrypts a handshake response packet using the current active handshake session.
/// # Errors
/// This function will error if a decryption step is unsuccessful
/// # Panics
/// This function, while containing unwraps, will never panic.
pub fn handshake_response_from(session: &mut HandshakeState, packet: [u8; 92]) -> Result<(), NoiseError> {
let msg = HandshakeResponseRaw::from_bytes(packet);
let e_pub_r = PublicKey::from(msg.ephemeral);
let mut ck = qcrypto_hkdf::<1>(&session.ck, e_pub_r.as_bytes())[0];
let mut h = qcrypto_hash_twice(&session.h, &msg.ephemeral);
ck = qcrypto_hkdf::<1>(&ck, qcrypto_dh_longterm(&session.e_priv_me, &e_pub_r).as_bytes())[0];
ck = qcrypto_hkdf::<1>(&ck, qcrypto_dh_longterm(session.s_priv_me, &e_pub_r).as_bytes())[0];
let cr_t_k = qcrypto_hkdf::<3>(&ck, &session.q);
ck = cr_t_k[0];
let t = cr_t_k[1];
let k = cr_t_k[2];
h = qcrypto_hash_twice(&h, &t);
println!("here");
println!("{:?} {:?} {:?}", k, 0, h);
let empty = match qcrypto_aead_decrypt(&k, 0, &msg.empty, &h) {
Ok(s) => s,
Err(e) => return Err(NoiseError::ChaCha20Error(e))
};
if !empty.is_empty() {
return Err(NoiseError::PacketUnauthenticated)
}
h = qcrypto_hash_twice(&h, &msg.empty);
let mac1: [u8; 16] = qcrypto_mac(&qcrypto_hash_twice(LABEL_MAC1.as_bytes(), session.s_pub_i.as_bytes()), &packet[..60]);
let mac2 = if needs_cookie(session) { qcrypto_mac(&session.cookies[session.cookies.len() - 1].cookie, &packet[..76]) } else { [0u8; 16] };
if mac1 != msg.mac1 || mac2 != msg.mac2 {
return Err(NoiseError::PacketUnauthenticated)
}
session.e_pub_r = e_pub_r;
session.ck = ck;
session.h = h;
Ok(())
}
struct HandshakeResponseRaw { struct HandshakeResponseRaw {
sender: [u8; 4], sender: [u8; 4],
receiver: [u8; 4], receiver: [u8; 4],
@ -147,3 +51,103 @@ impl HandshakeResponseRaw {
} }
} }
} }
/// Creates a handshake response packet using the current active handshake session.
/// # Errors
/// This function will error if an encryption step is unsuccessful
/// # Panics
/// This function, while containing unwraps, will never panic.
#[allow(clippy::unwrap_used)] // Used for known safe type conversions only
pub fn generate_handshake_response(session: &mut HandshakeState) -> Result<[u8; 92], NoiseError> {
let eph_keypair = qcrypto_dh_generate_longterm();
let mut msg = HandshakeResponseRaw {
sender: [0u8; 4],
receiver: [0u8; 4],
ephemeral: [0u8; 32],
empty: [0u8; 16],
mac1: [0u8; 16],
mac2: [0u8; 16]
};
msg.receiver = session.i_i.to_le_bytes();
msg.sender = session.i_r.to_le_bytes();
session.ck = qcrypto_hkdf::<1>(&session.ck, eph_keypair.1.as_bytes())[0];
msg.ephemeral = eph_keypair.1.to_bytes();
session.h = qcrypto_hash_twice(&session.h, &msg.ephemeral);
session.ck = qcrypto_hkdf::<1>(&session.ck, qcrypto_dh_longterm(&eph_keypair.0, &session.e_pub_i).as_bytes())[0];
session.ck = qcrypto_hkdf::<1>(&session.ck, qcrypto_dh_longterm(&eph_keypair.0, &session.s_pub_i).as_bytes())[0];
let cr_t_k = qcrypto_hkdf::<3>(&session.ck, &session.q);
session.ck = cr_t_k[0];
let t = cr_t_k[1];
let k = cr_t_k[2];
session.h = qcrypto_hash_twice(&session.h, &t);
println!("{:?} {:?} {:?}", k, 0, session.h);
msg.empty = match qcrypto_aead(&k, 0, &[], &session.h) {
Ok(s) => s.try_into().unwrap(),
Err(e) => return Err(NoiseError::ChaCha20Error(e))
};
session.h = qcrypto_hash_twice(&session.h, &msg.empty);
Ok(msg.to_bytes(session))
}
/// Decrypts a handshake response packet using the current active handshake session.
/// # Errors
/// This function will error if a decryption step is unsuccessful
/// # Panics
/// This function, while containing unwraps, will never panic.
pub fn parse_handshake_response(session: &mut HandshakeState, packet: [u8; 92]) -> Result<(), NoiseError> {
let msg = HandshakeResponseRaw::from_bytes(packet);
let e_pub_r = PublicKey::from(msg.ephemeral);
let mut ck = qcrypto_hkdf::<1>(&session.ck, e_pub_r.as_bytes())[0];
let mut h = qcrypto_hash_twice(&session.h, &msg.ephemeral);
ck = qcrypto_hkdf::<1>(&ck, qcrypto_dh_longterm(&session.e_priv_me, &e_pub_r).as_bytes())[0];
ck = qcrypto_hkdf::<1>(&ck, qcrypto_dh_longterm(session.s_priv_me, &e_pub_r).as_bytes())[0];
let cr_t_k = qcrypto_hkdf::<3>(&ck, &session.q);
ck = cr_t_k[0];
let t = cr_t_k[1];
let k = cr_t_k[2];
h = qcrypto_hash_twice(&h, &t);
let empty = match qcrypto_aead_decrypt(&k, 0, &msg.empty, &h) {
Ok(s) => s,
Err(e) => return Err(NoiseError::ChaCha20Error(e))
};
if !empty.is_empty() {
return Err(NoiseError::PacketUnauthenticated)
}
h = qcrypto_hash_twice(&h, &msg.empty);
let mac1: [u8; 16] = qcrypto_mac(&qcrypto_hash_twice(LABEL_MAC1.as_bytes(), session.s_pub_i.as_bytes()), &packet[..60]);
let mac2 = if needs_cookie(session) { qcrypto_mac(&session.cookies[session.cookies.len() - 1].cookie, &packet[..76]) } else { [0u8; 16] };
if mac1 != msg.mac1 || mac2 != msg.mac2 {
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.ck = ck;
session.h = h;
Ok(())
}

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]
@ -10,11 +10,13 @@ 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 = 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);
@ -27,14 +29,16 @@ 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 = 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

@ -5,3 +5,4 @@ pub mod data;
#[cfg(test)] #[cfg(test)]
pub mod tests; pub mod tests;
pub mod rfc6479;

View File

@ -0,0 +1,95 @@
//! A Rust implementation of the anti-replay algorithm described in [RFC 6479](https://rfc-editor.org/rfc6479).
const SIZE_OF_INTEGER: usize = std::mem::size_of::<usize>() * 8;
const BITMAP_LEN: usize = 1024 / SIZE_OF_INTEGER;
const BITMAP_INDEX_MASK: u64 = BITMAP_LEN as u64 - 1;
const REDUNDANT_BIT_SHIFTS: u64 = 5;
const REDUNDANT_BITS: u64 = 1 << REDUNDANT_BIT_SHIFTS;
const BITMAP_LOC_MASK: u64 = REDUNDANT_BITS - 1;
const WINDOW_SIZE: u64 = 512;
#[derive(Default)]
/// An implementation of the RFC6479 anti-replay algorithm
pub struct ShiftWindow {
replaywin_lastseq: u64,
replaywin_bitmap: [usize; BITMAP_LEN],
}
impl ShiftWindow {
/// Create a new ShiftWindow with default values
pub fn new() -> Self {
Self::default()
}
/// Check if a given sequence value is okay given the current state of the shift window
pub fn check_replay_window(&self, seq: u64) -> bool {
println!("sequence {} {}", seq, self.replaywin_lastseq);
// first == 0 or wrapped
if seq == 0 {
return false;
}
// larger is always good
if seq > self.replaywin_lastseq {
return true;
}
// too old
if (seq + WINDOW_SIZE) < self.replaywin_lastseq {
return false;
}
let bit_location = seq & BITMAP_LOC_MASK;
let index = (seq >> REDUNDANT_BIT_SHIFTS) & BITMAP_INDEX_MASK;
self.replaywin_bitmap[index as usize] & (1 << bit_location) == 0
}
/// Update the internal state of the replay window to mark the given sequence number as used and disallow future use of it.
pub fn update_replay_window(&mut self, seq: u64) {
if !self.check_replay_window(seq) {
return;
}
let index = seq >> REDUNDANT_BIT_SHIFTS;
if seq > self.replaywin_lastseq {
let index_cur = self.replaywin_lastseq >> REDUNDANT_BIT_SHIFTS;
let mut diff = index - index_cur;
if diff > BITMAP_LEN as u64 {
diff = BITMAP_LEN as u64;
}
for id in 0..diff {
let iindex = (id + index_cur + 1) & BITMAP_INDEX_MASK;
self.replaywin_bitmap[iindex as usize] = 0;
}
self.replaywin_lastseq = seq;
}
let index = index & BITMAP_INDEX_MASK;
let bit_location = seq & BITMAP_LOC_MASK;
self.replaywin_bitmap[index as usize] |= 1 << bit_location;
}
}
#[cfg(test)]
mod tests {
use crate::noise::rfc6479::ShiftWindow;
#[test]
pub fn rfc6479_tests() {
let mut shiftwin = ShiftWindow::new();
assert!(!shiftwin.check_replay_window(0));
assert!(shiftwin.check_replay_window(1));
shiftwin.update_replay_window(1);
assert!(!shiftwin.check_replay_window(1));
shiftwin.update_replay_window(329_846_324_987);
assert!(!shiftwin.check_replay_window(2));
}
}

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]
@ -12,14 +12,16 @@ 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 = 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();

View File

@ -0,0 +1,60 @@
//! The ENTRY stage of the packet processing stack. Provides a `PacketEntryStage` which implements `PacketProcessingStage`.
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 {
#[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.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<'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_cookie_reply(pkt: Vec<u8>, from: &SocketAddr, interface: &mut QTInterface) -> Result<(), Box<dyn Error>> {
unimplemented!();
}
fn handle_data_packet(pkt: Vec<u8>, from: &SocketAddr, interface: &mut QTInterface) -> Result<(), Box<dyn Error>> {
unimplemented!();
}
}

40
quicktap/src/stack/mod.rs Normal file
View File

@ -0,0 +1,40 @@
//! quicktap's packet processing stack
//! Packet processing is done in several *stages*:
//! - ENTRY
//! - CRYPT
//! - ROUTING
//! - PROCESSING
//! - EXIT
//! # UDP Packet Flow
//! For a packet received on the UDP socket, the following route is taken through the processing stack:
//! ```text
//! UDP Socket -> ENTRY -> CRYPT -> (drop) <-----+
//! | | ^ |
//! | | | |
//! | +-----> ROUTING -> CRYPT -> EXIT
//! | v
//! +---> PROCESSING -> (end flow)
//! ```
//! # TUN Packet Flow
//! For a packet received on the TUN interface, the following route is taken through the processing stack:
//! ```text
//! TUN Interface -> ENTRY -> ROUTING -> CRYPT -> EXIT
//! | |
//! v |
//! (drop) <----+
//! ```
use std::error::Error;
use std::net::SocketAddr;
use crate::device::QTInterface;
pub mod entry_udp;
/// The methods and functions that need to be implemented by a stage in the packet processor
pub trait PacketProcessingStage {
/// Process a raw datagram. This should go down the WHOLE routing chain - assume previous steps have already done.
/// 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<'a>(pkt: Vec<u8>, from: &SocketAddr, interface: &'a mut QTInterface<'a>) -> Result<(), Box<dyn Error>> where Self: Sized;
}

8
quicktap/src/tests.rs Normal file
View File

@ -0,0 +1,8 @@
#![allow(clippy::unwrap_used)] // we want to panic, this is a test file
use crate::version;
#[test] // This is one of those good ol' "just for codecov" tests. This doesn't actually need to be tested, it's for frontends, but I want that juicy 100% codecov
pub const fn version_test() {
version();
}

660
tarpaulin-report.html Normal file

File diff suppressed because one or more lines are too long