From 2c81abbe7973dfbe6113d66f9d92b6b4ad3b0afa Mon Sep 17 00:00:00 2001 From: Mathias Hall-Andersen Date: Sun, 28 Jul 2019 17:09:27 +0200 Subject: Restructured for wireguard-rs --- Cargo.lock | 32 ++-- Cargo.toml | 2 +- src/device.rs | 283 ------------------------------ src/lib.rs | 10 -- src/main.rs | 7 + src/messages.rs | 208 ---------------------- src/mod.rs | 2 + src/noise.rs | 456 ------------------------------------------------ src/noise/device.rs | 315 ++++++++++++++++++++++++++++++++++ src/noise/messages.rs | 208 ++++++++++++++++++++++ src/noise/mod.rs | 18 ++ src/noise/noise.rs | 458 +++++++++++++++++++++++++++++++++++++++++++++++++ src/noise/peer.rs | 136 +++++++++++++++ src/noise/timestamp.rs | 34 ++++ src/noise/types.rs | 81 +++++++++ src/peer.rs | 136 --------------- src/timestamp.rs | 34 ---- src/types.rs | 99 ----------- src/types/mod.rs | 23 +++ 19 files changed, 1299 insertions(+), 1243 deletions(-) delete mode 100644 src/device.rs delete mode 100644 src/lib.rs create mode 100644 src/main.rs delete mode 100644 src/messages.rs create mode 100644 src/mod.rs delete mode 100644 src/noise.rs create mode 100644 src/noise/device.rs create mode 100644 src/noise/messages.rs create mode 100644 src/noise/mod.rs create mode 100644 src/noise/noise.rs create mode 100644 src/noise/peer.rs create mode 100644 src/noise/timestamp.rs create mode 100644 src/noise/types.rs delete mode 100644 src/peer.rs delete mode 100644 src/timestamp.rs delete mode 100644 src/types.rs create mode 100644 src/types/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 1ff70c1..b7f93d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -342,22 +342,6 @@ name = "unicode-xid" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "wg-handshake" -version = "0.1.0" -dependencies = [ - "blake2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", - "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "hmac 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", - "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", - "spin 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "x25519-dalek 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "zerocopy 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "winapi" version = "0.3.7" @@ -377,6 +361,22 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "wireguard-rs" +version = "0.1.0" +dependencies = [ + "blake2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", + "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "hmac 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "spin 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "x25519-dalek 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "zerocopy 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "x25519-dalek" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index af24051..0b4ce0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "wg-handshake" +name = "wireguard-rs" version = "0.1.0" authors = ["Mathias Hall-Andersen "] edition = "2018" diff --git a/src/device.rs b/src/device.rs deleted file mode 100644 index a6081aa..0000000 --- a/src/device.rs +++ /dev/null @@ -1,283 +0,0 @@ -use spin::RwLock; -use std::collections::HashMap; - -use rand::prelude::*; -use rand::rngs::OsRng; - -use x25519_dalek::PublicKey; -use x25519_dalek::StaticSecret; - -use crate::messages; -use crate::noise; -use crate::peer::Peer; -use crate::types::*; - -pub struct Device { - pub sk: StaticSecret, // static secret key - pub pk: PublicKey, // static public key - pk_map: HashMap<[u8; 32], Peer>, // public key -> peer state - id_map: RwLock>, // receiver ids -> public key -} - -/* A mutable reference to the device needs to be held during configuration. - * Wrapping the device in a RwLock enables peer config after "configuration time" - */ -impl Device -where - T: Copy, -{ - /// Initialize a new handshake state machine - /// - /// # Arguments - /// - /// * `sk` - x25519 scalar representing the local private key - pub fn new(sk: StaticSecret) -> Device { - Device { - pk: PublicKey::from(&sk), - sk: sk, - pk_map: HashMap::new(), - id_map: RwLock::new(HashMap::new()), - } - } - - /// Add a new public key to the state machine - /// To remove public keys, you must create a new machine instance - /// - /// # Arguments - /// - /// * `pk` - The public key to add - /// * `identifier` - Associated identifier which can be used to distinguish the peers - pub fn add(&mut self, pk: PublicKey, identifier: T) -> Result<(), ConfigError> { - // check that the pk is not added twice - - if let Some(_) = self.pk_map.get(pk.as_bytes()) { - return Err(ConfigError::new("Duplicate public key")); - }; - - // check that the pk is not that of the device - - if *self.pk.as_bytes() == *pk.as_bytes() { - return Err(ConfigError::new( - "Public key corresponds to secret key of interface", - )); - } - - // map : pk -> new index - - self.pk_map.insert( - *pk.as_bytes(), - Peer::new(identifier, pk, self.sk.diffie_hellman(&pk)), - ); - - Ok(()) - } - - /// Remove a peer by public key - /// To remove public keys, you must create a new machine instance - /// - /// # Arguments - /// - /// * `pk` - The public key of the peer to remove - /// - /// # Returns - /// - /// The call might fail if the public key is not found - pub fn remove(&mut self, pk: PublicKey) -> Result<(), ConfigError> { - // take write-lock on receive id table - let mut id_map = self.id_map.write(); - - // remove the peer - self.pk_map - .remove(pk.as_bytes()) - .ok_or(ConfigError::new("Public key not in device"))?; - - // pruge the id map (linear scan) - id_map.retain(|_, v| v != pk.as_bytes()); - Ok(()) - } - - /// Add a psk to the peer - /// - /// # Arguments - /// - /// * `pk` - The public key of the peer - /// * `psk` - The psk to set / unset - /// - /// # Returns - /// - /// The call might fail if the public key is not found - pub fn psk(&mut self, pk: PublicKey, psk: Option) -> Result<(), ConfigError> { - match self.pk_map.get_mut(pk.as_bytes()) { - Some(mut peer) => { - peer.psk = match psk { - Some(v) => v, - None => [0u8; 32], - }; - Ok(()) - } - _ => Err(ConfigError::new("No such public key")), - } - } - - /// Release an id back to the pool - /// - /// # Arguments - /// - /// * `id` - The (sender) id to release - pub fn release(&self, id: u32) { - let mut m = self.id_map.write(); - debug_assert!(m.contains_key(&id), "Releasing id not allocated"); - m.remove(&id); - } - - /// Begin a new handshake - /// - /// # Arguments - /// - /// * `pk` - Public key of peer to initiate handshake for - pub fn begin(&self, pk: &PublicKey) -> Result, HandshakeError> { - match self.pk_map.get(pk.as_bytes()) { - None => Err(HandshakeError::UnknownPublicKey), - Some(peer) => { - let sender = self.allocate(peer); - noise::create_initiation(self, peer, sender) - } - } - } - - /// Process a handshake message. - /// - /// # Arguments - /// - /// * `msg` - Byte slice containing the message (untrusted input) - pub fn process(&self, msg: &[u8]) -> Result, HandshakeError> { - match msg.get(0) { - Some(&messages::TYPE_INITIATION) => { - // consume the initiation - let (peer, st) = noise::consume_initiation(self, msg)?; - - // allocate new index for response - let sender = self.allocate(peer); - - // create response (release id on error) - noise::create_response(peer, sender, st).map_err(|e| { - self.release(sender); - e - }) - } - Some(&messages::TYPE_RESPONSE) => noise::consume_response(self, msg), - _ => Err(HandshakeError::InvalidMessageFormat), - } - } - - // Internal function - // - // Return the peer associated with the public key - pub(crate) fn lookup_pk(&self, pk: &PublicKey) -> Result<&Peer, HandshakeError> { - self.pk_map - .get(pk.as_bytes()) - .ok_or(HandshakeError::UnknownPublicKey) - } - - // Internal function - // - // Return the peer currently associated with the receiver identifier - pub(crate) fn lookup_id(&self, id: u32) -> Result<&Peer, HandshakeError> { - let im = self.id_map.read(); - let pk = im.get(&id).ok_or(HandshakeError::UnknownReceiverId)?; - match self.pk_map.get(pk) { - Some(peer) => Ok(peer), - _ => unreachable!(), // if the id-lookup succeeded, the peer should exist - } - } - - // Internal function - // - // Allocated a new receiver identifier for the peer - fn allocate(&self, peer: &Peer) -> u32 { - let mut rng = OsRng::new().unwrap(); - - loop { - let id = rng.gen(); - - // check membership with read lock - if self.id_map.read().contains_key(&id) { - continue; - } - - // take write lock and add index - let mut m = self.id_map.write(); - if !m.contains_key(&id) { - m.insert(id, *peer.pk.as_bytes()); - return id; - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use hex; - use messages::*; - - #[test] - fn handshake() { - // generate new keypairs - - let mut rng = OsRng::new().unwrap(); - - let sk1 = StaticSecret::new(&mut rng); - let pk1 = PublicKey::from(&sk1); - - let sk2 = StaticSecret::new(&mut rng); - let pk2 = PublicKey::from(&sk2); - - // intialize devices on both ends - - let mut dev1 = Device::new(sk1); - let mut dev2 = Device::new(sk2); - - dev1.add(pk2, 1337).unwrap(); - dev2.add(pk1, 2600).unwrap(); - - // do a few handshakes - - for i in 0..10 { - println!("handshake : {}", i); - - // create initiation - - let msg1 = dev1.begin(&pk2).unwrap(); - - println!("msg1 = {}", hex::encode(&msg1[..])); - println!("msg1 = {:?}", Initiation::parse(&msg1[..]).unwrap()); - - // process initiation and create response - - let (_, msg2, ks_r) = dev2.process(&msg1).unwrap(); - - let ks_r = ks_r.unwrap(); - let msg2 = msg2.unwrap(); - - println!("msg2 = {}", hex::encode(&msg2[..])); - println!("msg2 = {:?}", Response::parse(&msg2[..]).unwrap()); - - assert!(!ks_r.confirmed, "Responders key-pair is confirmed"); - - // process response and obtain confirmed key-pair - - let (_, msg3, ks_i) = dev1.process(&msg2).unwrap(); - let ks_i = ks_i.unwrap(); - - assert!(msg3.is_none(), "Returned message after response"); - assert!(ks_i.confirmed, "Initiators key-pair is not confirmed"); - - assert_eq!(ks_i.send, ks_r.recv, "KeyI.send != KeyR.recv"); - assert_eq!(ks_i.recv, ks_r.send, "KeyI.recv != KeyR.send"); - - dev1.release(ks_i.send.id); - dev2.release(ks_r.send.id); - } - } -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index d5a9c31..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,10 +0,0 @@ -mod device; -mod messages; -mod noise; -mod peer; -mod timestamp; -mod types; - -// publicly exposed interface - -pub use device::Device; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..b2995e7 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,7 @@ +mod noise; +mod types; + +use noise::Device; +use types::KeyPair; + +fn main() {} diff --git a/src/messages.rs b/src/messages.rs deleted file mode 100644 index 78f0838..0000000 --- a/src/messages.rs +++ /dev/null @@ -1,208 +0,0 @@ -#[cfg(test)] -use hex; - -#[cfg(test)] -use std::fmt; - -use byteorder::LittleEndian; -use zerocopy::byteorder::U32; -use zerocopy::{AsBytes, ByteSlice, FromBytes, LayoutVerified}; - -use crate::types::*; - -const SIZE_TAG: usize = 16; -const SIZE_X25519_POINT: usize = 32; -const SIZE_TIMESTAMP: usize = 12; - -pub const TYPE_INITIATION: u8 = 1; -pub const TYPE_RESPONSE: u8 = 2; - -#[repr(C)] -#[derive(Copy, Clone, FromBytes, AsBytes)] -pub struct Initiation { - f_type: U32, - pub f_sender: U32, - pub f_ephemeral: [u8; SIZE_X25519_POINT], - pub f_static: [u8; SIZE_X25519_POINT], - pub f_static_tag: [u8; SIZE_TAG], - pub f_timestamp: [u8; SIZE_TIMESTAMP], - pub f_timestamp_tag: [u8; SIZE_TAG], -} - -impl Default for Initiation { - fn default() -> Self { - Self { - f_type: >::new(TYPE_INITIATION as u32), - - f_sender: >::ZERO, - f_ephemeral: [0u8; SIZE_X25519_POINT], - f_static: [0u8; SIZE_X25519_POINT], - f_static_tag: [0u8; SIZE_TAG], - f_timestamp: [0u8; SIZE_TIMESTAMP], - f_timestamp_tag: [0u8; SIZE_TAG], - } - } -} - -impl Initiation { - pub fn parse(bytes: B) -> Result, HandshakeError> { - let msg: LayoutVerified = - LayoutVerified::new(bytes).ok_or(HandshakeError::InvalidMessageFormat)?; - - if msg.f_type.get() != (TYPE_INITIATION as u32) { - return Err(HandshakeError::InvalidMessageFormat); - } - - Ok(msg) - } -} - -#[cfg(test)] -impl fmt::Debug for Initiation { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, - "MessageInitiation {{ type = {}, sender = {}, ephemeral = {}, static = {}|{}, timestamp = {}|{} }}", - self.f_type.get(), - self.f_sender.get(), - hex::encode(self.f_ephemeral), - hex::encode(self.f_static), - hex::encode(self.f_static_tag), - hex::encode(self.f_timestamp), - hex::encode(self.f_timestamp_tag) - ) - } -} - -#[cfg(test)] -impl PartialEq for Initiation { - fn eq(&self, other: &Self) -> bool { - self.f_type.get() == other.f_type.get() - && self.f_sender.get() == other.f_sender.get() - && self.f_ephemeral[..] == other.f_ephemeral[..] - && self.f_static[..] == other.f_static[..] - && self.f_static_tag[..] == other.f_static_tag[..] - && self.f_timestamp[..] == other.f_timestamp - && self.f_timestamp_tag[..] == other.f_timestamp_tag - } -} - -#[cfg(test)] -impl Eq for Initiation {} - -#[repr(C)] -#[derive(Copy, Clone, FromBytes, AsBytes)] -pub struct Response { - f_type: U32, - pub f_sender: U32, - pub f_receiver: U32, - pub f_ephemeral: [u8; SIZE_X25519_POINT], - pub f_empty_tag: [u8; SIZE_TAG], -} - -impl Response { - pub fn parse(bytes: B) -> Result, HandshakeError> { - let msg: LayoutVerified = - LayoutVerified::new(bytes).ok_or(HandshakeError::InvalidMessageFormat)?; - - if msg.f_type.get() != (TYPE_RESPONSE as u32) { - return Err(HandshakeError::InvalidMessageFormat); - } - - Ok(msg) - } -} - -impl Default for Response { - fn default() -> Self { - Self { - f_type: >::new(TYPE_RESPONSE as u32), - f_sender: >::ZERO, - f_receiver: >::ZERO, - f_ephemeral: [0u8; SIZE_X25519_POINT], - f_empty_tag: [0u8; SIZE_TAG], - } - } -} - -#[cfg(test)] -impl fmt::Debug for Response { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, - "MessageResponse {{ type = {}, sender = {}, receiver = {}, ephemeral = {}, empty = |{} }}", - self.f_type, - self.f_sender, - self.f_receiver, - hex::encode(self.f_ephemeral), - hex::encode(self.f_empty_tag) - ) - } -} - -#[cfg(test)] -impl PartialEq for Response { - fn eq(&self, other: &Self) -> bool { - self.f_type == other.f_type - && self.f_sender == other.f_sender - && self.f_receiver == other.f_receiver - && self.f_ephemeral == other.f_ephemeral - && self.f_empty_tag == other.f_empty_tag - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn message_response_identity() { - let mut msg: Response = Default::default(); - - msg.f_sender.set(146252); - msg.f_receiver.set(554442); - msg.f_ephemeral = [ - 0xc1, 0x66, 0x0a, 0x0c, 0xdc, 0x0f, 0x6c, 0x51, 0x0f, 0xc2, 0xcc, 0x51, 0x52, 0x0c, - 0xde, 0x1e, 0xf7, 0xf1, 0xca, 0x90, 0x86, 0x72, 0xad, 0x67, 0xea, 0x89, 0x45, 0x44, - 0x13, 0x56, 0x52, 0x1f, - ]; - msg.f_empty_tag = [ - 0x60, 0x0e, 0x1e, 0x95, 0x41, 0x6b, 0x52, 0x05, 0xa2, 0x09, 0xe1, 0xbf, 0x40, 0x05, - 0x2f, 0xde, - ]; - - let buf: Vec = msg.as_bytes().to_vec(); - let msg_p = Response::parse(&buf[..]).unwrap(); - assert_eq!(msg, *msg_p.into_ref()); - } - - #[test] - fn message_initiate_identity() { - let mut msg: Initiation = Default::default(); - - msg.f_sender.set(575757); - msg.f_ephemeral = [ - 0xc1, 0x66, 0x0a, 0x0c, 0xdc, 0x0f, 0x6c, 0x51, 0x0f, 0xc2, 0xcc, 0x51, 0x52, 0x0c, - 0xde, 0x1e, 0xf7, 0xf1, 0xca, 0x90, 0x86, 0x72, 0xad, 0x67, 0xea, 0x89, 0x45, 0x44, - 0x13, 0x56, 0x52, 0x1f, - ]; - msg.f_static = [ - 0xdc, 0x33, 0x90, 0x15, 0x8f, 0x82, 0x3e, 0x06, 0x44, 0xa0, 0xde, 0x4c, 0x15, 0x6c, - 0x5d, 0xa4, 0x65, 0x99, 0xf6, 0x6c, 0xa1, 0x14, 0x77, 0xf9, 0xeb, 0x6a, 0xec, 0xc3, - 0x3c, 0xda, 0x47, 0xe1, - ]; - msg.f_static_tag = [ - 0x45, 0xac, 0x8d, 0x43, 0xea, 0x1b, 0x2f, 0x02, 0x45, 0x5d, 0x86, 0x37, 0xee, 0x83, - 0x6b, 0x42, - ]; - msg.f_timestamp = [ - 0x4f, 0x1c, 0x60, 0xec, 0x0e, 0xf6, 0x36, 0xf0, 0x78, 0x28, 0x57, 0x42, - ]; - msg.f_timestamp_tag = [ - 0x60, 0x0e, 0x1e, 0x95, 0x41, 0x6b, 0x52, 0x05, 0xa2, 0x09, 0xe1, 0xbf, 0x40, 0x05, - 0x2f, 0xde, - ]; - - let buf: Vec = msg.as_bytes().to_vec(); - let msg_p = Initiation::parse(&buf[..]).unwrap(); - assert_eq!(msg, *msg_p.into_ref()); - } -} diff --git a/src/mod.rs b/src/mod.rs new file mode 100644 index 0000000..dd3d99f --- /dev/null +++ b/src/mod.rs @@ -0,0 +1,2 @@ +mod noise; +mod types; diff --git a/src/noise.rs b/src/noise.rs deleted file mode 100644 index 08935e0..0000000 --- a/src/noise.rs +++ /dev/null @@ -1,456 +0,0 @@ -// DH -use x25519_dalek::PublicKey; -use x25519_dalek::StaticSecret; - -// HASH & MAC -use blake2::Blake2s; -use hmac::Hmac; - -// AEAD -use crypto::aead::{AeadDecryptor, AeadEncryptor}; -use crypto::chacha20poly1305::ChaCha20Poly1305; - -use rand::rngs::OsRng; - -use generic_array::typenum::*; -use generic_array::GenericArray; - -use zerocopy::AsBytes; - -use crate::device::Device; -use crate::messages::{Initiation, Response}; -use crate::peer::{Peer, State}; -use crate::timestamp; -use crate::types::*; - -// HMAC hasher (generic construction) - -type HMACBlake2s = Hmac; - -// convenient alias to pass state temporarily into device.rs and back - -type TemporaryState = (u32, PublicKey, GenericArray, GenericArray); - -const SIZE_CK: usize = 32; -const SIZE_HS: usize = 32; -const SIZE_NONCE: usize = 8; - -// C := Hash(Construction) -const INITIAL_CK: [u8; SIZE_CK] = [ - 0x60, 0xe2, 0x6d, 0xae, 0xf3, 0x27, 0xef, 0xc0, 0x2e, 0xc3, 0x35, 0xe2, 0xa0, 0x25, 0xd2, 0xd0, - 0x16, 0xeb, 0x42, 0x06, 0xf8, 0x72, 0x77, 0xf5, 0x2d, 0x38, 0xd1, 0x98, 0x8b, 0x78, 0xcd, 0x36, -]; - -// H := Hash(C || Identifier) -const INITIAL_HS: [u8; SIZE_HS] = [ - 0x22, 0x11, 0xb3, 0x61, 0x08, 0x1a, 0xc5, 0x66, 0x69, 0x12, 0x43, 0xdb, 0x45, 0x8a, 0xd5, 0x32, - 0x2d, 0x9c, 0x6c, 0x66, 0x22, 0x93, 0xe8, 0xb7, 0x0e, 0xe1, 0x9c, 0x65, 0xba, 0x07, 0x9e, 0xf3, -]; - -const ZERO_NONCE: [u8; SIZE_NONCE] = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; - -macro_rules! HASH { - ( $($input:expr),* ) => {{ - use blake2::Digest; - let mut hsh = Blake2s::new(); - $( - hsh.input($input); - )* - hsh.result() - }}; -} - -macro_rules! HMAC { - ($key:expr, $($input:expr),*) => {{ - use hmac::Mac; - let mut mac = HMACBlake2s::new_varkey($key).unwrap(); - $( - mac.input($input); - )* - mac.result().code() - }}; -} - -macro_rules! KDF1 { - ($ck:expr, $input:expr) => {{ - let t0 = HMAC!($ck, $input); - let t1 = HMAC!(&t0, &[0x1]); - t1 - }}; -} - -macro_rules! KDF2 { - ($ck:expr, $input:expr) => {{ - let t0 = HMAC!($ck, $input); - let t1 = HMAC!(&t0, &[0x1]); - let t2 = HMAC!(&t0, &t1, &[0x2]); - (t1, t2) - }}; -} - -macro_rules! KDF3 { - ($ck:expr, $input:expr) => {{ - let t0 = HMAC!($ck, $input); - let t1 = HMAC!(&t0, &[0x1]); - let t2 = HMAC!(&t0, &t1, &[0x2]); - let t3 = HMAC!(&t0, &t2, &[0x3]); - (t1, t2, t3) - }}; -} - -macro_rules! SEAL { - ($key:expr, $aead:expr, $pt:expr, $ct:expr, $tag:expr) => {{ - let mut aead = ChaCha20Poly1305::new($key, &ZERO_NONCE, $aead); - aead.encrypt($pt, $ct, $tag); - }}; -} - -macro_rules! OPEN { - ($key:expr, $aead:expr, $pt:expr, $ct:expr, $tag:expr) => {{ - let mut aead = ChaCha20Poly1305::new($key, &ZERO_NONCE, $aead); - if !aead.decrypt($ct, $pt, $tag) { - Err(HandshakeError::DecryptionFailure) - } else { - Ok(()) - } - }}; -} - -#[cfg(test)] -mod tests { - use super::*; - - const IDENTIFIER: &[u8] = b"WireGuard v1 zx2c4 Jason@zx2c4.com"; - const CONSTRUCTION: &[u8] = b"Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s"; - - #[test] - fn precomputed_chain_key() { - assert_eq!(INITIAL_CK[..], HASH!(CONSTRUCTION)[..]); - } - - #[test] - fn precomputed_hash() { - assert_eq!(INITIAL_HS[..], HASH!(INITIAL_CK, IDENTIFIER)[..]); - } -} - -pub fn create_initiation( - device: &Device, - peer: &Peer, - sender: u32, -) -> Result, HandshakeError> { - let mut rng = OsRng::new().unwrap(); - let mut msg: Initiation = Default::default(); - - // initialize state - - let ck = INITIAL_CK; - let hs = INITIAL_HS; - let hs = HASH!(&hs, peer.pk.as_bytes()); - - msg.f_sender.set(sender); - - // (E_priv, E_pub) := DH-Generate() - - let eph_sk = StaticSecret::new(&mut rng); - let eph_pk = PublicKey::from(&eph_sk); - - // C := Kdf(C, E_pub) - - let ck = KDF1!(&ck, eph_pk.as_bytes()); - - // msg.ephemeral := E_pub - - msg.f_ephemeral = *eph_pk.as_bytes(); - - // H := HASH(H, msg.ephemeral) - - let hs = HASH!(&hs, msg.f_ephemeral); - - // (C, k) := Kdf2(C, DH(E_priv, S_pub)) - - let (ck, key) = KDF2!(&ck, eph_sk.diffie_hellman(&peer.pk).as_bytes()); - - // msg.static := Aead(k, 0, S_pub, H) - - SEAL!( - &key, - &hs, // ad - device.pk.as_bytes(), // pt - &mut msg.f_static, // ct - &mut msg.f_static_tag // tag - ); - - // H := Hash(H || msg.static) - - let hs = HASH!(&hs, &msg.f_static, &msg.f_static_tag); - - // (C, k) := Kdf2(C, DH(S_priv, S_pub)) - - let (ck, key) = KDF2!(&ck, peer.ss.as_bytes()); - - // msg.timestamp := Aead(k, 0, Timestamp(), H) - - SEAL!( - &key, - &hs, // ad - ×tamp::now(), // pt - &mut msg.f_timestamp, // ct - &mut msg.f_timestamp_tag // tag - ); - - // H := Hash(H || msg.timestamp) - - let hs = HASH!(&hs, &msg.f_timestamp, &msg.f_timestamp_tag); - - // update state of peer - - peer.set_state(State::InitiationSent { - hs, - ck, - eph_sk, - sender, - }); - - // return message as vector - - Ok(msg.as_bytes().to_vec()) -} - -pub fn consume_initiation<'a, T: Copy>( - device: &'a Device, - msg: &[u8], -) -> Result<(&'a Peer, TemporaryState), HandshakeError> { - // parse message - - let msg = Initiation::parse(msg)?; - - // initialize state - - let ck = INITIAL_CK; - let hs = INITIAL_HS; - let hs = HASH!(&hs, device.pk.as_bytes()); - - // C := Kdf(C, E_pub) - - let ck = KDF1!(&ck, &msg.f_ephemeral); - - // H := HASH(H, msg.ephemeral) - - let hs = HASH!(&hs, &msg.f_ephemeral); - - // (C, k) := Kdf2(C, DH(E_priv, S_pub)) - - let eph_r_pk = PublicKey::from(msg.f_ephemeral); - let (ck, key) = KDF2!(&ck, device.sk.diffie_hellman(&eph_r_pk).as_bytes()); - - // msg.static := Aead(k, 0, S_pub, H) - - let mut pk = [0u8; 32]; - - OPEN!( - &key, - &hs, // ad - &mut pk, // pt - &msg.f_static, // ct - &msg.f_static_tag // tag - )?; - - let peer = device.lookup_pk(&PublicKey::from(pk))?; - - // H := Hash(H || msg.static) - - let hs = HASH!(&hs, &msg.f_static, &msg.f_static_tag); - - // (C, k) := Kdf2(C, DH(S_priv, S_pub)) - - let (ck, key) = KDF2!(&ck, peer.ss.as_bytes()); - - // msg.timestamp := Aead(k, 0, Timestamp(), H) - - let mut ts = timestamp::zero(); - - OPEN!( - &key, - &hs, // ad - &mut ts, // pt - &msg.f_timestamp, // ct - &msg.f_timestamp_tag // tag - )?; - - // check and update timestamp - - peer.check_timestamp(device, &ts)?; - - // H := Hash(H || msg.timestamp) - - let hs = HASH!(&hs, &msg.f_timestamp, &msg.f_timestamp_tag); - - // return state (to create response) - - Ok((peer, (msg.f_sender.get(), eph_r_pk, hs, ck))) -} - -pub fn create_response( - peer: &Peer, - sender: u32, // sending identifier - state: TemporaryState, // state from "consume_initiation" -) -> Result, HandshakeError> { - let mut rng = OsRng::new().unwrap(); - let mut msg: Response = Default::default(); - - let (receiver, eph_r_pk, hs, ck) = state; - - msg.f_sender.set(sender); - msg.f_receiver.set(receiver); - - // (E_priv, E_pub) := DH-Generate() - - let eph_sk = StaticSecret::new(&mut rng); - let eph_pk = PublicKey::from(&eph_sk); - - // C := Kdf1(C, E_pub) - - let ck = KDF1!(&ck, eph_pk.as_bytes()); - - // msg.ephemeral := E_pub - - msg.f_ephemeral = *eph_pk.as_bytes(); - - // H := Hash(H || msg.ephemeral) - - let hs = HASH!(&hs, &msg.f_ephemeral); - - // C := Kdf1(C, DH(E_priv, E_pub)) - - let ck = KDF1!(&ck, eph_sk.diffie_hellman(&eph_r_pk).as_bytes()); - - // C := Kdf1(C, DH(E_priv, S_pub)) - - let ck = KDF1!(&ck, eph_sk.diffie_hellman(&peer.pk).as_bytes()); - - // (C, tau, k) := Kdf3(C, Q) - - let (ck, tau, key) = KDF3!(&ck, &peer.psk); - - // H := Hash(H || tau) - - let hs = HASH!(&hs, tau); - - // msg.empty := Aead(k, 0, [], H) - - SEAL!( - &key, - &hs, // ad - &[], // pt - &mut [], // ct - &mut msg.f_empty_tag // tag - ); - - /* not strictly needed - * // H := Hash(H || msg.empty) - * let hs = HASH!(&hs, &msg.f_empty_tag); - */ - - // derive key-pair - // (verbose code, due to GenericArray -> [u8; 32] conversion) - - let (key_recv, key_send) = KDF2!(&ck, &[]); - - // return response and unconfirmed key-pair - - Ok(( - peer.identifier, - Some(msg.as_bytes().to_vec()), - Some(KeyPair { - confirmed: false, - send: Key { - id: sender, - key: key_send.into(), - }, - recv: Key { - id: receiver, - key: key_recv.into(), - }, - }), - )) -} - -pub fn consume_response( - device: &Device, - msg: &[u8], -) -> Result, HandshakeError> { - // parse message - - let msg = Response::parse(msg)?; - - // retrieve peer and associated state - - let peer = device.lookup_id(msg.f_receiver.get())?; - let (hs, ck, sender, eph_sk) = match peer.get_state() { - State::Reset => Err(HandshakeError::InvalidState), - State::InitiationSent { - hs, - ck, - sender, - eph_sk, - } => Ok((hs, ck, sender, eph_sk)), - }?; - - // C := Kdf1(C, E_pub) - - let ck = KDF1!(&ck, &msg.f_ephemeral); - - // H := Hash(H || msg.ephemeral) - - let hs = HASH!(&hs, &msg.f_ephemeral); - - // C := Kdf1(C, DH(E_priv, E_pub)) - - let eph_r_pk = PublicKey::from(msg.f_ephemeral); - let ck = KDF1!(&ck, eph_sk.diffie_hellman(&eph_r_pk).as_bytes()); - - // C := Kdf1(C, DH(E_priv, S_pub)) - - let ck = KDF1!(&ck, device.sk.diffie_hellman(&eph_r_pk).as_bytes()); - - // (C, tau, k) := Kdf3(C, Q) - - let (ck, tau, key) = KDF3!(&ck, &peer.psk); - - // H := Hash(H || tau) - - let hs = HASH!(&hs, tau); - - // msg.empty := Aead(k, 0, [], H) - - OPEN!( - &key, - &hs, // ad - &mut [], // pt - &[], // ct - &msg.f_empty_tag // tag - )?; - - // derive key-pair - - let (key_send, key_recv) = KDF2!(&ck, &[]); - - // return confirmed key-pair - - Ok(( - peer.identifier, - None, - Some(KeyPair { - confirmed: true, - send: Key { - id: sender, - key: key_send.into(), - }, - recv: Key { - id: msg.f_sender.get(), - key: key_recv.into(), - }, - }), - )) -} diff --git a/src/noise/device.rs b/src/noise/device.rs new file mode 100644 index 0000000..04e00f9 --- /dev/null +++ b/src/noise/device.rs @@ -0,0 +1,315 @@ +use spin::RwLock; +use std::collections::HashMap; + +use rand::prelude::*; +use rand::rngs::OsRng; + +use x25519_dalek::PublicKey; +use x25519_dalek::StaticSecret; + +use super::messages; +use super::noise; +use super::peer::Peer; +use super::types::*; + +pub struct Device { + pub sk: StaticSecret, // static secret key + pub pk: PublicKey, // static public key + pk_map: HashMap<[u8; 32], Peer>, // public key -> peer state + id_map: RwLock>, // receiver ids -> public key +} + +/* A mutable reference to the device needs to be held during configuration. + * Wrapping the device in a RwLock enables peer config after "configuration time" + */ +impl Device +where + T: Copy, +{ + /// Initialize a new handshake state machine + /// + /// # Arguments + /// + /// * `sk` - x25519 scalar representing the local private key + pub fn new(sk: StaticSecret) -> Device { + Device { + pk: PublicKey::from(&sk), + sk: sk, + pk_map: HashMap::new(), + id_map: RwLock::new(HashMap::new()), + } + } + + /// Add a new public key to the state machine + /// To remove public keys, you must create a new machine instance + /// + /// # Arguments + /// + /// * `pk` - The public key to add + /// * `identifier` - Associated identifier which can be used to distinguish the peers + pub fn add(&mut self, pk: PublicKey, identifier: T) -> Result<(), ConfigError> { + // check that the pk is not added twice + + if let Some(_) = self.pk_map.get(pk.as_bytes()) { + return Err(ConfigError::new("Duplicate public key")); + }; + + // check that the pk is not that of the device + + if *self.pk.as_bytes() == *pk.as_bytes() { + return Err(ConfigError::new( + "Public key corresponds to secret key of interface", + )); + } + + // map : pk -> new index + + self.pk_map.insert( + *pk.as_bytes(), + Peer::new(identifier, pk, self.sk.diffie_hellman(&pk)), + ); + + Ok(()) + } + + /// Remove a peer by public key + /// To remove public keys, you must create a new machine instance + /// + /// # Arguments + /// + /// * `pk` - The public key of the peer to remove + /// + /// # Returns + /// + /// The call might fail if the public key is not found + pub fn remove(&mut self, pk: PublicKey) -> Result<(), ConfigError> { + // take write-lock on receive id table + let mut id_map = self.id_map.write(); + + // remove the peer + self.pk_map + .remove(pk.as_bytes()) + .ok_or(ConfigError::new("Public key not in device"))?; + + // pruge the id map (linear scan) + id_map.retain(|_, v| v != pk.as_bytes()); + Ok(()) + } + + /// Add a psk to the peer + /// + /// # Arguments + /// + /// * `pk` - The public key of the peer + /// * `psk` - The psk to set / unset + /// + /// # Returns + /// + /// The call might fail if the public key is not found + pub fn set_psk(&mut self, pk: PublicKey, psk: Option) -> Result<(), ConfigError> { + match self.pk_map.get_mut(pk.as_bytes()) { + Some(mut peer) => { + peer.psk = match psk { + Some(v) => v, + None => [0u8; 32], + }; + Ok(()) + } + _ => Err(ConfigError::new("No such public key")), + } + } + + /// Return the psk for the peer + /// + /// # Arguments + /// + /// * `pk` - The public key of the peer + /// + /// # Returns + /// + /// A 32 byte array holding the PSK + /// + /// The call might fail if the public key is not found + pub fn get_psk(&self, pk: PublicKey) -> Result { + match self.pk_map.get(pk.as_bytes()) { + Some(peer) => Ok(peer.psk), + _ => Err(ConfigError::new("No such public key")), + } + } + + /// Release an id back to the pool + /// + /// # Arguments + /// + /// * `id` - The (sender) id to release + pub fn release(&self, id: u32) { + let mut m = self.id_map.write(); + debug_assert!(m.contains_key(&id), "Releasing id not allocated"); + m.remove(&id); + } + + /// Begin a new handshake + /// + /// # Arguments + /// + /// * `pk` - Public key of peer to initiate handshake for + pub fn begin(&self, pk: &PublicKey) -> Result, HandshakeError> { + match self.pk_map.get(pk.as_bytes()) { + None => Err(HandshakeError::UnknownPublicKey), + Some(peer) => { + let sender = self.allocate(peer); + noise::create_initiation(self, peer, sender) + } + } + } + + /// Process a handshake message. + /// + /// # Arguments + /// + /// * `msg` - Byte slice containing the message (untrusted input) + pub fn process(&self, msg: &[u8]) -> Result, HandshakeError> { + match msg.get(0) { + Some(&messages::TYPE_INITIATION) => { + // consume the initiation + let (peer, st) = noise::consume_initiation(self, msg)?; + + // allocate new index for response + let sender = self.allocate(peer); + + // create response (release id on error) + noise::create_response(peer, sender, st).map_err(|e| { + self.release(sender); + e + }) + } + Some(&messages::TYPE_RESPONSE) => noise::consume_response(self, msg), + _ => Err(HandshakeError::InvalidMessageFormat), + } + } + + // Internal function + // + // Return the peer associated with the public key + pub(crate) fn lookup_pk(&self, pk: &PublicKey) -> Result<&Peer, HandshakeError> { + self.pk_map + .get(pk.as_bytes()) + .ok_or(HandshakeError::UnknownPublicKey) + } + + // Internal function + // + // Return the peer currently associated with the receiver identifier + pub(crate) fn lookup_id(&self, id: u32) -> Result<&Peer, HandshakeError> { + let im = self.id_map.read(); + let pk = im.get(&id).ok_or(HandshakeError::UnknownReceiverId)?; + match self.pk_map.get(pk) { + Some(peer) => Ok(peer), + _ => unreachable!(), // if the id-lookup succeeded, the peer should exist + } + } + + // Internal function + // + // Allocated a new receiver identifier for the peer + fn allocate(&self, peer: &Peer) -> u32 { + let mut rng = OsRng::new().unwrap(); + + loop { + let id = rng.gen(); + + // check membership with read lock + if self.id_map.read().contains_key(&id) { + continue; + } + + // take write lock and add index + let mut m = self.id_map.write(); + if !m.contains_key(&id) { + m.insert(id, *peer.pk.as_bytes()); + return id; + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use hex; + use messages::*; + + #[test] + fn handshake() { + // generate new keypairs + + let mut rng = OsRng::new().unwrap(); + + let sk1 = StaticSecret::new(&mut rng); + let pk1 = PublicKey::from(&sk1); + + let sk2 = StaticSecret::new(&mut rng); + let pk2 = PublicKey::from(&sk2); + + // pick random psk + + let mut psk = [0u8; 32]; + rng.fill_bytes(&mut psk[..]); + + // intialize devices on both ends + + let mut dev1 = Device::new(sk1); + let mut dev2 = Device::new(sk2); + + dev1.add(pk2, 1337).unwrap(); + dev2.add(pk1, 2600).unwrap(); + + dev1.set_psk(pk2, Some(psk)).unwrap(); + dev2.set_psk(pk1, Some(psk)).unwrap(); + + // do a few handshakes + + for i in 0..10 { + println!("handshake : {}", i); + + // create initiation + + let msg1 = dev1.begin(&pk2).unwrap(); + + println!("msg1 = {}", hex::encode(&msg1[..])); + println!("msg1 = {:?}", Initiation::parse(&msg1[..]).unwrap()); + + // process initiation and create response + + let (_, msg2, ks_r) = dev2.process(&msg1).unwrap(); + + let ks_r = ks_r.unwrap(); + let msg2 = msg2.unwrap(); + + println!("msg2 = {}", hex::encode(&msg2[..])); + println!("msg2 = {:?}", Response::parse(&msg2[..]).unwrap()); + + assert!(!ks_r.confirmed, "Responders key-pair is confirmed"); + + // process response and obtain confirmed key-pair + + let (_, msg3, ks_i) = dev1.process(&msg2).unwrap(); + let ks_i = ks_i.unwrap(); + + assert!(msg3.is_none(), "Returned message after response"); + assert!(ks_i.confirmed, "Initiators key-pair is not confirmed"); + + assert_eq!(ks_i.send, ks_r.recv, "KeyI.send != KeyR.recv"); + assert_eq!(ks_i.recv, ks_r.send, "KeyI.recv != KeyR.send"); + + dev1.release(ks_i.send.id); + dev2.release(ks_r.send.id); + } + + assert_eq!(dev1.get_psk(pk2).unwrap(), psk); + assert_eq!(dev2.get_psk(pk1).unwrap(), psk); + + dev1.remove(pk2).unwrap(); + dev2.remove(pk1).unwrap(); + } +} diff --git a/src/noise/messages.rs b/src/noise/messages.rs new file mode 100644 index 0000000..dca49b9 --- /dev/null +++ b/src/noise/messages.rs @@ -0,0 +1,208 @@ +#[cfg(test)] +use hex; + +#[cfg(test)] +use std::fmt; + +use byteorder::LittleEndian; +use zerocopy::byteorder::U32; +use zerocopy::{AsBytes, ByteSlice, FromBytes, LayoutVerified}; + +use super::types::*; + +const SIZE_TAG: usize = 16; +const SIZE_X25519_POINT: usize = 32; +const SIZE_TIMESTAMP: usize = 12; + +pub const TYPE_INITIATION: u8 = 1; +pub const TYPE_RESPONSE: u8 = 2; + +#[repr(C)] +#[derive(Copy, Clone, FromBytes, AsBytes)] +pub struct Initiation { + f_type: U32, + pub f_sender: U32, + pub f_ephemeral: [u8; SIZE_X25519_POINT], + pub f_static: [u8; SIZE_X25519_POINT], + pub f_static_tag: [u8; SIZE_TAG], + pub f_timestamp: [u8; SIZE_TIMESTAMP], + pub f_timestamp_tag: [u8; SIZE_TAG], +} + +impl Default for Initiation { + fn default() -> Self { + Self { + f_type: >::new(TYPE_INITIATION as u32), + + f_sender: >::ZERO, + f_ephemeral: [0u8; SIZE_X25519_POINT], + f_static: [0u8; SIZE_X25519_POINT], + f_static_tag: [0u8; SIZE_TAG], + f_timestamp: [0u8; SIZE_TIMESTAMP], + f_timestamp_tag: [0u8; SIZE_TAG], + } + } +} + +impl Initiation { + pub fn parse(bytes: B) -> Result, HandshakeError> { + let msg: LayoutVerified = + LayoutVerified::new(bytes).ok_or(HandshakeError::InvalidMessageFormat)?; + + if msg.f_type.get() != (TYPE_INITIATION as u32) { + return Err(HandshakeError::InvalidMessageFormat); + } + + Ok(msg) + } +} + +#[cfg(test)] +impl fmt::Debug for Initiation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, + "MessageInitiation {{ type = {}, sender = {}, ephemeral = {}, static = {}|{}, timestamp = {}|{} }}", + self.f_type.get(), + self.f_sender.get(), + hex::encode(self.f_ephemeral), + hex::encode(self.f_static), + hex::encode(self.f_static_tag), + hex::encode(self.f_timestamp), + hex::encode(self.f_timestamp_tag) + ) + } +} + +#[cfg(test)] +impl PartialEq for Initiation { + fn eq(&self, other: &Self) -> bool { + self.f_type.get() == other.f_type.get() + && self.f_sender.get() == other.f_sender.get() + && self.f_ephemeral[..] == other.f_ephemeral[..] + && self.f_static[..] == other.f_static[..] + && self.f_static_tag[..] == other.f_static_tag[..] + && self.f_timestamp[..] == other.f_timestamp + && self.f_timestamp_tag[..] == other.f_timestamp_tag + } +} + +#[cfg(test)] +impl Eq for Initiation {} + +#[repr(C)] +#[derive(Copy, Clone, FromBytes, AsBytes)] +pub struct Response { + f_type: U32, + pub f_sender: U32, + pub f_receiver: U32, + pub f_ephemeral: [u8; SIZE_X25519_POINT], + pub f_empty_tag: [u8; SIZE_TAG], +} + +impl Response { + pub fn parse(bytes: B) -> Result, HandshakeError> { + let msg: LayoutVerified = + LayoutVerified::new(bytes).ok_or(HandshakeError::InvalidMessageFormat)?; + + if msg.f_type.get() != (TYPE_RESPONSE as u32) { + return Err(HandshakeError::InvalidMessageFormat); + } + + Ok(msg) + } +} + +impl Default for Response { + fn default() -> Self { + Self { + f_type: >::new(TYPE_RESPONSE as u32), + f_sender: >::ZERO, + f_receiver: >::ZERO, + f_ephemeral: [0u8; SIZE_X25519_POINT], + f_empty_tag: [0u8; SIZE_TAG], + } + } +} + +#[cfg(test)] +impl fmt::Debug for Response { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, + "MessageResponse {{ type = {}, sender = {}, receiver = {}, ephemeral = {}, empty = |{} }}", + self.f_type, + self.f_sender, + self.f_receiver, + hex::encode(self.f_ephemeral), + hex::encode(self.f_empty_tag) + ) + } +} + +#[cfg(test)] +impl PartialEq for Response { + fn eq(&self, other: &Self) -> bool { + self.f_type == other.f_type + && self.f_sender == other.f_sender + && self.f_receiver == other.f_receiver + && self.f_ephemeral == other.f_ephemeral + && self.f_empty_tag == other.f_empty_tag + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn message_response_identity() { + let mut msg: Response = Default::default(); + + msg.f_sender.set(146252); + msg.f_receiver.set(554442); + msg.f_ephemeral = [ + 0xc1, 0x66, 0x0a, 0x0c, 0xdc, 0x0f, 0x6c, 0x51, 0x0f, 0xc2, 0xcc, 0x51, 0x52, 0x0c, + 0xde, 0x1e, 0xf7, 0xf1, 0xca, 0x90, 0x86, 0x72, 0xad, 0x67, 0xea, 0x89, 0x45, 0x44, + 0x13, 0x56, 0x52, 0x1f, + ]; + msg.f_empty_tag = [ + 0x60, 0x0e, 0x1e, 0x95, 0x41, 0x6b, 0x52, 0x05, 0xa2, 0x09, 0xe1, 0xbf, 0x40, 0x05, + 0x2f, 0xde, + ]; + + let buf: Vec = msg.as_bytes().to_vec(); + let msg_p = Response::parse(&buf[..]).unwrap(); + assert_eq!(msg, *msg_p.into_ref()); + } + + #[test] + fn message_initiate_identity() { + let mut msg: Initiation = Default::default(); + + msg.f_sender.set(575757); + msg.f_ephemeral = [ + 0xc1, 0x66, 0x0a, 0x0c, 0xdc, 0x0f, 0x6c, 0x51, 0x0f, 0xc2, 0xcc, 0x51, 0x52, 0x0c, + 0xde, 0x1e, 0xf7, 0xf1, 0xca, 0x90, 0x86, 0x72, 0xad, 0x67, 0xea, 0x89, 0x45, 0x44, + 0x13, 0x56, 0x52, 0x1f, + ]; + msg.f_static = [ + 0xdc, 0x33, 0x90, 0x15, 0x8f, 0x82, 0x3e, 0x06, 0x44, 0xa0, 0xde, 0x4c, 0x15, 0x6c, + 0x5d, 0xa4, 0x65, 0x99, 0xf6, 0x6c, 0xa1, 0x14, 0x77, 0xf9, 0xeb, 0x6a, 0xec, 0xc3, + 0x3c, 0xda, 0x47, 0xe1, + ]; + msg.f_static_tag = [ + 0x45, 0xac, 0x8d, 0x43, 0xea, 0x1b, 0x2f, 0x02, 0x45, 0x5d, 0x86, 0x37, 0xee, 0x83, + 0x6b, 0x42, + ]; + msg.f_timestamp = [ + 0x4f, 0x1c, 0x60, 0xec, 0x0e, 0xf6, 0x36, 0xf0, 0x78, 0x28, 0x57, 0x42, + ]; + msg.f_timestamp_tag = [ + 0x60, 0x0e, 0x1e, 0x95, 0x41, 0x6b, 0x52, 0x05, 0xa2, 0x09, 0xe1, 0xbf, 0x40, 0x05, + 0x2f, 0xde, + ]; + + let buf: Vec = msg.as_bytes().to_vec(); + let msg_p = Initiation::parse(&buf[..]).unwrap(); + assert_eq!(msg, *msg_p.into_ref()); + } +} diff --git a/src/noise/mod.rs b/src/noise/mod.rs new file mode 100644 index 0000000..d48b5e0 --- /dev/null +++ b/src/noise/mod.rs @@ -0,0 +1,18 @@ +/* Implementation of the: + * + * Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s + * + * Protocol pattern, see: http://www.noiseprotocol.org/noise.html. + * For documentation. + */ + +mod device; +mod messages; +mod noise; +mod peer; +mod timestamp; +mod types; + +// publicly exposed interface + +pub use device::Device; diff --git a/src/noise/noise.rs b/src/noise/noise.rs new file mode 100644 index 0000000..980f1db --- /dev/null +++ b/src/noise/noise.rs @@ -0,0 +1,458 @@ +// DH +use x25519_dalek::PublicKey; +use x25519_dalek::StaticSecret; + +// HASH & MAC +use blake2::Blake2s; +use hmac::Hmac; + +// AEAD +use crypto::aead::{AeadDecryptor, AeadEncryptor}; +use crypto::chacha20poly1305::ChaCha20Poly1305; + +use rand::rngs::OsRng; + +use generic_array::typenum::*; +use generic_array::GenericArray; + +use zerocopy::AsBytes; + +use super::device::Device; +use super::messages::{Initiation, Response}; +use super::peer::{Peer, State}; +use super::timestamp; +use super::types::*; + +use crate::types::{Key, KeyPair}; + +// HMAC hasher (generic construction) + +type HMACBlake2s = Hmac; + +// convenient alias to pass state temporarily into device.rs and back + +type TemporaryState = (u32, PublicKey, GenericArray, GenericArray); + +const SIZE_CK: usize = 32; +const SIZE_HS: usize = 32; +const SIZE_NONCE: usize = 8; + +// C := Hash(Construction) +const INITIAL_CK: [u8; SIZE_CK] = [ + 0x60, 0xe2, 0x6d, 0xae, 0xf3, 0x27, 0xef, 0xc0, 0x2e, 0xc3, 0x35, 0xe2, 0xa0, 0x25, 0xd2, 0xd0, + 0x16, 0xeb, 0x42, 0x06, 0xf8, 0x72, 0x77, 0xf5, 0x2d, 0x38, 0xd1, 0x98, 0x8b, 0x78, 0xcd, 0x36, +]; + +// H := Hash(C || Identifier) +const INITIAL_HS: [u8; SIZE_HS] = [ + 0x22, 0x11, 0xb3, 0x61, 0x08, 0x1a, 0xc5, 0x66, 0x69, 0x12, 0x43, 0xdb, 0x45, 0x8a, 0xd5, 0x32, + 0x2d, 0x9c, 0x6c, 0x66, 0x22, 0x93, 0xe8, 0xb7, 0x0e, 0xe1, 0x9c, 0x65, 0xba, 0x07, 0x9e, 0xf3, +]; + +const ZERO_NONCE: [u8; SIZE_NONCE] = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + +macro_rules! HASH { + ( $($input:expr),* ) => {{ + use blake2::Digest; + let mut hsh = Blake2s::new(); + $( + hsh.input($input); + )* + hsh.result() + }}; +} + +macro_rules! HMAC { + ($key:expr, $($input:expr),*) => {{ + use hmac::Mac; + let mut mac = HMACBlake2s::new_varkey($key).unwrap(); + $( + mac.input($input); + )* + mac.result().code() + }}; +} + +macro_rules! KDF1 { + ($ck:expr, $input:expr) => {{ + let t0 = HMAC!($ck, $input); + let t1 = HMAC!(&t0, &[0x1]); + t1 + }}; +} + +macro_rules! KDF2 { + ($ck:expr, $input:expr) => {{ + let t0 = HMAC!($ck, $input); + let t1 = HMAC!(&t0, &[0x1]); + let t2 = HMAC!(&t0, &t1, &[0x2]); + (t1, t2) + }}; +} + +macro_rules! KDF3 { + ($ck:expr, $input:expr) => {{ + let t0 = HMAC!($ck, $input); + let t1 = HMAC!(&t0, &[0x1]); + let t2 = HMAC!(&t0, &t1, &[0x2]); + let t3 = HMAC!(&t0, &t2, &[0x3]); + (t1, t2, t3) + }}; +} + +macro_rules! SEAL { + ($key:expr, $aead:expr, $pt:expr, $ct:expr, $tag:expr) => {{ + let mut aead = ChaCha20Poly1305::new($key, &ZERO_NONCE, $aead); + aead.encrypt($pt, $ct, $tag); + }}; +} + +macro_rules! OPEN { + ($key:expr, $aead:expr, $pt:expr, $ct:expr, $tag:expr) => {{ + let mut aead = ChaCha20Poly1305::new($key, &ZERO_NONCE, $aead); + if !aead.decrypt($ct, $pt, $tag) { + Err(HandshakeError::DecryptionFailure) + } else { + Ok(()) + } + }}; +} + +#[cfg(test)] +mod tests { + use super::*; + + const IDENTIFIER: &[u8] = b"WireGuard v1 zx2c4 Jason@zx2c4.com"; + const CONSTRUCTION: &[u8] = b"Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s"; + + #[test] + fn precomputed_chain_key() { + assert_eq!(INITIAL_CK[..], HASH!(CONSTRUCTION)[..]); + } + + #[test] + fn precomputed_hash() { + assert_eq!(INITIAL_HS[..], HASH!(INITIAL_CK, IDENTIFIER)[..]); + } +} + +pub fn create_initiation( + device: &Device, + peer: &Peer, + sender: u32, +) -> Result, HandshakeError> { + let mut rng = OsRng::new().unwrap(); + let mut msg: Initiation = Default::default(); + + // initialize state + + let ck = INITIAL_CK; + let hs = INITIAL_HS; + let hs = HASH!(&hs, peer.pk.as_bytes()); + + msg.f_sender.set(sender); + + // (E_priv, E_pub) := DH-Generate() + + let eph_sk = StaticSecret::new(&mut rng); + let eph_pk = PublicKey::from(&eph_sk); + + // C := Kdf(C, E_pub) + + let ck = KDF1!(&ck, eph_pk.as_bytes()); + + // msg.ephemeral := E_pub + + msg.f_ephemeral = *eph_pk.as_bytes(); + + // H := HASH(H, msg.ephemeral) + + let hs = HASH!(&hs, msg.f_ephemeral); + + // (C, k) := Kdf2(C, DH(E_priv, S_pub)) + + let (ck, key) = KDF2!(&ck, eph_sk.diffie_hellman(&peer.pk).as_bytes()); + + // msg.static := Aead(k, 0, S_pub, H) + + SEAL!( + &key, + &hs, // ad + device.pk.as_bytes(), // pt + &mut msg.f_static, // ct + &mut msg.f_static_tag // tag + ); + + // H := Hash(H || msg.static) + + let hs = HASH!(&hs, &msg.f_static, &msg.f_static_tag); + + // (C, k) := Kdf2(C, DH(S_priv, S_pub)) + + let (ck, key) = KDF2!(&ck, peer.ss.as_bytes()); + + // msg.timestamp := Aead(k, 0, Timestamp(), H) + + SEAL!( + &key, + &hs, // ad + ×tamp::now(), // pt + &mut msg.f_timestamp, // ct + &mut msg.f_timestamp_tag // tag + ); + + // H := Hash(H || msg.timestamp) + + let hs = HASH!(&hs, &msg.f_timestamp, &msg.f_timestamp_tag); + + // update state of peer + + peer.set_state(State::InitiationSent { + hs, + ck, + eph_sk, + sender, + }); + + // return message as vector + + Ok(msg.as_bytes().to_vec()) +} + +pub fn consume_initiation<'a, T: Copy>( + device: &'a Device, + msg: &[u8], +) -> Result<(&'a Peer, TemporaryState), HandshakeError> { + // parse message + + let msg = Initiation::parse(msg)?; + + // initialize state + + let ck = INITIAL_CK; + let hs = INITIAL_HS; + let hs = HASH!(&hs, device.pk.as_bytes()); + + // C := Kdf(C, E_pub) + + let ck = KDF1!(&ck, &msg.f_ephemeral); + + // H := HASH(H, msg.ephemeral) + + let hs = HASH!(&hs, &msg.f_ephemeral); + + // (C, k) := Kdf2(C, DH(E_priv, S_pub)) + + let eph_r_pk = PublicKey::from(msg.f_ephemeral); + let (ck, key) = KDF2!(&ck, device.sk.diffie_hellman(&eph_r_pk).as_bytes()); + + // msg.static := Aead(k, 0, S_pub, H) + + let mut pk = [0u8; 32]; + + OPEN!( + &key, + &hs, // ad + &mut pk, // pt + &msg.f_static, // ct + &msg.f_static_tag // tag + )?; + + let peer = device.lookup_pk(&PublicKey::from(pk))?; + + // H := Hash(H || msg.static) + + let hs = HASH!(&hs, &msg.f_static, &msg.f_static_tag); + + // (C, k) := Kdf2(C, DH(S_priv, S_pub)) + + let (ck, key) = KDF2!(&ck, peer.ss.as_bytes()); + + // msg.timestamp := Aead(k, 0, Timestamp(), H) + + let mut ts = timestamp::zero(); + + OPEN!( + &key, + &hs, // ad + &mut ts, // pt + &msg.f_timestamp, // ct + &msg.f_timestamp_tag // tag + )?; + + // check and update timestamp + + peer.check_timestamp(device, &ts)?; + + // H := Hash(H || msg.timestamp) + + let hs = HASH!(&hs, &msg.f_timestamp, &msg.f_timestamp_tag); + + // return state (to create response) + + Ok((peer, (msg.f_sender.get(), eph_r_pk, hs, ck))) +} + +pub fn create_response( + peer: &Peer, + sender: u32, // sending identifier + state: TemporaryState, // state from "consume_initiation" +) -> Result, HandshakeError> { + let mut rng = OsRng::new().unwrap(); + let mut msg: Response = Default::default(); + + let (receiver, eph_r_pk, hs, ck) = state; + + msg.f_sender.set(sender); + msg.f_receiver.set(receiver); + + // (E_priv, E_pub) := DH-Generate() + + let eph_sk = StaticSecret::new(&mut rng); + let eph_pk = PublicKey::from(&eph_sk); + + // C := Kdf1(C, E_pub) + + let ck = KDF1!(&ck, eph_pk.as_bytes()); + + // msg.ephemeral := E_pub + + msg.f_ephemeral = *eph_pk.as_bytes(); + + // H := Hash(H || msg.ephemeral) + + let hs = HASH!(&hs, &msg.f_ephemeral); + + // C := Kdf1(C, DH(E_priv, E_pub)) + + let ck = KDF1!(&ck, eph_sk.diffie_hellman(&eph_r_pk).as_bytes()); + + // C := Kdf1(C, DH(E_priv, S_pub)) + + let ck = KDF1!(&ck, eph_sk.diffie_hellman(&peer.pk).as_bytes()); + + // (C, tau, k) := Kdf3(C, Q) + + let (ck, tau, key) = KDF3!(&ck, &peer.psk); + + // H := Hash(H || tau) + + let hs = HASH!(&hs, tau); + + // msg.empty := Aead(k, 0, [], H) + + SEAL!( + &key, + &hs, // ad + &[], // pt + &mut [], // ct + &mut msg.f_empty_tag // tag + ); + + /* not strictly needed + * // H := Hash(H || msg.empty) + * let hs = HASH!(&hs, &msg.f_empty_tag); + */ + + // derive key-pair + // (verbose code, due to GenericArray -> [u8; 32] conversion) + + let (key_recv, key_send) = KDF2!(&ck, &[]); + + // return response and unconfirmed key-pair + + Ok(( + peer.identifier, + Some(msg.as_bytes().to_vec()), + Some(KeyPair { + confirmed: false, + send: Key { + id: sender, + key: key_send.into(), + }, + recv: Key { + id: receiver, + key: key_recv.into(), + }, + }), + )) +} + +pub fn consume_response( + device: &Device, + msg: &[u8], +) -> Result, HandshakeError> { + // parse message + + let msg = Response::parse(msg)?; + + // retrieve peer and associated state + + let peer = device.lookup_id(msg.f_receiver.get())?; + let (hs, ck, sender, eph_sk) = match peer.get_state() { + State::Reset => Err(HandshakeError::InvalidState), + State::InitiationSent { + hs, + ck, + sender, + eph_sk, + } => Ok((hs, ck, sender, eph_sk)), + }?; + + // C := Kdf1(C, E_pub) + + let ck = KDF1!(&ck, &msg.f_ephemeral); + + // H := Hash(H || msg.ephemeral) + + let hs = HASH!(&hs, &msg.f_ephemeral); + + // C := Kdf1(C, DH(E_priv, E_pub)) + + let eph_r_pk = PublicKey::from(msg.f_ephemeral); + let ck = KDF1!(&ck, eph_sk.diffie_hellman(&eph_r_pk).as_bytes()); + + // C := Kdf1(C, DH(E_priv, S_pub)) + + let ck = KDF1!(&ck, device.sk.diffie_hellman(&eph_r_pk).as_bytes()); + + // (C, tau, k) := Kdf3(C, Q) + + let (ck, tau, key) = KDF3!(&ck, &peer.psk); + + // H := Hash(H || tau) + + let hs = HASH!(&hs, tau); + + // msg.empty := Aead(k, 0, [], H) + + OPEN!( + &key, + &hs, // ad + &mut [], // pt + &[], // ct + &msg.f_empty_tag // tag + )?; + + // derive key-pair + + let (key_send, key_recv) = KDF2!(&ck, &[]); + + // return confirmed key-pair + + Ok(( + peer.identifier, + None, + Some(KeyPair { + confirmed: true, + send: Key { + id: sender, + key: key_send.into(), + }, + recv: Key { + id: msg.f_sender.get(), + key: key_recv.into(), + }, + }), + )) +} diff --git a/src/noise/peer.rs b/src/noise/peer.rs new file mode 100644 index 0000000..5b01d75 --- /dev/null +++ b/src/noise/peer.rs @@ -0,0 +1,136 @@ +use spin::Mutex; + +use generic_array::typenum::U32; +use generic_array::GenericArray; + +use x25519_dalek::PublicKey; +use x25519_dalek::SharedSecret; +use x25519_dalek::StaticSecret; + +use super::device::Device; +use super::timestamp; +use super::types::*; + +/* Represents the recomputation and state of a peer. + * + * This type is only for internal use and not exposed. + */ + +pub struct Peer { + // external identifier + pub(crate) identifier: T, + + // mutable state + state: Mutex, + timestamp: Mutex>, + + // constant state + pub(crate) pk: PublicKey, // public key of peer + pub(crate) ss: SharedSecret, // precomputed DH(static, static) + pub(crate) psk: Psk, // psk of peer +} + +pub enum State { + Reset, + InitiationSent { + sender: u32, // assigned sender id + eph_sk: StaticSecret, + hs: GenericArray, + ck: GenericArray, + }, +} + +impl Clone for State { + fn clone(&self) -> State { + match self { + State::Reset => State::Reset, + State::InitiationSent { + sender, + eph_sk, + hs, + ck, + } => State::InitiationSent { + sender: *sender, + eph_sk: StaticSecret::from(eph_sk.to_bytes()), + hs: *hs, + ck: *ck, + }, + } + } +} + +impl Peer +where + T: Copy, +{ + pub fn new( + identifier: T, // external identifier + pk: PublicKey, // public key of peer + ss: SharedSecret, // precomputed DH(static, static) + ) -> Self { + Self { + identifier: identifier, + state: Mutex::new(State::Reset), + timestamp: Mutex::new(None), + pk: pk, + ss: ss, + psk: [0u8; 32], + } + } + + /// Return the state of the peer + /// + /// # Arguments + pub fn get_state(&self) -> State { + self.state.lock().clone() + } + + /// Set the state of the peer unconditionally + /// + /// # Arguments + /// + pub fn set_state(&self, state_new: State) { + *self.state.lock() = state_new; + } + + /// Set the mutable state of the peer conditioned on the timestamp being newer + /// + /// # Arguments + /// + /// * st_new - The updated state of the peer + /// * ts_new - The associated timestamp + pub fn check_timestamp( + &self, + device: &Device, + timestamp_new: ×tamp::TAI64N, + ) -> Result<(), HandshakeError> { + let mut state = self.state.lock(); + let mut timestamp = self.timestamp.lock(); + + let update = match *timestamp { + None => true, + Some(timestamp_old) => { + if timestamp::compare(×tamp_old, ×tamp_new) { + true + } else { + false + } + } + }; + + if update { + // release existing identifier + match *state { + State::InitiationSent { sender, .. } => device.release(sender), + _ => (), + } + + // reset state and update timestamp + *state = State::Reset; + *timestamp = Some(*timestamp_new); + Ok(()) + } else { + Err(HandshakeError::OldTimestamp) + } + } +} diff --git a/src/noise/timestamp.rs b/src/noise/timestamp.rs new file mode 100644 index 0000000..0996f8b --- /dev/null +++ b/src/noise/timestamp.rs @@ -0,0 +1,34 @@ +use std::time::{SystemTime, UNIX_EPOCH}; + +const TAI64_EPOCH: u64 = 0x4000000000000000; + +pub type TAI64N = [u8; 12]; + +pub fn zero() -> TAI64N { + [0u8; 12] +} + +pub fn now() -> TAI64N { + // get system time as duration + let sysnow = SystemTime::now(); + let delta = sysnow.duration_since(UNIX_EPOCH).unwrap(); + + // convert to tai64n + let tai64_secs = delta.as_secs() + TAI64_EPOCH; + let tai64_nano = delta.subsec_nanos(); + + // serialize + let mut res = [0u8; 12]; + res[..8].copy_from_slice(&tai64_secs.to_be_bytes()[..]); + res[8..].copy_from_slice(&tai64_nano.to_be_bytes()[..]); + res +} + +pub fn compare(old: &TAI64N, new: &TAI64N) -> bool { + for i in 0..12 { + if new[i] > old[i] { + return true; + } + } + return false; +} diff --git a/src/noise/types.rs b/src/noise/types.rs new file mode 100644 index 0000000..0d9a5d3 --- /dev/null +++ b/src/noise/types.rs @@ -0,0 +1,81 @@ +use std::error::Error; +use std::fmt; + +use crate::types::KeyPair; + +/* Internal types for the noise IKpsk2 implementation */ + +// config error + +#[derive(Debug)] +pub struct ConfigError(String); + +impl ConfigError { + pub fn new(s: &str) -> Self { + ConfigError(s.to_string()) + } +} + +impl fmt::Display for ConfigError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "ConfigError({})", self.0) + } +} + +impl Error for ConfigError { + fn description(&self) -> &str { + &self.0 + } + + fn source(&self) -> Option<&(dyn Error + 'static)> { + None + } +} + +// handshake error + +#[derive(Debug)] +pub enum HandshakeError { + DecryptionFailure, + UnknownPublicKey, + UnknownReceiverId, + InvalidMessageFormat, + OldTimestamp, + InvalidState, +} + +impl fmt::Display for HandshakeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + HandshakeError::DecryptionFailure => write!(f, "Failed to AEAD:OPEN"), + HandshakeError::UnknownPublicKey => write!(f, "Unknown public key"), + HandshakeError::UnknownReceiverId => { + write!(f, "Receiver id not allocated to any handshake") + } + HandshakeError::InvalidMessageFormat => write!(f, "Invalid handshake message format"), + HandshakeError::OldTimestamp => write!(f, "Timestamp is less/equal to the newest"), + HandshakeError::InvalidState => write!(f, "Message does not apply to handshake state"), + } + } +} + +impl Error for HandshakeError { + fn description(&self) -> &str { + "Generic Handshake Error" + } + + fn source(&self) -> Option<&(dyn Error + 'static)> { + None + } +} + +pub type Output = ( + T, // external identifier associated with peer + // (e.g. a reference or vector index) + Option>, // message to send + Option, // resulting key-pair of successful handshake +); + +// preshared key + +pub type Psk = [u8; 32]; diff --git a/src/peer.rs b/src/peer.rs deleted file mode 100644 index 2dff10e..0000000 --- a/src/peer.rs +++ /dev/null @@ -1,136 +0,0 @@ -use spin::Mutex; - -use generic_array::typenum::U32; -use generic_array::GenericArray; - -use x25519_dalek::PublicKey; -use x25519_dalek::SharedSecret; -use x25519_dalek::StaticSecret; - -use crate::device::Device; -use crate::timestamp; -use crate::types::*; - -/* Represents the recomputation and state of a peer. - * - * This type is only for internal use and not exposed. - */ - -pub struct Peer { - // external identifier - pub(crate) identifier: T, - - // mutable state - state: Mutex, - timestamp: Mutex>, - - // constant state - pub(crate) pk: PublicKey, // public key of peer - pub(crate) ss: SharedSecret, // precomputed DH(static, static) - pub(crate) psk: Psk, // psk of peer -} - -pub enum State { - Reset, - InitiationSent { - sender: u32, // assigned sender id - eph_sk: StaticSecret, - hs: GenericArray, - ck: GenericArray, - }, -} - -impl Clone for State { - fn clone(&self) -> State { - match self { - State::Reset => State::Reset, - State::InitiationSent { - sender, - eph_sk, - hs, - ck, - } => State::InitiationSent { - sender: *sender, - eph_sk: StaticSecret::from(eph_sk.to_bytes()), - hs: *hs, - ck: *ck, - }, - } - } -} - -impl Peer -where - T: Copy, -{ - pub fn new( - identifier: T, // external identifier - pk: PublicKey, // public key of peer - ss: SharedSecret, // precomputed DH(static, static) - ) -> Self { - Self { - identifier: identifier, - state: Mutex::new(State::Reset), - timestamp: Mutex::new(None), - pk: pk, - ss: ss, - psk: [0u8; 32], - } - } - - /// Return the state of the peer - /// - /// # Arguments - pub fn get_state(&self) -> State { - self.state.lock().clone() - } - - /// Set the state of the peer unconditionally - /// - /// # Arguments - /// - pub fn set_state(&self, state_new: State) { - *self.state.lock() = state_new; - } - - /// Set the mutable state of the peer conditioned on the timestamp being newer - /// - /// # Arguments - /// - /// * st_new - The updated state of the peer - /// * ts_new - The associated timestamp - pub fn check_timestamp( - &self, - device: &Device, - timestamp_new: ×tamp::TAI64N, - ) -> Result<(), HandshakeError> { - let mut state = self.state.lock(); - let mut timestamp = self.timestamp.lock(); - - let update = match *timestamp { - None => true, - Some(timestamp_old) => { - if timestamp::compare(×tamp_old, ×tamp_new) { - true - } else { - false - } - } - }; - - if update { - // release existing identifier - match *state { - State::InitiationSent { sender, .. } => device.release(sender), - _ => (), - } - - // reset state and update timestamp - *state = State::Reset; - *timestamp = Some(*timestamp_new); - Ok(()) - } else { - Err(HandshakeError::OldTimestamp) - } - } -} diff --git a/src/timestamp.rs b/src/timestamp.rs deleted file mode 100644 index 0996f8b..0000000 --- a/src/timestamp.rs +++ /dev/null @@ -1,34 +0,0 @@ -use std::time::{SystemTime, UNIX_EPOCH}; - -const TAI64_EPOCH: u64 = 0x4000000000000000; - -pub type TAI64N = [u8; 12]; - -pub fn zero() -> TAI64N { - [0u8; 12] -} - -pub fn now() -> TAI64N { - // get system time as duration - let sysnow = SystemTime::now(); - let delta = sysnow.duration_since(UNIX_EPOCH).unwrap(); - - // convert to tai64n - let tai64_secs = delta.as_secs() + TAI64_EPOCH; - let tai64_nano = delta.subsec_nanos(); - - // serialize - let mut res = [0u8; 12]; - res[..8].copy_from_slice(&tai64_secs.to_be_bytes()[..]); - res[8..].copy_from_slice(&tai64_nano.to_be_bytes()[..]); - res -} - -pub fn compare(old: &TAI64N, new: &TAI64N) -> bool { - for i in 0..12 { - if new[i] > old[i] { - return true; - } - } - return false; -} diff --git a/src/types.rs b/src/types.rs deleted file mode 100644 index a18057f..0000000 --- a/src/types.rs +++ /dev/null @@ -1,99 +0,0 @@ -use std::error::Error; -use std::fmt; - -// config error - -#[derive(Debug)] -pub struct ConfigError(String); - -impl ConfigError { - pub fn new(s: &str) -> Self { - ConfigError(s.to_string()) - } -} - -impl fmt::Display for ConfigError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "ConfigError({})", self.0) - } -} - -impl Error for ConfigError { - fn description(&self) -> &str { - &self.0 - } - - fn source(&self) -> Option<&(dyn Error + 'static)> { - None - } -} - -// handshake error - -#[derive(Debug)] -pub enum HandshakeError { - DecryptionFailure, - UnknownPublicKey, - UnknownReceiverId, - InvalidMessageFormat, - OldTimestamp, - InvalidState, -} - -impl fmt::Display for HandshakeError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - HandshakeError::DecryptionFailure => write!(f, "Failed to AEAD:OPEN"), - HandshakeError::UnknownPublicKey => write!(f, "Unknown public key"), - HandshakeError::UnknownReceiverId => { - write!(f, "Receiver id not allocated to any handshake") - } - HandshakeError::InvalidMessageFormat => write!(f, "Invalid handshake message format"), - HandshakeError::OldTimestamp => write!(f, "Timestamp is less/equal to the newest"), - HandshakeError::InvalidState => write!(f, "Message does not apply to handshake state"), - } - } -} - -impl Error for HandshakeError { - fn description(&self) -> &str { - "Generic Handshake Error" - } - - fn source(&self) -> Option<&(dyn Error + 'static)> { - None - } -} - -// types for resulting key-material - -#[derive(Debug)] -pub struct Key { - pub key: [u8; 32], - pub id: u32, -} - -#[cfg(test)] -impl PartialEq for Key { - fn eq(&self, other: &Self) -> bool { - self.id == other.id && self.key[..] == other.key[..] - } -} - -#[derive(Debug)] -pub struct KeyPair { - pub confirmed: bool, // has the key-pair been confirmed? - pub send: Key, // key for outbound messages - pub recv: Key, // key for inbound messages -} - -pub type Output = ( - T, // external identifier associated with peer - // (e.g. a reference or vector index) - Option>, // message to send - Option, // resulting key-pair of successful handshake -); - -// preshared key - -pub type Psk = [u8; 32]; diff --git a/src/types/mod.rs b/src/types/mod.rs new file mode 100644 index 0000000..ac6a307 --- /dev/null +++ b/src/types/mod.rs @@ -0,0 +1,23 @@ +/* This file holds types passed between components. + * Whenever a type cannot be held local to a single module. + */ + +#[derive(Debug)] +pub struct Key { + pub key: [u8; 32], + pub id: u32, +} + +#[cfg(test)] +impl PartialEq for Key { + fn eq(&self, other: &Self) -> bool { + self.id == other.id && self.key[..] == other.key[..] + } +} + +#[derive(Debug)] +pub struct KeyPair { + pub confirmed: bool, // has the key-pair been confirmed? + pub send: Key, // key for outbound messages + pub recv: Key, // key for inbound messages +} -- cgit v1.2.3-59-g8ed1b