[noise][stack] fixup initiator to increase security and allow it to initialize HandshakeStates even when we dont know the other parties public key prior to initialization

This commit is contained in:
c0repwn3r 2023-01-12 13:30:11 -05:00
parent 2f98b78f85
commit 78fb32b253
Signed by: core
GPG Key ID: FDBF740DADDCEECF
6 changed files with 64 additions and 27 deletions

View File

@ -1,2 +1,37 @@
//! 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;
use crate::noise::handshake::HandshakeState;
#[derive(Default)]
/// A high-level struct for the configuration of a `WireGuard` device. This is the equivalent of `[Interface]` in wg-quick.
pub struct QTInterface<'a> {
peers: HashMap<PublicKey, QTPeer<'a>>
}
impl<'a> QTInterface<'a> {
/// Create a new, blank `QTInterface` with no configuration
pub fn new() -> Self {
QTInterface {
peers: HashMap::default(),
}
}
}
/// A high-level struct for the current state with another peer. This is the equivalent of `[Peer]` in wg-quick, with additional tunneling information added.
pub struct QTPeer<'a> {
handshake: Option<HandshakeState<'a>>,
handshake_in_flight: Option<HandshakeState<'a>>,
public_key: PublicKey
}
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,
}
}
}

View File

@ -3,8 +3,9 @@
use std::error::Error;
use std::io;
use std::net::UdpSocket;
use crate::device::QTInterface;
use crate::drivers::error::DriverError;
use crate::stack::entry::PacketEntryStage;
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.
@ -29,7 +30,7 @@ pub enum SocketRoundResult {
/// If you're writing a frontend, this is probably not what you want. Use `qtap_socket_rounds_until_empty` instead.
/// # Errors
/// This function will error if a socket read fails or the processing of the packet fails. See `PacketEntryStage::process_packet()` for more details on packet processing.
pub fn qtap_socket_round(socket: &mut UdpSocket) -> Result<(), SocketRoundResult> {
pub fn qtap_socket_round(socket: &mut UdpSocket, interface: &QTInterface) -> Result<(), SocketRoundResult> {
let mut packet_id = [0u8; 1];
match socket.peek(&mut packet_id) {
Ok(_) => (),
@ -54,7 +55,7 @@ pub fn qtap_socket_round(socket: &mut UdpSocket) -> Result<(), SocketRoundResult
let packet = packet_buf[..read_bytes].to_vec();
match PacketEntryStage::process_packet(packet, &from) {
match UDPPacketEntryStage::process_packet(packet, &from, interface) {
Ok(_) => Ok(()),
Err(e) => Err(SocketRoundResult::GenericAnyError(e))
}
@ -65,9 +66,9 @@ pub fn qtap_socket_round(socket: &mut UdpSocket) -> Result<(), SocketRoundResult
/// If you only want to run one update, use `qtap_socket_round` instead.
/// # Errors
/// See `qtap_socket_round`
pub fn qtap_socket_rounds_until_empty(socket: &mut UdpSocket) -> Result<(), Box<dyn Error>> {
pub fn qtap_socket_rounds_until_empty(socket: &mut UdpSocket, interface: &QTInterface) -> Result<(), Box<dyn Error>> {
loop {
match qtap_socket_round(socket) {
match qtap_socket_round(socket, interface) {
Ok(_) => (),
Err(e) => {
match e {

View File

@ -117,7 +117,7 @@ pub fn generate_handshake_init(session: &mut HandshakeState) -> Result<[u8; 148]
Ok(msg.to_bytes(session))
}
/// Parse a handshake initiator packet and encrypt it using the given session state, updating the session state with decrypted and authenticated values
/// Parse a handshake initiator packet and decrypt 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
@ -125,7 +125,6 @@ pub fn generate_handshake_init(session: &mut HandshakeState) -> Result<[u8; 148]
#[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> {
let s_pub_i = session.s_pub_them;
let s_pub_r = PublicKey::from(session.s_priv_me);
let msg = HandshakeInitiatorRaw::from_bytes(packet);
@ -151,14 +150,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
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,
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, &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 k = ci_k_pair[1];
@ -172,7 +171,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(), 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] };
if mac1 != msg.mac1 || mac2 != msg.mac2 {

View File

@ -1,14 +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 crate::stack::{PacketProcessingStage};
/// The ENTRY stage of the packet processing stack
pub struct PacketEntryStage {}
impl PacketProcessingStage for PacketEntryStage {
fn process_packet(pkt: Vec<u8>, from: &SocketAddr) -> Result<(), Box<dyn Error>> {
Ok(())
}
}

View File

@ -0,0 +1,15 @@
//! The ENTRY stage of the packet processing stack. Provides a `PacketEntryStage` which implements `PacketProcessingStage`.
use std::error::Error;
use std::net::SocketAddr;
use crate::device::QTInterface;
use crate::stack::{PacketProcessingStage};
/// The ENTRY stage of the packet processing stack for UDP packets
pub struct UDPPacketEntryStage {}
impl PacketProcessingStage for UDPPacketEntryStage {
fn process_packet(pkt: Vec<u8>, from: &SocketAddr, interface: &QTInterface) -> Result<(), Box<dyn Error>> {
unimplemented!();
}
}

View File

@ -26,8 +26,9 @@
use std::error::Error;
use std::net::SocketAddr;
use crate::device::QTInterface;
pub mod entry;
pub mod entry_udp;
/// The methods and functions that need to be implemented by a stage in the packet processor
pub trait PacketProcessingStage {
@ -35,5 +36,5 @@ pub trait PacketProcessingStage {
/// For example, if you are the CRYPT layer, and are processing a tun packet, assume that the ENTRY and ROUTING stack stages have already been completed and you are being given the result of that processing.
/// # Errors
/// This function will error if an error occurs
fn process_packet(pkt: Vec<u8>, from: &SocketAddr) -> Result<(), Box<dyn Error>> where Self: Sized;
fn process_packet(pkt: Vec<u8>, from: &SocketAddr, interface: &QTInterface) -> Result<(), Box<dyn Error>> where Self: Sized;
}