Compare commits
10 Commits
8d2e6afec4
...
4e73da252d
Author | SHA1 | Date |
---|---|---|
c0repwn3r | 4e73da252d | |
c0repwn3r | b07895b043 | |
c0repwn3r | 78fb32b253 | |
c0repwn3r | 2f98b78f85 | |
c0repwn3r | 70bff48a54 | |
c0repwn3r | 45bcd98f08 | |
c0repwn3r | e1093cab1b | |
core | 1f5a812262 | |
core | b642112b4d | |
core | c410d7bf81 |
|
@ -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"
|
|
@ -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.
|
|
@ -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
|
||||||
|
}
|
|
@ -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(())
|
||||||
|
}
|
|
@ -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())],
|
|
||||||
|
let args = Cli::parse();
|
||||||
|
|
||||||
|
info!("Hello, world! {}", quicktap::version());
|
||||||
|
info!("Starting up");
|
||||||
|
|
||||||
|
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,
|
mtu: None,
|
||||||
name: "ela1".to_string(),
|
name: args.interface_name.clone(),
|
||||||
}).unwrap();
|
}) {
|
||||||
loop {
|
Ok(d) => d,
|
||||||
println!("{:?}", device.read().unwrap().header)
|
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!");
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"
|
|
@ -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).
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
fn main() {
|
||||||
|
build_info_build::build_script();
|
||||||
|
}
|
|
@ -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]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
|
@ -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());
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(())
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
|
@ -5,3 +5,4 @@ pub mod data;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod tests;
|
pub mod tests;
|
||||||
|
pub mod rfc6479;
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
|
|
@ -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!();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue