use std::io;
use std::net::SocketAddr;
use std::sync::atomic::AtomicU64;
use std::sync::Arc;
use std::time::Instant;
use zeroize::Zeroize;
use crate::wg::constants::{
HandshakeState, NoisePrivateKey, NoisePublicKey, BLAKE2S_128_SIZE, BLAKE2S_256_SIZE,
CHACHAPOLY_KEY_SIZE, CHACHAPOLY_OVERHEAD, MAX_HANDSHAKES, MAX_SESSIONS,
MESSAGE_INITIATION_SIZE, MESSAGE_INITIATION_TYPE, MESSAGE_RESPONSE_SIZE, MESSAGE_RESPONSE_TYPE,
NOISE_PUBLIC_KEY_SIZE, TAI64N_TIMESTAMP_SIZE,
};
use crate::wg::crypto::{
aead_open_zero, aead_seal_zero, blake2s_mac_128, calculate_mac1_key, ct_eq, fill_random,
generate_private_key, initial_chain_key, initial_hash, is_zero, mix_hash, mix_key, mix_psk,
x25519_dh, x25519_public,
};
use crate::wg::handler::{Handler, Keypair, PacketResult, PacketType};
use crate::wg::replay::SlidingWindow;
use crate::wg::time::tai64n_now;
use crate::Result;
#[derive(Clone)]
pub(crate) struct Handshake {
pub state: HandshakeState,
pub hash: [u8; BLAKE2S_256_SIZE],
pub chain_key: [u8; BLAKE2S_256_SIZE],
pub local_ephemeral: NoisePrivateKey,
pub local_index: u32,
pub remote_index: u32,
pub remote_static: NoisePublicKey,
pub remote_ephemeral: NoisePublicKey,
pub precomputed_static_static: [u8; NOISE_PUBLIC_KEY_SIZE],
pub created: Instant,
}
impl Drop for Handshake {
fn drop(&mut self) {
self.hash.zeroize();
self.chain_key.zeroize();
self.precomputed_static_static.zeroize();
}
}
const INIT_OFF_TYPE: usize = 0;
const INIT_OFF_SENDER: usize = 4;
const INIT_OFF_EPHEMERAL: usize = 8;
const INIT_OFF_STATIC: usize = 40;
const INIT_OFF_STATIC_END: usize = INIT_OFF_STATIC + NOISE_PUBLIC_KEY_SIZE + CHACHAPOLY_OVERHEAD; const INIT_OFF_TIMESTAMP: usize = INIT_OFF_STATIC_END;
const INIT_OFF_TIMESTAMP_END: usize =
INIT_OFF_TIMESTAMP + TAI64N_TIMESTAMP_SIZE + CHACHAPOLY_OVERHEAD; const INIT_OFF_MAC1: usize = INIT_OFF_TIMESTAMP_END; const INIT_OFF_MAC2: usize = INIT_OFF_MAC1 + BLAKE2S_128_SIZE; #[allow(dead_code)]
const INIT_OFF_END: usize = INIT_OFF_MAC2 + BLAKE2S_128_SIZE;
const RESP_OFF_TYPE: usize = 0;
const RESP_OFF_SENDER: usize = 4;
const RESP_OFF_RECEIVER: usize = 8;
const RESP_OFF_EPHEMERAL: usize = 12;
const RESP_OFF_EMPTY: usize = 44;
const RESP_OFF_EMPTY_END: usize = RESP_OFF_EMPTY + CHACHAPOLY_OVERHEAD; const RESP_OFF_MAC1: usize = RESP_OFF_EMPTY_END;
const RESP_OFF_MAC2: usize = RESP_OFF_MAC1 + BLAKE2S_128_SIZE;
#[allow(dead_code)]
const RESP_OFF_END: usize = RESP_OFF_MAC2 + BLAKE2S_128_SIZE;
pub(crate) fn initiate_handshake(h: &Handler, peer_key: &NoisePublicKey) -> Result<Vec<u8>> {
if !h.is_authorized_peer(peer_key) {
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
"peer not authorized",
));
}
let client_priv = h.private_key().clone();
let client_pub = h.public_key();
let mut hs = Handshake {
state: HandshakeState::Zeroed,
hash: initial_hash(),
chain_key: initial_chain_key(),
local_ephemeral: NoisePrivateKey::zero(),
local_index: 0,
remote_index: 0,
remote_static: *peer_key,
remote_ephemeral: NoisePublicKey::zero(),
precomputed_static_static: [0u8; NOISE_PUBLIC_KEY_SIZE],
created: Instant::now(),
};
let temp_ss = x25519_dh(&client_priv, peer_key);
hs.precomputed_static_static.copy_from_slice(&temp_ss);
let mut temp_ss = temp_ss; temp_ss.zeroize();
let h_save = hs.hash;
mix_hash(&mut hs.hash, &h_save, &peer_key.0);
hs.local_ephemeral = generate_private_key()?;
let eph_pub = x25519_public(&hs.local_ephemeral);
let h_save = hs.hash;
mix_hash(&mut hs.hash, &h_save, &eph_pub.0);
let ck_save = hs.chain_key;
mix_key(&mut hs.chain_key, &ck_save, &eph_pub.0);
let mut temp_ss = x25519_dh(&hs.local_ephemeral, peer_key);
let mut key = [0u8; CHACHAPOLY_KEY_SIZE];
let mut ck_next = [0u8; BLAKE2S_256_SIZE];
crate::wg::crypto::kdf2(&mut ck_next, key_blake(&mut key), &hs.chain_key, &temp_ss);
hs.chain_key = ck_next;
temp_ss.zeroize();
let enc_static = aead_seal_zero(&key, &client_pub.0, &hs.hash);
if enc_static.len() != NOISE_PUBLIC_KEY_SIZE + CHACHAPOLY_OVERHEAD {
return Err(io::Error::other("unexpected static field size"));
}
let h_save = hs.hash;
mix_hash(&mut hs.hash, &h_save, &enc_static);
let mut ck_next = [0u8; BLAKE2S_256_SIZE];
crate::wg::crypto::kdf2(
&mut ck_next,
key_blake(&mut key),
&hs.chain_key,
&hs.precomputed_static_static,
);
hs.chain_key = ck_next;
let timestamp = tai64n_now();
let enc_timestamp = aead_seal_zero(&key, ×tamp, &hs.hash);
if enc_timestamp.len() != TAI64N_TIMESTAMP_SIZE + CHACHAPOLY_OVERHEAD {
return Err(io::Error::other("unexpected timestamp field size"));
}
let h_save = hs.hash;
mix_hash(&mut hs.hash, &h_save, &enc_timestamp);
let mut sender_idx: u32 = 0;
while sender_idx == 0 {
let mut buf = [0u8; 4];
fill_random(&mut buf)?;
sender_idx = u32::from_le_bytes(buf);
}
hs.local_index = sender_idx;
hs.state = HandshakeState::InitiationCreated;
hs.created = Instant::now();
let mut pkt = vec![0u8; MESSAGE_INITIATION_SIZE];
pkt[INIT_OFF_TYPE..INIT_OFF_SENDER].copy_from_slice(&MESSAGE_INITIATION_TYPE.to_le_bytes());
pkt[INIT_OFF_SENDER..INIT_OFF_EPHEMERAL].copy_from_slice(&sender_idx.to_le_bytes());
pkt[INIT_OFF_EPHEMERAL..INIT_OFF_STATIC].copy_from_slice(&eph_pub.0);
pkt[INIT_OFF_STATIC..INIT_OFF_STATIC_END].copy_from_slice(&enc_static);
pkt[INIT_OFF_TIMESTAMP..INIT_OFF_TIMESTAMP_END].copy_from_slice(&enc_timestamp);
h.insert_handshake(sender_idx, hs)?;
h.cookie_add_macs(peer_key, &mut pkt);
key.zeroize();
Ok(pkt)
}
pub(crate) fn process_handshake_response(h: &Handler, data: &[u8]) -> Result<PacketResult> {
if data.len() < MESSAGE_RESPONSE_SIZE {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"response too short",
));
}
let msg_type = u32::from_le_bytes(data[RESP_OFF_TYPE..RESP_OFF_SENDER].try_into().unwrap());
if msg_type != MESSAGE_RESPONSE_TYPE {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"wrong message type for response",
));
}
let sender_idx =
u32::from_le_bytes(data[RESP_OFF_SENDER..RESP_OFF_RECEIVER].try_into().unwrap());
let receiver_idx = u32::from_le_bytes(
data[RESP_OFF_RECEIVER..RESP_OFF_EPHEMERAL]
.try_into()
.unwrap(),
);
let mut hs = h
.take_handshake(receiver_idx)
.ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "no pending handshake"))?;
if !check_mac1(h.public_key().as_bytes(), data) {
h.insert_handshake(receiver_idx, hs)?;
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"invalid MAC1 on response",
));
}
let mut server_eph_pub = NoisePublicKey::zero();
server_eph_pub
.0
.copy_from_slice(&data[RESP_OFF_EPHEMERAL..RESP_OFF_EMPTY]);
let h_save = hs.hash;
mix_hash(&mut hs.hash, &h_save, &server_eph_pub.0);
let ck_save = hs.chain_key;
mix_key(&mut hs.chain_key, &ck_save, &server_eph_pub.0);
let mut temp = x25519_dh(&hs.local_ephemeral, &server_eph_pub);
let ck_save = hs.chain_key;
mix_key(&mut hs.chain_key, &ck_save, &temp);
temp.zeroize();
let mut temp = x25519_dh(h.private_key(), &server_eph_pub);
let ck_save = hs.chain_key;
mix_key(&mut hs.chain_key, &ck_save, &temp);
temp.zeroize();
let psk = h.preshared_key(&hs.remote_static);
let mut k = [0u8; CHACHAPOLY_KEY_SIZE];
let mut chain = hs.chain_key;
let mut hash = hs.hash;
mix_psk(&mut chain, &mut hash, &mut k, &psk);
hs.chain_key = chain;
hs.hash = hash;
let empty_ct = &data[RESP_OFF_EMPTY..RESP_OFF_EMPTY_END];
let opened = aead_open_zero(&k, empty_ct, &hs.hash)
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "decrypt empty"))?;
if !opened.is_empty() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"non-empty empty field",
));
}
let h_save = hs.hash;
mix_hash(&mut hs.hash, &h_save, empty_ct);
hs.remote_index = sender_idx;
hs.remote_ephemeral = server_eph_pub;
let mut send_key = [0u8; CHACHAPOLY_KEY_SIZE];
let mut recv_key = [0u8; CHACHAPOLY_KEY_SIZE];
let mut t0 = [0u8; BLAKE2S_256_SIZE];
let mut t1 = [0u8; BLAKE2S_256_SIZE];
crate::wg::crypto::kdf2(&mut t0, &mut t1, &hs.chain_key, &[]);
send_key.copy_from_slice(&t0[..CHACHAPOLY_KEY_SIZE]);
recv_key.copy_from_slice(&t1[..CHACHAPOLY_KEY_SIZE]);
t0.zeroize();
t1.zeroize();
k.zeroize();
let peer_key = hs.remote_static;
let local_idx = hs.local_index;
let remote_idx = hs.remote_index;
let kp = Arc::new(Keypair {
send_key,
receive_key: recv_key,
send_counter: AtomicU64::new(0),
created: Instant::now(),
local_index: local_idx,
remote_index: remote_idx,
peer_key,
is_initiator: true,
replay_filter: SlidingWindow::new(),
});
h.install_keypair(local_idx, kp.clone());
h.upsert_session(peer_key, kp);
h.touch_peer_handshake(&peer_key);
let keepalive = match crate::wg::transport::encrypt_data_packet(h, &[], &peer_key) {
Ok(buf) => buf,
Err(crate::wg::transport::EncryptError::RekeyRequired(buf)) => buf,
Err(e) => return Err(io::Error::other(e.to_string())),
};
Ok(PacketResult {
ty: PacketType::HandshakeResponse,
response: keepalive,
data: Vec::new(),
peer_key,
})
}
pub(crate) fn process_handshake_initiation(
h: &Handler,
data: &[u8],
remote_addr: &SocketAddr,
) -> Result<PacketResult> {
if data.len() < MESSAGE_INITIATION_SIZE {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"initiation too short",
));
}
h.inc_active_handshakes();
let _guard = DecGuard(h);
if !h.cookie_check_mac1(data) {
return Err(io::Error::new(io::ErrorKind::InvalidData, "invalid MAC1"));
}
if h.is_under_load() {
let sender = u32::from_le_bytes(
data[INIT_OFF_SENDER..INIT_OFF_EPHEMERAL]
.try_into()
.unwrap(),
);
let src = ip_bytes(remote_addr);
let mac2_ok =
!is_zero(&data[INIT_OFF_MAC2..INIT_OFF_END]) && h.cookie_check_mac2(data, &src);
if !mac2_ok {
let reply =
h.cookie_generate_reply(&src, sender, &data[INIT_OFF_MAC1..INIT_OFF_MAC2])?;
return Ok(PacketResult {
ty: PacketType::CookieReply,
response: reply,
data: Vec::new(),
peer_key: NoisePublicKey::zero(),
});
}
}
let msg_type = u32::from_le_bytes(data[INIT_OFF_TYPE..INIT_OFF_SENDER].try_into().unwrap());
if msg_type != MESSAGE_INITIATION_TYPE {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"wrong message type",
));
}
let sender_idx = u32::from_le_bytes(
data[INIT_OFF_SENDER..INIT_OFF_EPHEMERAL]
.try_into()
.unwrap(),
);
let server_priv = h.private_key().clone();
let server_pub = h.public_key();
let mut hs = Handshake {
state: HandshakeState::Zeroed,
hash: initial_hash(),
chain_key: initial_chain_key(),
local_ephemeral: NoisePrivateKey::zero(),
local_index: 0,
remote_index: sender_idx,
remote_static: NoisePublicKey::zero(),
remote_ephemeral: NoisePublicKey::zero(),
precomputed_static_static: [0u8; NOISE_PUBLIC_KEY_SIZE],
created: Instant::now(),
};
let h_save = hs.hash;
mix_hash(&mut hs.hash, &h_save, &server_pub.0);
let mut remote_eph = NoisePublicKey::zero();
remote_eph
.0
.copy_from_slice(&data[INIT_OFF_EPHEMERAL..INIT_OFF_STATIC]);
hs.remote_ephemeral = remote_eph;
let h_save = hs.hash;
mix_hash(&mut hs.hash, &h_save, &remote_eph.0);
let ck_save = hs.chain_key;
mix_key(&mut hs.chain_key, &ck_save, &remote_eph.0);
let mut k = [0u8; CHACHAPOLY_KEY_SIZE];
let mut temp_ss = x25519_dh(&server_priv, &remote_eph);
let mut t0 = [0u8; BLAKE2S_256_SIZE];
let mut t1 = [0u8; BLAKE2S_256_SIZE];
crate::wg::crypto::kdf2(&mut t0, &mut t1, &hs.chain_key, &temp_ss);
hs.chain_key = t0;
k.copy_from_slice(&t1[..CHACHAPOLY_KEY_SIZE]);
temp_ss.zeroize();
t1.zeroize();
let enc_static = &data[INIT_OFF_STATIC..INIT_OFF_STATIC_END];
let client_static = aead_open_zero(&k, enc_static, &hs.hash)
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "decrypt static"))?;
if client_static.len() != NOISE_PUBLIC_KEY_SIZE {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"client static wrong length",
));
}
hs.remote_static.0.copy_from_slice(&client_static);
let h_save = hs.hash;
mix_hash(&mut hs.hash, &h_save, enc_static);
let mut temp_ss = x25519_dh(&server_priv, &hs.remote_static);
hs.precomputed_static_static.copy_from_slice(&temp_ss);
let mut t0 = [0u8; BLAKE2S_256_SIZE];
let mut t1 = [0u8; BLAKE2S_256_SIZE];
crate::wg::crypto::kdf2(&mut t0, &mut t1, &hs.chain_key, &temp_ss);
hs.chain_key = t0;
k.copy_from_slice(&t1[..CHACHAPOLY_KEY_SIZE]);
temp_ss.zeroize();
t1.zeroize();
let enc_timestamp = &data[INIT_OFF_TIMESTAMP..INIT_OFF_TIMESTAMP_END];
let timestamp = aead_open_zero(&k, enc_timestamp, &hs.hash)
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "decrypt timestamp"))?;
let h_save = hs.hash;
mix_hash(&mut hs.hash, &h_save, enc_timestamp);
if !h.is_authorized_peer(&hs.remote_static) {
h.notify_unknown_peer(&hs.remote_static, remote_addr, data);
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
"unauthorized peer",
));
}
if !h.accept_peer_timestamp(&hs.remote_static, ×tamp) {
return Err(io::Error::other("replayed handshake timestamp"));
}
let mut sender_idx_local: u32 = 0;
while sender_idx_local == 0 {
let mut buf = [0u8; 4];
fill_random(&mut buf)?;
sender_idx_local = u32::from_le_bytes(buf);
}
hs.local_ephemeral = generate_private_key()?;
let eph_pub = x25519_public(&hs.local_ephemeral);
let h_save = hs.hash;
mix_hash(&mut hs.hash, &h_save, &eph_pub.0);
let ck_save = hs.chain_key;
mix_key(&mut hs.chain_key, &ck_save, &eph_pub.0);
let mut temp = x25519_dh(&hs.local_ephemeral, &hs.remote_ephemeral);
let ck_save = hs.chain_key;
mix_key(&mut hs.chain_key, &ck_save, &temp);
temp.zeroize();
let mut temp = x25519_dh(&hs.local_ephemeral, &hs.remote_static);
let ck_save = hs.chain_key;
mix_key(&mut hs.chain_key, &ck_save, &temp);
temp.zeroize();
let psk = h.preshared_key(&hs.remote_static);
let mut chain = hs.chain_key;
let mut hash = hs.hash;
mix_psk(&mut chain, &mut hash, &mut k, &psk);
hs.chain_key = chain;
hs.hash = hash;
let empty_ct = aead_seal_zero(&k, &[], &hs.hash);
if empty_ct.len() != CHACHAPOLY_OVERHEAD {
return Err(io::Error::other("empty seal wrong size"));
}
let h_save = hs.hash;
mix_hash(&mut hs.hash, &h_save, &empty_ct);
let mut pkt = vec![0u8; MESSAGE_RESPONSE_SIZE];
pkt[RESP_OFF_TYPE..RESP_OFF_SENDER].copy_from_slice(&MESSAGE_RESPONSE_TYPE.to_le_bytes());
pkt[RESP_OFF_SENDER..RESP_OFF_RECEIVER].copy_from_slice(&sender_idx_local.to_le_bytes());
pkt[RESP_OFF_RECEIVER..RESP_OFF_EPHEMERAL].copy_from_slice(&sender_idx.to_le_bytes());
pkt[RESP_OFF_EPHEMERAL..RESP_OFF_EMPTY].copy_from_slice(&eph_pub.0);
pkt[RESP_OFF_EMPTY..RESP_OFF_EMPTY_END].copy_from_slice(&empty_ct);
let mac1_key = calculate_mac1_key(&hs.remote_static);
let mac1 = blake2s_mac_128(&mac1_key, &pkt[..RESP_OFF_MAC1]);
pkt[RESP_OFF_MAC1..RESP_OFF_MAC2].copy_from_slice(&mac1);
let mut t0 = [0u8; BLAKE2S_256_SIZE];
let mut t1 = [0u8; BLAKE2S_256_SIZE];
crate::wg::crypto::kdf2(&mut t0, &mut t1, &hs.chain_key, &[]);
let mut recv_key = [0u8; CHACHAPOLY_KEY_SIZE];
let mut send_key = [0u8; CHACHAPOLY_KEY_SIZE];
recv_key.copy_from_slice(&t0[..CHACHAPOLY_KEY_SIZE]);
send_key.copy_from_slice(&t1[..CHACHAPOLY_KEY_SIZE]);
t0.zeroize();
t1.zeroize();
k.zeroize();
hs.local_index = sender_idx_local;
hs.state = HandshakeState::ResponseCreated;
hs.created = Instant::now();
let peer_key = hs.remote_static;
let kp = Arc::new(Keypair {
send_key,
receive_key: recv_key,
send_counter: AtomicU64::new(0),
created: Instant::now(),
local_index: sender_idx_local,
remote_index: sender_idx,
peer_key,
is_initiator: false,
replay_filter: SlidingWindow::new(),
});
h.check_keypair_capacity(MAX_HANDSHAKES)?;
h.install_keypair(sender_idx_local, kp.clone());
h.check_session_capacity(MAX_SESSIONS, &peer_key)?;
h.upsert_session(peer_key, kp);
h.touch_peer_handshake(&peer_key);
Ok(PacketResult {
ty: PacketType::HandshakeResponse,
response: pkt,
data: Vec::new(),
peer_key,
})
}
pub(crate) fn process_cookie_reply(h: &Handler, data: &[u8]) -> Result<PacketResult> {
if data.len() < crate::wg::constants::MESSAGE_COOKIE_REPLY_SIZE {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"cookie reply too short",
));
}
let receiver = u32::from_le_bytes(data[4..8].try_into().unwrap());
let peer = h.handshake_remote_static(receiver).ok_or_else(|| {
io::Error::new(
io::ErrorKind::NotFound,
"no pending handshake for cookie reply",
)
})?;
let mut nonce = [0u8; 24];
nonce.copy_from_slice(&data[8..32]);
h.peer_consume_cookie(
&peer,
&nonce,
&data[32..crate::wg::constants::MESSAGE_COOKIE_REPLY_SIZE],
)?;
Ok(PacketResult {
ty: PacketType::CookieReceived,
response: Vec::new(),
data: Vec::new(),
peer_key: peer,
})
}
fn ip_bytes(addr: &SocketAddr) -> Vec<u8> {
match addr.ip() {
std::net::IpAddr::V4(a) => a.octets().to_vec(),
std::net::IpAddr::V6(a) => a.octets().to_vec(),
}
}
pub(crate) fn check_mac1(local_pub: &[u8; NOISE_PUBLIC_KEY_SIZE], msg: &[u8]) -> bool {
if msg.len() < 2 * BLAKE2S_128_SIZE {
return false;
}
let smac2 = msg.len() - BLAKE2S_128_SIZE;
let smac1 = smac2 - BLAKE2S_128_SIZE;
let key = calculate_mac1_key(&NoisePublicKey(*local_pub));
let computed = blake2s_mac_128(&key, &msg[..smac1]);
ct_eq(&computed, &msg[smac1..smac2])
}
struct DecGuard<'a>(&'a Handler);
impl<'a> Drop for DecGuard<'a> {
fn drop(&mut self) {
self.0.dec_active_handshakes();
}
}
fn key_blake(k: &mut [u8; CHACHAPOLY_KEY_SIZE]) -> &mut [u8; BLAKE2S_256_SIZE] {
let _: [(); 0] = [(); (BLAKE2S_256_SIZE == CHACHAPOLY_KEY_SIZE) as usize - 1];
unsafe { &mut *(k.as_mut_ptr() as *mut [u8; BLAKE2S_256_SIZE]) }
}