144 lines
No EOL
4.7 KiB
Rust
144 lines
No EOL
4.7 KiB
Rust
//! `Noise_IKpsk2` handshake, specifically the way `WireGuard` defines it
|
|
use std::fmt::{Debug, Formatter};
|
|
|
|
use rand::rngs::OsRng;
|
|
use tai64::Tai64N;
|
|
use x25519_dalek::{PublicKey, StaticSecret};
|
|
|
|
use crate::qcrypto::hkdf::qcrypto_hkdf;
|
|
use crate::qcrypto::timestamp;
|
|
|
|
pub mod initiator;
|
|
pub mod response;
|
|
|
|
#[cfg(test)]
|
|
pub mod tests;
|
|
|
|
/// The Blake2s hash of the construction
|
|
pub const HANDSHAKE_INITIATOR_CHAIN_KEY: [u8; 32] = [
|
|
96, 226, 109, 174, 243, 39, 239, 192, 46, 195, 53, 226, 160, 37, 210, 208, 22, 235, 66, 6, 248,
|
|
114, 119, 245, 45, 56, 209, 152, 139, 120, 205, 54,
|
|
];
|
|
|
|
/// The hashed chaining key
|
|
pub const HANDSHAKE_INITIATOR_CHAIN_KEY_HASH: [u8; 32] = [
|
|
34, 17, 179, 97, 8, 26, 197, 102, 105, 18, 67, 219, 69, 138, 213, 50, 45, 156, 108, 102, 34,
|
|
147, 232, 183, 14, 225, 156, 101, 186, 7, 158, 243,
|
|
];
|
|
|
|
/// Represents a cookie we got from the other peer
|
|
#[derive(Debug)]
|
|
pub struct Cookie {
|
|
time: Tai64N,
|
|
cookie: [u8; 16]
|
|
}
|
|
|
|
/// Represents the internal handshake state. This does not really need to be messed with by outside users
|
|
#[allow(missing_docs)]
|
|
#[allow(clippy::module_name_repetitions)]
|
|
pub struct HandshakeState<'a> {
|
|
pub h: [u8; 32],
|
|
pub ck: [u8; 32],
|
|
|
|
pub e_pub_i: PublicKey,
|
|
pub e_pub_r: PublicKey,
|
|
|
|
pub s_pub_i: PublicKey,
|
|
pub s_pub_r: PublicKey,
|
|
|
|
pub e_priv_me: StaticSecret,
|
|
pub s_priv_me: &'a StaticSecret,
|
|
pub s_pub_them: PublicKey,
|
|
|
|
pub i_i: u32,
|
|
pub i_r: u32,
|
|
|
|
pub q: [u8; 32],
|
|
|
|
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> {
|
|
/// Determines if the state variables of this `HandshakeState` are the same as another
|
|
#[allow(clippy::suspicious_operation_groupings)]
|
|
pub fn is_eq(&self, other: &HandshakeState) -> bool {
|
|
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.
|
|
/// 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 {
|
|
Self {
|
|
h: [0u8; 32],
|
|
ck: [0u8; 32],
|
|
e_pub_i: PublicKey::from([0u8; 32]),
|
|
e_pub_r: PublicKey::from([0u8; 32]),
|
|
s_pub_i: PublicKey::from([0u8; 32]),
|
|
s_pub_r: PublicKey::from([0u8; 32]),
|
|
e_priv_me: StaticSecret::new(OsRng),
|
|
s_priv_me: private_key,
|
|
s_pub_them: other_pubkey,
|
|
i_i: 0,
|
|
i_r: 0,
|
|
q: pre_shared_key.unwrap_or([0u8; 32]),
|
|
cookies: vec![],
|
|
t_send: [0u8; 32],
|
|
t_recv: [0u8; 32],
|
|
n_send: 0,
|
|
n_recv: 0,
|
|
we_are_initiator: false,
|
|
}
|
|
}
|
|
}
|
|
impl<'a> Debug for HandshakeState<'a> {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_struct("HandshakeState")
|
|
.field("h", &self.h)
|
|
.field("ck", &self.ck)
|
|
.field("e_pub_i", &self.e_pub_i)
|
|
.field("s_pub_i", &self.s_pub_i)
|
|
.field("s_pub_r", &self.s_pub_r)
|
|
.field("s_pub_them", &self.s_pub_them)
|
|
.field("i_i", &self.i_i)
|
|
.field("i_r", &self.i_r)
|
|
.field("cookies", &self.cookies).finish()
|
|
}
|
|
}
|
|
|
|
/// Determines if a cookie MAC needs to be added to the packet being sent
|
|
pub fn needs_cookie(session: &HandshakeState) -> bool {
|
|
if !session.cookies.is_empty() {
|
|
let past_cookie_timeout = session.cookies[session.cookies.len()-1].time.duration_since(×tamp()).map_or(true, |t| t.as_secs() >= 120);
|
|
if !past_cookie_timeout {
|
|
return true;
|
|
}
|
|
}
|
|
false
|
|
} |