Compare commits
No commits in common. "4e73da252d0d3aed5adb7948402f7616224340a7" and "8d2e6afec4a0ac67a2e6233025a0b91d8633c0f1" have entirely different histories.
4e73da252d
...
8d2e6afec4
|
@ -6,8 +6,4 @@ edition = "2021"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
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"
|
||||
quicktap = { path = "../quicktap", version = "0.1.0" }
|
|
@ -1,20 +0,0 @@
|
|||
# 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.
|
|
@ -1,9 +0,0 @@
|
|||
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
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
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,132 +1,15 @@
|
|||
|
||||
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 std::net::{Ipv4Addr};
|
||||
use quicktap::cidr::{IpInet, Ipv4Inet};
|
||||
use quicktap::drivers::tun::TunDevice;
|
||||
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() {
|
||||
simple_logger::init().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![],
|
||||
let mut device = TunDevice::new(&GenericInterface {
|
||||
addresses: vec![IpInet::V4(Ipv4Inet::new(Ipv4Addr::new(10, 19, 2, 2), 16).unwrap())],
|
||||
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);
|
||||
}
|
||||
name: "ela1".to_string(),
|
||||
}).unwrap();
|
||||
loop {
|
||||
println!("{:?}", device.read().unwrap().header)
|
||||
}
|
||||
|
||||
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!");
|
||||
}
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
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,15 +14,10 @@ x25519-dalek = "2.0.0-pre.1"
|
|||
rand = "0.8.5"
|
||||
hmac = "0.12.1"
|
||||
chacha20poly1305 = "0.10.1"
|
||||
build-info = "0.0.29"
|
||||
log = "0.4.17"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
tun = "0.5.4"
|
||||
|
||||
[dev-dependencies]
|
||||
hex_lit = "0.1.1"
|
||||
hex = "0.4.3"
|
||||
|
||||
[build-dependencies]
|
||||
build-info-build = "0.0.29"
|
||||
hex = "0.4.3"
|
|
@ -1,4 +1,4 @@
|
|||
# Quicktap Engine
|
||||
# Quicktap
|
||||
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).
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
fn main() {
|
||||
build_info_build::build_script();
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
//! 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]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
//! 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,10 +3,8 @@
|
|||
use std::error::Error;
|
||||
use std::io;
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use etherparse::IpHeader;
|
||||
use tun::platform::Device;
|
||||
|
||||
use crate::drivers::tungeneric::{GenericDriver, GenericInterface, TunPacket};
|
||||
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
|
@ -33,18 +31,15 @@ impl GenericDriver for TunDevice {
|
|||
if let Some(mtu) = config.mtu {
|
||||
device_config.mtu(mtu);
|
||||
}
|
||||
let device = tun::create(&device_config)?;
|
||||
device.set_nonblock()?;
|
||||
|
||||
Ok(Self {
|
||||
device,
|
||||
device: tun::create(&device_config)?,
|
||||
read_buf: [0u8; 4096],
|
||||
read_offset: 0,
|
||||
packet_buf: [0u8; 4096],
|
||||
})
|
||||
}
|
||||
|
||||
fn read(&mut self) -> Result<TunPacket, io::Error> {
|
||||
fn read(&mut self) -> Result<TunPacket, Box<dyn Error>> {
|
||||
self.packet_buf = [0u8; 4096];
|
||||
let _ = self.device.read(&mut self.packet_buf)?;
|
||||
let ip_header = IpHeader::from_slice(&self.packet_buf);
|
||||
|
@ -58,7 +53,7 @@ impl GenericDriver for TunDevice {
|
|||
})
|
||||
}
|
||||
|
||||
Err(io::Error::new(io::ErrorKind::WouldBlock, "No packets detected yet"))
|
||||
Err(io::Error::new(io::ErrorKind::WouldBlock, "No packets detected yet").into())
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
|
@ -66,9 +61,8 @@ impl GenericDriver for TunDevice {
|
|||
self.read_offset = 0;
|
||||
}
|
||||
|
||||
fn write(&mut self, packet: TunPacket) -> Result<(), io::Error> {
|
||||
fn write(&mut self, packet: TunPacket) -> Result<(), Box<dyn Error>> {
|
||||
let _ = self.device.write(&packet.packet)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -4,10 +4,4 @@
|
|||
#[path = "linux.rs"]
|
||||
pub mod tun; // Tun/tap drivers for Linux
|
||||
|
||||
pub mod tungeneric;
|
||||
|
||||
pub mod udp_listener;
|
||||
pub mod error;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests;
|
||||
pub mod tungeneric;
|
|
@ -1,11 +0,0 @@
|
|||
#![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,10 +1,8 @@
|
|||
//! Generic traits and modules to represent a tun/tap device
|
||||
|
||||
use std::error::Error;
|
||||
use std::io;
|
||||
|
||||
use cidr::IpInet;
|
||||
use etherparse::IpHeader;
|
||||
use etherparse::{IpHeader};
|
||||
|
||||
#[derive(Debug)]
|
||||
/// A parsed packet from an interface
|
||||
|
@ -26,7 +24,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.
|
||||
/// # 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.
|
||||
fn read(&mut self) -> Result<TunPacket, io::Error>;
|
||||
fn read(&mut self) -> Result<TunPacket, Box<dyn Error>>;
|
||||
|
||||
/// Clear any internal state of this interface.
|
||||
fn clear(&mut self);
|
||||
|
@ -34,7 +32,7 @@ pub trait GenericDriver {
|
|||
/// Attempt to write a packet to the interface.
|
||||
/// # Errors
|
||||
/// This function will error if an error occured writing to the device.
|
||||
fn write(&mut self, packet: TunPacket) -> Result<(), io::Error>;
|
||||
fn write(&mut self, packet: TunPacket) -> Result<(), Box<dyn Error>>;
|
||||
}
|
||||
|
||||
/// Generic interface config
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
//! 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,20 +10,9 @@
|
|||
|
||||
// This is an annoyance
|
||||
#![allow(clippy::must_use_candidate)]
|
||||
#![allow(clippy::module_name_repetitions)]
|
||||
|
||||
pub use cidr;
|
||||
|
||||
pub mod drivers;
|
||||
pub mod qcrypto;
|
||||
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)
|
||||
}
|
||||
pub mod noise;
|
|
@ -18,8 +18,8 @@ fn unpad_packet(p: &[u8]) -> Vec<u8> {
|
|||
/// This function will error if the encryption step is unsuccessful.
|
||||
pub fn encapsulate(state: &mut HandshakeState, packet: &Vec<u8>) -> Result<Vec<u8>, NoiseError> {
|
||||
let packet = pad_packet(packet);
|
||||
let counter = state.n_send + 1;
|
||||
let packet_data = match qcrypto_aead(&state.t_send, state.n_send + 1, &packet, &[]) {
|
||||
let counter = state.n_send;
|
||||
let packet_data = match qcrypto_aead(&state.t_send, state.n_send, &packet, &[]) {
|
||||
Ok(d) => d,
|
||||
Err(e) => return Err(NoiseError::ChaCha20Error(e))
|
||||
};
|
||||
|
@ -32,8 +32,6 @@ pub fn encapsulate(state: &mut HandshakeState, packet: &Vec<u8>) -> Result<Vec<u
|
|||
res[8..16].copy_from_slice(&counter.to_le_bytes());
|
||||
res[16..16+packet_data.len()].copy_from_slice(&packet_data);
|
||||
|
||||
state.n_send += 1;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
|
@ -55,18 +53,14 @@ 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()))))
|
||||
}
|
||||
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..], &[]) {
|
||||
Ok(p) => p,
|
||||
Err(e) => return Err(NoiseError::ChaCha20Error(e))
|
||||
};
|
||||
|
||||
state.bitfield.update_replay_window(counter);
|
||||
state.n_recv = counter;
|
||||
|
||||
Ok(unpad_packet(&packet))
|
||||
|
|
|
@ -13,10 +13,6 @@ pub enum NoiseError {
|
|||
ChaCha20Error(chacha20poly1305::Error),
|
||||
/// Represents that the packet had a missing or incorrect cookie MAC.
|
||||
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 Display for NoiseError {
|
||||
|
@ -24,9 +20,7 @@ impl Display for NoiseError {
|
|||
match &self {
|
||||
Self::PacketParseError(err) => write!(f, "{}", err),
|
||||
Self::ChaCha20Error(error) => write!(f, "Encryption error: {}", error),
|
||||
Self::PacketUnauthenticated => write!(f, "Unauthenticated packet"),
|
||||
Self::UnrelatedHandshakePacket => write!(f, "Unrelated handshake packet"),
|
||||
Self::PacketReplayed => write!(f, "Packet was replayed")
|
||||
Self::PacketUnauthenticated => write!(f, "Unauthenticated packet")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,48 +11,6 @@ use crate::qcrypto::hashes::{qcrypto_hash_twice, qcrypto_mac};
|
|||
use crate::qcrypto::hkdf::qcrypto_hkdf;
|
||||
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
|
||||
/// # Errors
|
||||
/// This function will error if encryption was unsuccessful
|
||||
|
@ -60,7 +18,7 @@ impl HandshakeInitiatorRaw {
|
|||
/// While containing unwraps, this function will never panic.
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
#[allow(clippy::unwrap_used)] // Safe because it is only used for type conversions in known safe ways
|
||||
pub fn generate_handshake_init(session: &mut HandshakeState) -> Result<[u8; 148], NoiseError> {
|
||||
pub fn handshake_init_to(session: &mut HandshakeState) -> Result<[u8; 148], NoiseError> {
|
||||
session.we_are_initiator = true;
|
||||
session.s_pub_i = PublicKey::from(session.s_priv_me);
|
||||
session.s_pub_r = session.s_pub_them;
|
||||
|
@ -117,14 +75,58 @@ pub fn generate_handshake_init(session: &mut HandshakeState) -> Result<[u8; 148]
|
|||
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
|
||||
/// This function will error if decryption was unsuccessful
|
||||
/// # Panics
|
||||
/// While containing unwraps, this function will never panic.
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
#[allow(clippy::unwrap_used)] // Only used for type conversions in known safe ways
|
||||
pub fn parse_handshake_init(session: &mut HandshakeState, packet: [u8; 148]) -> Result<(), NoiseError> {
|
||||
pub fn handshake_init_from(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 msg = HandshakeInitiatorRaw::from_bytes(packet);
|
||||
|
@ -150,14 +152,14 @@ pub fn parse_handshake_init(session: &mut HandshakeState, packet: [u8; 148]) ->
|
|||
// This unwrap is safe because the output length is a known constant with these inputs
|
||||
|
||||
|
||||
let s_pub_i = PublicKey::from(<Vec<u8> as TryInto<[u8; 32]>>::try_into(match qcrypto_aead_decrypt(&k, 0, &msg.static_pub, &h) {
|
||||
session.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,
|
||||
Err(e) => return Err(NoiseError::ChaCha20Error(e))
|
||||
}).unwrap());
|
||||
|
||||
let h = qcrypto_hash_twice(&h, &msg.static_pub);
|
||||
|
||||
let ci_k_pair = qcrypto_hkdf::<2>(&ck, qcrypto_dh_longterm(session.s_priv_me, &s_pub_i).as_bytes());
|
||||
let ci_k_pair = qcrypto_hkdf::<2>(&ck, qcrypto_dh_longterm(session.s_priv_me, &session.s_pub_i).as_bytes());
|
||||
let ck = ci_k_pair[0];
|
||||
let k = ci_k_pair[1];
|
||||
|
||||
|
@ -171,7 +173,7 @@ pub fn parse_handshake_init(session: &mut HandshakeState, packet: [u8; 148]) ->
|
|||
|
||||
// we need to check mac1 and mac2
|
||||
|
||||
let mac1: [u8; 16] = qcrypto_mac(&qcrypto_hash_twice(LABEL_MAC1.as_bytes(), s_pub_i.as_bytes()), &packet[..116]);
|
||||
let mac1: [u8; 16] = qcrypto_mac(&qcrypto_hash_twice(LABEL_MAC1.as_bytes(), session.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] };
|
||||
|
||||
if mac1 != msg.mac1 || mac2 != msg.mac2 {
|
||||
|
|
|
@ -4,7 +4,6 @@ use std::fmt::{Debug, Formatter};
|
|||
use rand::rngs::OsRng;
|
||||
use tai64::Tai64N;
|
||||
use x25519_dalek::{PublicKey, StaticSecret};
|
||||
use crate::noise::rfc6479::ShiftWindow;
|
||||
|
||||
use crate::qcrypto::hkdf::qcrypto_hkdf;
|
||||
use crate::qcrypto::timestamp;
|
||||
|
@ -27,22 +26,6 @@ pub const HANDSHAKE_INITIATOR_CHAIN_KEY_HASH: [u8; 32] = [
|
|||
147, 232, 183, 14, 225, 156, 101, 186, 7, 158, 243,
|
||||
];
|
||||
|
||||
/// Represents what mode the handshake state machine currently is in.
|
||||
pub enum HandshakeMode {
|
||||
/// No state is currently present; an init has not been sent or received.
|
||||
Ready,
|
||||
/// We have received an init from the other peer, and need to send the ack packet.
|
||||
InitReceived,
|
||||
/// We have sent an init to the other peer, and are waiting for their ack packet
|
||||
InitSent,
|
||||
/// We have received the ack packet from the other peer, and are ready to derive data keys and begin transport.
|
||||
AckReceived,
|
||||
/// We have sent the ack packet to the other peer, and are ready to derive data keys and begin transport.
|
||||
AckSent,
|
||||
/// We have derived data keys and are ready to send data.
|
||||
Data
|
||||
}
|
||||
|
||||
/// Represents a cookie we got from the other peer
|
||||
#[derive(Debug)]
|
||||
pub struct Cookie {
|
||||
|
@ -80,11 +63,7 @@ pub struct HandshakeState<'a> {
|
|||
pub n_send: u64,
|
||||
pub n_recv: u64,
|
||||
|
||||
pub we_are_initiator: bool,
|
||||
|
||||
pub bitfield: ShiftWindow,
|
||||
|
||||
pub mode: HandshakeMode
|
||||
pub we_are_initiator: bool
|
||||
}
|
||||
impl<'a> HandshakeState<'a> {
|
||||
/// Determines if the state variables of this `HandshakeState` are the same as another
|
||||
|
@ -115,7 +94,7 @@ impl<'a> HandshakeState<'a> {
|
|||
|
||||
/// Create a new handshake state representing a brand-new handshake.
|
||||
/// This function initializes the important values with their appropriate initialization vectors, and zeroes out all other values.
|
||||
pub fn new(private_key: &'a StaticSecret) -> Self {
|
||||
pub fn new(private_key: &'a StaticSecret, other_pubkey: PublicKey, pre_shared_key: Option<[u8; 32]>) -> Self {
|
||||
Self {
|
||||
h: [0u8; 32],
|
||||
ck: [0u8; 32],
|
||||
|
@ -125,18 +104,16 @@ impl<'a> HandshakeState<'a> {
|
|||
s_pub_r: PublicKey::from([0u8; 32]),
|
||||
e_priv_me: StaticSecret::new(OsRng),
|
||||
s_priv_me: private_key,
|
||||
s_pub_them: PublicKey::from([0u8; 32]),
|
||||
s_pub_them: other_pubkey,
|
||||
i_i: 0,
|
||||
i_r: 0,
|
||||
q: [0u8; 32],
|
||||
q: pre_shared_key.unwrap_or([0u8; 32]),
|
||||
cookies: vec![],
|
||||
t_send: [0u8; 32],
|
||||
t_recv: [0u8; 32],
|
||||
n_send: 1,
|
||||
n_recv: 1,
|
||||
n_send: 0,
|
||||
n_recv: 0,
|
||||
we_are_initiator: false,
|
||||
bitfield: ShiftWindow::new(),
|
||||
mode: HandshakeMode::Ready
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,102 @@ use crate::qcrypto::hkdf::qcrypto_hkdf;
|
|||
use crate::qcrypto::LABEL_MAC1;
|
||||
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 {
|
||||
sender: [u8; 4],
|
||||
receiver: [u8; 4],
|
||||
|
@ -50,104 +146,4 @@ impl HandshakeResponseRaw {
|
|||
mac2: packet[76..92].try_into().unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
|
||||
use crate::noise::handshake::HandshakeState;
|
||||
use crate::noise::handshake::initiator::{parse_handshake_init, generate_handshake_init};
|
||||
use crate::noise::handshake::response::{parse_handshake_response, generate_handshake_response};
|
||||
use crate::noise::handshake::initiator::{handshake_init_from, handshake_init_to};
|
||||
use crate::noise::handshake::response::{handshake_response_from, handshake_response_to};
|
||||
use crate::qcrypto::pki::qcrypto_dh_generate_longterm;
|
||||
|
||||
#[test]
|
||||
|
@ -10,13 +10,11 @@ fn noise_halfhandshake_test() {
|
|||
let alice_keypair = qcrypto_dh_generate_longterm();
|
||||
let bob_keypair = qcrypto_dh_generate_longterm();
|
||||
|
||||
let mut alice_session = HandshakeState::new(&alice_keypair.0);
|
||||
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 mut alice_session = HandshakeState::new(&alice_keypair.0, bob_keypair.1, None);
|
||||
let mut bob_session = HandshakeState::new(&bob_keypair.0, alice_keypair.1, None);
|
||||
|
||||
let handshake_init = generate_handshake_init(&mut alice_session).unwrap();
|
||||
parse_handshake_init(&mut bob_session, handshake_init).unwrap();
|
||||
let handshake_init = handshake_init_to(&mut alice_session).unwrap();
|
||||
handshake_init_from(&mut bob_session, handshake_init).unwrap();
|
||||
|
||||
println!("{:?}", alice_session);
|
||||
println!("{:?}", bob_session);
|
||||
|
@ -29,16 +27,14 @@ fn noise_nocookie_handshake_test() {
|
|||
let alice_keypair = qcrypto_dh_generate_longterm();
|
||||
let bob_keypair = qcrypto_dh_generate_longterm();
|
||||
|
||||
let mut alice_session = HandshakeState::new(&alice_keypair.0);
|
||||
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 mut alice_session = HandshakeState::new(&alice_keypair.0, bob_keypair.1, None);
|
||||
let mut bob_session = HandshakeState::new(&bob_keypair.0, alice_keypair.1, None);
|
||||
|
||||
let handshake_init = generate_handshake_init(&mut alice_session).unwrap();
|
||||
parse_handshake_init(&mut bob_session, handshake_init).unwrap();
|
||||
let handshake_init = handshake_init_to(&mut alice_session).unwrap();
|
||||
handshake_init_from(&mut bob_session, handshake_init).unwrap();
|
||||
|
||||
let handshake_response = generate_handshake_response(&mut bob_session).unwrap();
|
||||
parse_handshake_response(&mut alice_session, handshake_response).unwrap();
|
||||
let handshake_response = handshake_response_to(&mut bob_session).unwrap();
|
||||
handshake_response_from(&mut alice_session, handshake_response).unwrap();
|
||||
|
||||
println!("{:?}", alice_session);
|
||||
println!("{:?}", bob_session);
|
||||
|
|
|
@ -4,5 +4,4 @@ pub mod error;
|
|||
pub mod data;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests;
|
||||
pub mod rfc6479;
|
||||
pub mod tests;
|
|
@ -1,95 +0,0 @@
|
|||
//! 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::handshake::HandshakeState;
|
||||
use crate::noise::handshake::initiator::{parse_handshake_init, generate_handshake_init};
|
||||
use crate::noise::handshake::response::{parse_handshake_response, generate_handshake_response};
|
||||
use crate::noise::handshake::initiator::{handshake_init_from, handshake_init_to};
|
||||
use crate::noise::handshake::response::{handshake_response_from, handshake_response_to};
|
||||
use crate::qcrypto::pki::qcrypto_dh_generate_longterm;
|
||||
|
||||
#[test]
|
||||
|
@ -12,16 +12,14 @@ pub fn noise_transport_test() {
|
|||
let alice_keypair = qcrypto_dh_generate_longterm();
|
||||
let bob_keypair = qcrypto_dh_generate_longterm();
|
||||
|
||||
let mut alice_session = HandshakeState::new(&alice_keypair.0);
|
||||
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 mut alice_session = HandshakeState::new(&alice_keypair.0, bob_keypair.1, None);
|
||||
let mut bob_session = HandshakeState::new(&bob_keypair.0, alice_keypair.1, None);
|
||||
|
||||
let handshake_init = generate_handshake_init(&mut alice_session).unwrap();
|
||||
parse_handshake_init(&mut bob_session, handshake_init).unwrap();
|
||||
let handshake_init = handshake_init_to(&mut alice_session).unwrap();
|
||||
handshake_init_from(&mut bob_session, handshake_init).unwrap();
|
||||
|
||||
let handshake_response = generate_handshake_response(&mut bob_session).unwrap();
|
||||
parse_handshake_response(&mut alice_session, handshake_response).unwrap();
|
||||
let handshake_response = handshake_response_to(&mut bob_session).unwrap();
|
||||
handshake_response_from(&mut alice_session, handshake_response).unwrap();
|
||||
|
||||
alice_session.derive_transport();
|
||||
bob_session.derive_transport();
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
//! 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!();
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
//! 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;
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
#![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