diff options
Diffstat (limited to 'src/protocol/handshake.rs')
-rw-r--r-- | src/protocol/handshake.rs | 298 |
1 files changed, 298 insertions, 0 deletions
diff --git a/src/protocol/handshake.rs b/src/protocol/handshake.rs new file mode 100644 index 0000000..f8023ce --- /dev/null +++ b/src/protocol/handshake.rs @@ -0,0 +1,298 @@ +// Copyright 2017 Sopium + +// This file is part of WireGuard.rs. + +// WireGuard.rs is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. + +// WireGuard.rs is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with WireGuard.rs. If not, see <https://www.gnu.org/licenses/>. + +extern crate blake2_rfc; +extern crate noise_protocol; +extern crate noise_sodiumoxide; +extern crate sodiumoxide; +extern crate tai64; + +use self::blake2_rfc::blake2s::Blake2s; +use self::noise_protocol::*; +use self::noise_protocol::patterns::noise_ik; +use self::noise_sodiumoxide::{ChaCha20Poly1305, X25519}; +use self::sodiumoxide::utils::memcmp; +use self::tai64::TAI64N; +use protocol::*; + +const PROLOGUE: &'static [u8] = b"WireGuard v0 zx2c4 Jason@zx2c4.com"; + +pub type HS = HandshakeState<X25519, ChaCha20Poly1305, NoiseBlake2s>; + +#[derive(Clone)] +pub struct NoiseBlake2s(Blake2s); + +impl Default for NoiseBlake2s { + fn default() -> Self { + NoiseBlake2s(Blake2s::new(32)) + } +} + +impl Hash for NoiseBlake2s { + type Output = [u8; 32]; + type Block = [u8; 64]; + + fn name() -> &'static str { + "BLAKE2s" + } + + fn input(&mut self, data: &[u8]) { + self.0.update(data); + } + + fn result(&mut self) -> Self::Output { + Self::Output::from_slice(self.0 + .clone() + .finalize() + .as_bytes()) + } +} + +/// Calc mac/hash of some data, with an optional key. +/// +/// This is exactly the mac function defined in WireGuard paper. +fn mac<K>(key: Option<K>, data: &[&[u8]]) -> [u8; 16] + where K: AsRef<[u8]> +{ + let mut mac = [0u8; 16]; + let mut blake2s = Blake2s::with_key(16, key.as_ref().map_or(&[], |k| k.as_ref())); + for d in data { + blake2s.update(d); + } + mac.copy_from_slice(blake2s.finalize().as_bytes()); + mac +} + +/// Generate handshake initiation message. +/// +/// Will generate a new ephemeral key and use current timestamp. +/// +/// Returns: Message, noise handshake state. +pub fn initiate(wg: &WgInfo, peer: &PeerInfo, self_index: Id) -> ([u8; 148], HS) { + let mut msg = [0u8; 148]; + + let mut hs = { + let mut hsbuilder = HandshakeStateBuilder::<X25519>::new(); + hsbuilder.set_pattern(noise_ik()); + hsbuilder.set_is_initiator(true); + hsbuilder.set_prologue(PROLOGUE); + if let Some(ref psk) = wg.psk { + hsbuilder.set_psk(psk.as_slice()); + } + hsbuilder.set_s(wg.key.clone()); + hsbuilder.set_rs(peer.peer_pubkey); + hsbuilder.build_handshake_state() + }; + + // Type and reserved zeros. + msg[0..4].copy_from_slice(&[1, 0, 0, 0]); + // Self index. + msg[4..8].copy_from_slice(self_index.as_slice()); + + // Noise part: e, s, timestamp. + let timestamp = TAI64N::now(); + hs.write_message(×tamp.to_external(), &mut msg[8..116]); + + // Mac1. + let mac1 = mac(wg.psk.as_ref(), + &[peer.peer_pubkey.as_slice(), &msg[..116]]); + msg[116..132].copy_from_slice(&mac1); + + (msg, hs) +} + +pub struct InitProcessResult { + pub peer_id: Id, + pub timestamp: TAI64N, + pub handshake_state: HS, +} + +/// Process a handshake initiation message. +/// +/// Will generate a new ephemeral key. +pub fn process_initiation(wg: &WgInfo, msg: &[u8]) -> Result<InitProcessResult, ()> { + if msg.len() != 148 { + return Err(()); + } + + // Check mac1. + let mac1 = mac(wg.psk.as_ref(), &[&wg.pubkey, &msg[..116]]); + if !memcmp(&mac1, &msg[116..132]) { + return Err(()); + } + + // Check type and zeros. + if &msg[0..4] != &[1, 0, 0, 0] { + return Err(()); + } + + // Peer index. + let peer_index = Id::from_slice(&msg[4..8]); + + let mut hs: HS = { + let mut hsbuilder = HandshakeStateBuilder::<X25519>::new(); + hsbuilder.set_is_initiator(false); + hsbuilder.set_prologue(PROLOGUE); + hsbuilder.set_pattern(noise_ik()); + if let Some(ref psk) = wg.psk { + hsbuilder.set_psk(psk); + } + hsbuilder.set_s(wg.key.clone()); + hsbuilder.build_handshake_state() + }; + + // Noise message, contains encrypted timestamp. + let mut timestamp = [0u8; 12]; + hs.read_message(&msg[8..116], &mut timestamp).map_err(|_| ())?; + let timestamp = TAI64N::from_external(×tamp).ok_or(())?; + + Ok(InitProcessResult { + peer_id: peer_index, + timestamp: timestamp, + handshake_state: hs, + }) +} + +/// Generate handshake response message. +pub fn responde(wg: &WgInfo, result: &mut InitProcessResult, self_id: Id) -> [u8; 92] { + let mut response = [0u8; 92]; + + // Type and zeros. + response[0..4].copy_from_slice(&[2, 0, 0, 0]); + response[4..8].copy_from_slice(self_id.as_slice()); + response[8..12].copy_from_slice(result.peer_id.as_slice()); + + let mut hs = &mut result.handshake_state; + + hs.write_message(&[], &mut response[12..60]); + + let mac1 = mac(wg.psk.as_ref(), &[hs.get_rs().as_ref().unwrap(), &response[..60]]); + response[60..76].copy_from_slice(&mac1); + + response +} + +/// Process handshake response message. +/// +/// Returns peer index. +pub fn process_response(wg: &WgInfo, hs: &mut HS, msg: &[u8]) -> Result<Id, ()> { + if msg.len() != 92 { + return Err(()); + } + + // Check mac1. + let mac1 = mac(wg.psk.as_ref(), &[&wg.pubkey, &msg[..60]]); + + if !memcmp(&mac1, &msg[60..76]) { + return Err(()); + } + + // Check type and zeros. + if &msg[0..4] != &[2, 0, 0, 0] { + return Err(()); + } + + // Peer index. + let peer_index = Id::from_slice(&msg[4..8]); + + // msg[8..12] is self index, skip. + + let mut out = []; + + hs.read_message(&msg[12..60], &mut out).map_err(|_| ())?; + + Ok(peer_index) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn wg_handshake_init_responde() { + let k = X25519::genkey(); + let init = WgInfo { + psk: None, + pubkey: X25519::pubkey(&k), + key: k, + }; + + let k = X25519::genkey(); + let resp = WgInfo { + psk: None, + pubkey: X25519::pubkey(&k), + key: k, + }; + + let init_peer = PeerInfo { + peer_pubkey: Clone::clone(&resp.pubkey), + endpoint: None, + allowed_ips: vec![], + keep_alive_interval: None, + }; + + let si = Id::gen(); + let (m0, mut ihs) = initiate(&init, &init_peer, si); + let mut result0 = process_initiation(&resp, &m0).unwrap(); + let ri = Id::gen(); + let m1 = responde(&resp, &mut result0, ri); + let ri1 = process_response(&init, &mut ihs, &m1).unwrap(); + + assert_eq!(result0.peer_id, si); + assert_eq!(ri1, ri); + + assert_eq!(ihs.get_hash(), result0.handshake_state.get_hash()); + } + + #[test] + fn wg_handshake_init_responde_with_psk() { + let psk = [0xc7; 32]; + + let k = X25519::genkey(); + let init = WgInfo { + psk: Some(psk), + pubkey: X25519::pubkey(&k), + key: k, + }; + + let k = X25519::genkey(); + let resp = WgInfo { + psk: Some(psk), + pubkey: X25519::pubkey(&k), + key: k, + }; + + let init_peer = PeerInfo { + peer_pubkey: Clone::clone(&resp.pubkey), + endpoint: None, + allowed_ips: vec![], + keep_alive_interval: None, + }; + + let si = Id::gen(); + let (m0, mut ihs) = initiate(&init, &init_peer, si); + let mut result0 = process_initiation(&resp, &m0).unwrap(); + let ri = Id::gen(); + let m1 = responde(&resp, &mut result0, ri); + let ri1 = process_response(&init, &mut ihs, &m1).unwrap(); + + assert_eq!(result0.peer_id, si); + assert_eq!(ri1, ri); + + assert_eq!(ihs.get_hash(), result0.handshake_state.get_hash()); + } +} |