[quicktap][transport] transport key derivation
This commit is contained in:
parent
8bc64762f7
commit
041915154a
|
@ -2,3 +2,6 @@
|
||||||
Elaeis is a implementation of the WireGuard protocol (quicktap) and a management system built around it (elaeis itself).
|
Elaeis is a implementation of the WireGuard protocol (quicktap) and a management system built around it (elaeis itself).
|
||||||
|
|
||||||
For more information, check [quicktap's README](quicktap/README.md) or [elaeis4's README](elaeis/README.md)
|
For more information, check [quicktap's README](quicktap/README.md) or [elaeis4's README](elaeis/README.md)
|
||||||
|
|
||||||
|
# Need help?
|
||||||
|
Get in touch! Find us on Matrix at [#e3team-software:e3t.cc](https://matrix.to/#/#e3team-software:e3t.cc).
|
|
@ -0,0 +1,67 @@
|
||||||
|
//! `WireGuard` transport data messages. see 5.4.6
|
||||||
|
|
||||||
|
use crate::noise::error::{NoiseError, NoisePacketParseError};
|
||||||
|
use crate::noise::handshake::HandshakeState;
|
||||||
|
use crate::qcrypto::aead::{qcrypto_aead, qcrypto_aead_decrypt};
|
||||||
|
|
||||||
|
fn pad_packet(p: &Vec<u8>) -> Vec<u8> {
|
||||||
|
let mut padded = p.clone();
|
||||||
|
padded.append(&mut vec![0u8; 16 - (p.len() % 16)]);
|
||||||
|
padded
|
||||||
|
}
|
||||||
|
fn unpad_packet(p: &Vec<u8>) -> Vec<u8> {
|
||||||
|
p.into_iter().rev().skip_while(|x| **x == 0).map(|x| x.clone()).collect::<Vec<u8>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Given any arbitrary IP packet, pad and encrypt it with the current handshake state.
|
||||||
|
/// # Errors
|
||||||
|
/// 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;
|
||||||
|
let packet_data = match qcrypto_aead(&state.t_send, state.n_send, &packet, &[]) {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(e) => return Err(NoiseError::ChaCha20Error(e))
|
||||||
|
};
|
||||||
|
|
||||||
|
let i_other = if state.we_are_initiator { state.i_r } else { state.i_i };
|
||||||
|
|
||||||
|
let mut res = vec![4u8; 16 + packet_data.len()];
|
||||||
|
res[1..4].copy_from_slice(&[0u8; 3]);
|
||||||
|
res[4..8].copy_from_slice(&i_other.to_le_bytes());
|
||||||
|
res[8..16].copy_from_slice(&counter.to_le_bytes());
|
||||||
|
res[16..16+packet_data.len()].copy_from_slice(&packet_data);
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Given an encrypted, encapsulated IP packet, decrypt and unpad it with the current handshake state.
|
||||||
|
/// # Errors
|
||||||
|
/// This function will error if the decryption step is unsuccessful.
|
||||||
|
/// # Panics
|
||||||
|
/// This function, while containing unwraps for type conversions, is safe and will never panic.
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
pub fn deencapsulate(state: &mut HandshakeState, packet: &[u8]) -> Result<Vec<u8>, NoiseError> {
|
||||||
|
if packet[0] != 4u8 {
|
||||||
|
return Err(NoiseError::PacketParseError(NoisePacketParseError::WrongPacketType(4, packet[0] as usize)))
|
||||||
|
}
|
||||||
|
if packet[1..4] != [0u8; 3] {
|
||||||
|
return Err(NoiseError::PacketParseError(NoisePacketParseError::MissingReserved))
|
||||||
|
}
|
||||||
|
let index_me = if state.we_are_initiator { state.i_i } else { state.i_r };
|
||||||
|
if packet[4..8] != index_me.to_le_bytes() {
|
||||||
|
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 {
|
||||||
|
|
||||||
|
}
|
||||||
|
// 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.n_recv = counter;
|
||||||
|
|
||||||
|
Ok(unpad_packet(&packet))
|
||||||
|
}
|
|
@ -12,7 +12,7 @@ pub enum NoiseError {
|
||||||
/// Represents an opaque error from ChaCha
|
/// Represents an opaque error from ChaCha
|
||||||
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,
|
||||||
}
|
}
|
||||||
impl Error for NoiseError {}
|
impl Error for NoiseError {}
|
||||||
impl Display for NoiseError {
|
impl Display for NoiseError {
|
||||||
|
@ -32,12 +32,21 @@ pub enum NoisePacketParseError {
|
||||||
InvalidLength(usize, usize),
|
InvalidLength(usize, usize),
|
||||||
/// Represents that the packet being parsed is of the incorrect type. The first value is the expected packet type, the second was the actual packet type
|
/// Represents that the packet being parsed is of the incorrect type. The first value is the expected packet type, the second was the actual packet type
|
||||||
WrongPacketType(usize, usize),
|
WrongPacketType(usize, usize),
|
||||||
|
/// Represents that the expected 3 bytes of reserved space are missing
|
||||||
|
MissingReserved,
|
||||||
|
/// Represents that the index expected to be present on the message is incorrect
|
||||||
|
WrongIndex(usize, usize),
|
||||||
|
/// Indicates that this packet has been replayed and should be ignored.
|
||||||
|
ReplayAttack,
|
||||||
}
|
}
|
||||||
impl Display for NoisePacketParseError {
|
impl Display for NoisePacketParseError {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
match &self {
|
match &self {
|
||||||
Self::InvalidLength(expected, got) => write!(f, "Invalid packet length: expected {} got {}", expected, got),
|
Self::InvalidLength(expected, got) => write!(f, "Invalid packet length: expected {} got {}", expected, got),
|
||||||
Self::WrongPacketType(expected, got) => write!(f, "Incorrect packet type: expected {} got {}", expected, got)
|
Self::WrongPacketType(expected, got) => write!(f, "Incorrect packet type: expected {} got {}", expected, got),
|
||||||
|
Self::MissingReserved => write!(f, "Missing reserved bytes"),
|
||||||
|
Self::WrongIndex(expected, got) => write!(f, "Incorrect session index: expected {} got {}", expected, got),
|
||||||
|
Self::ReplayAttack => write!(f, "!!! !!! THIS PACKET HAS BEEN REPLAYED !!! !!! Something fishy is going on.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -19,6 +19,7 @@ use crate::qcrypto::pki::{qcrypto_dh_generate_longterm, qcrypto_dh_longterm};
|
||||||
#[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 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_i = PublicKey::from(session.s_priv_me);
|
||||||
session.s_pub_r = session.s_pub_them;
|
session.s_pub_r = session.s_pub_them;
|
||||||
|
|
||||||
|
@ -185,6 +186,7 @@ pub fn handshake_init_from(session: &mut HandshakeState, packet: [u8; 148]) -> R
|
||||||
session.s_pub_i = s_pub_i;
|
session.s_pub_i = s_pub_i;
|
||||||
session.s_pub_r = s_pub_r;
|
session.s_pub_r = s_pub_r;
|
||||||
session.i_i = i_i;
|
session.i_i = i_i;
|
||||||
|
session.we_are_initiator = false;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
|
@ -3,8 +3,9 @@ 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::{EphemeralSecret, PublicKey, StaticSecret};
|
||||||
|
|
||||||
|
use crate::qcrypto::hkdf::qcrypto_hkdf;
|
||||||
use crate::qcrypto::timestamp;
|
use crate::qcrypto::timestamp;
|
||||||
|
|
||||||
pub mod initiator;
|
pub mod initiator;
|
||||||
|
@ -54,7 +55,15 @@ pub struct HandshakeState<'a> {
|
||||||
|
|
||||||
pub q: [u8; 32],
|
pub q: [u8; 32],
|
||||||
|
|
||||||
pub cookies: Vec<Cookie>
|
pub cookies: Vec<Cookie>,
|
||||||
|
|
||||||
|
pub t_send: [u8; 32],
|
||||||
|
pub t_recv: [u8; 32],
|
||||||
|
|
||||||
|
pub n_send: u64,
|
||||||
|
pub n_recv: u64,
|
||||||
|
|
||||||
|
pub we_are_initiator: bool
|
||||||
}
|
}
|
||||||
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
|
||||||
|
@ -63,6 +72,26 @@ impl<'a> HandshakeState<'a> {
|
||||||
self.h == other.h && self.ck == other.ck && self.e_pub_i == other.e_pub_i && self.s_pub_i == other.s_pub_i && self.s_pub_r == other.s_pub_r && self.i_i == other.i_i && self.i_r == other.i_r
|
self.h == other.h && self.ck == other.ck && self.e_pub_i == other.e_pub_i && self.s_pub_i == other.s_pub_i && self.s_pub_r == other.s_pub_r && self.i_i == other.i_i && self.i_r == other.i_r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Zeroes out handshake state and derives data transport keys. The handshake must be completed and the other party must do this at the same time otherwise the handshake will be trashed and you have to start over.
|
||||||
|
pub fn derive_transport(&mut self) {
|
||||||
|
let t_send_recv = qcrypto_hkdf::<2>(&self.ck, &[]);
|
||||||
|
if self.we_are_initiator {
|
||||||
|
self.t_send = t_send_recv[0];
|
||||||
|
self.t_recv = t_send_recv[1];
|
||||||
|
} else {
|
||||||
|
self.t_send = t_send_recv[1];
|
||||||
|
self.t_recv = t_send_recv[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
self.n_send = 0;
|
||||||
|
self.n_recv = 0;
|
||||||
|
|
||||||
|
self.e_pub_r = PublicKey::from([0u8; 32]);
|
||||||
|
self.e_priv_me = StaticSecret::from([0u8; 32]);
|
||||||
|
self.e_pub_i = PublicKey::from([0u8; 32]);
|
||||||
|
self.ck = [0u8; 32];
|
||||||
|
}
|
||||||
|
|
||||||
/// 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, other_pubkey: PublicKey, pre_shared_key: Option<[u8; 32]>) -> Self {
|
||||||
|
@ -80,6 +109,11 @@ impl<'a> HandshakeState<'a> {
|
||||||
i_r: 0,
|
i_r: 0,
|
||||||
q: pre_shared_key.unwrap_or([0u8; 32]),
|
q: pre_shared_key.unwrap_or([0u8; 32]),
|
||||||
cookies: vec![],
|
cookies: vec![],
|
||||||
|
t_send: [0u8; 32],
|
||||||
|
t_recv: [0u8; 32],
|
||||||
|
n_send: 0,
|
||||||
|
n_recv: 0,
|
||||||
|
we_are_initiator: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
//! Contains structs and functions for serializing and deserializing different packets in the `Noise_IKpsk2` handshake and data frames
|
//! Contains structs and functions for serializing and deserializing different packets in the `Noise_IKpsk2` handshake and data frames
|
||||||
pub mod handshake;
|
pub mod handshake;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
pub mod data;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod tests;
|
|
@ -0,0 +1,35 @@
|
||||||
|
#![allow(clippy::unwrap_used)]
|
||||||
|
#![allow(clippy::pedantic)]
|
||||||
|
|
||||||
|
use crate::noise::data::{deencapsulate, encapsulate};
|
||||||
|
use crate::noise::handshake::HandshakeState;
|
||||||
|
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]
|
||||||
|
pub fn noise_transport_test() {
|
||||||
|
let alice_keypair = qcrypto_dh_generate_longterm();
|
||||||
|
let bob_keypair = qcrypto_dh_generate_longterm();
|
||||||
|
|
||||||
|
let mut alice_session = HandshakeState::new(&alice_keypair.0, bob_keypair.1, None);
|
||||||
|
let mut bob_session = HandshakeState::new(&bob_keypair.0, alice_keypair.1, None);
|
||||||
|
|
||||||
|
let handshake_init = handshake_init_to(&mut alice_session).unwrap();
|
||||||
|
handshake_init_from(&mut bob_session, handshake_init).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();
|
||||||
|
|
||||||
|
assert!(alice_session.is_eq(&bob_session));
|
||||||
|
|
||||||
|
let packet_abc = vec![42u8; 17];
|
||||||
|
let packet_encapsulated = encapsulate(&mut alice_session, &packet_abc).unwrap();
|
||||||
|
|
||||||
|
let packet_abc_deencapsulated = deencapsulate(&mut bob_session, &packet_encapsulated).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(packet_abc, packet_abc_deencapsulated)
|
||||||
|
}
|
Loading…
Reference in New Issue