From c77697b8edb529834bc5f57fe3bdb35cbbe3e809 Mon Sep 17 00:00:00 2001 From: Mathias Hall-Andersen Date: Mon, 22 Jul 2019 23:40:04 +0200 Subject: Finish handshake exchange --- Cargo.lock | 7 +++ Cargo.toml | 1 + src/device.rs | 61 ++++++++++----------- src/noise.rs | 169 +++++++++++++++++++++++++++++++++++++++++++++++++--------- src/peer.rs | 106 ++++++++++++++++++------------------ src/types.rs | 10 +++- 6 files changed, 247 insertions(+), 107 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2351810..1833136 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -265,6 +265,11 @@ name = "rustc-serialize" version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "spin" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "subtle" version = "1.0.0" @@ -299,6 +304,7 @@ dependencies = [ "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)", ] @@ -365,6 +371,7 @@ dependencies = [ "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" "checksum rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" +"checksum spin 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44363f6f51401c34e7be73db0db371c04705d35efbe9f7d6082e03a921a32c55" "checksum subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" "checksum subtle 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "702662512f3ddeb74a64ce2fbbf3707ee1b6bb663d28bb054e0779bbc720d926" "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" diff --git a/Cargo.toml b/Cargo.toml index 2d27e3d..7631157 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2018" license = "GPL-3.0" [dependencies] +spin = "0.5.0" rand = "0.6.5" blake2 = "0.8.0" hmac = "0.7.1" diff --git a/src/device.rs b/src/device.rs index 0635a5e..1acbbd4 100644 --- a/src/device.rs +++ b/src/device.rs @@ -1,4 +1,4 @@ -use std::sync::Mutex; +use spin::RwLock; use std::collections::HashMap; use rand::prelude::*; @@ -13,11 +13,11 @@ use crate::types::*; use crate::peer::Peer; pub struct Device { - pub sk : StaticSecret, // static secret key - pub pk : PublicKey, // static public key - peers : Vec, // peer index -> state - pkmap : HashMap<[u8; 32], usize>, // public key -> peer index - ids : Mutex> // receive ids -> peer index + pub sk : StaticSecret, // static secret key + pub pk : PublicKey, // static public key + peers : Vec, // peer index -> state + pk_map : HashMap<[u8; 32], usize>, // public key -> peer index + id_map : RwLock> // receive ids -> peer index } /* A mutable reference to the state machine needs to be held, @@ -31,11 +31,11 @@ impl Device { /// * `sk` - x25519 scalar representing the local private key pub fn new(sk : StaticSecret) -> Device { Device { - pk : PublicKey::from(&sk), - sk : sk, - peers : vec![], - pkmap : HashMap::new(), - ids : Mutex::new(HashMap::new()) + pk : PublicKey::from(&sk), + sk : sk, + peers : vec![], + pk_map : HashMap::new(), + id_map : RwLock::new(HashMap::new()) } } @@ -48,7 +48,7 @@ impl Device { pub fn add(&mut self, pk : PublicKey) -> Result<(), ConfigError> { // check that the pk is not added twice - if let Some(_) = self.pkmap.get(pk.as_bytes()) { + if let Some(_) = self.pk_map.get(pk.as_bytes()) { return Err(ConfigError::new("Duplicate public key")); }; @@ -61,7 +61,7 @@ impl Device { // map : pk -> new index let idx = self.peers.len(); - self.pkmap.insert(*pk.as_bytes(), idx); + self.pk_map.insert(*pk.as_bytes(), idx); // map : new index -> peer @@ -83,7 +83,7 @@ impl Device { /// /// The call might fail if the public key is not found pub fn psk(&mut self, pk : PublicKey, psk : Option) -> Result<(), ConfigError> { - match self.pkmap.get(pk.as_bytes()) { + match self.pk_map.get(pk.as_bytes()) { Some(&idx) => { let peer = &mut self.peers[idx]; peer.psk = match psk { @@ -102,7 +102,7 @@ impl Device { /// /// * `id` - The (sender) id to release pub fn release(&self, id : u32) { - self.ids.lock().unwrap().remove(&id); + self.id_map.write().remove(&id); } /// Begin a new handshake @@ -111,7 +111,7 @@ impl Device { /// /// * `pk` - Public key of peer to initiate handshake for pub fn begin(&self, pk : &PublicKey) -> Result, HandshakeError> { - match self.pkmap.get(pk.as_bytes()) { + match self.pk_map.get(pk.as_bytes()) { None => Err(HandshakeError::UnknownPublicKey), Some(&idx) => { let peer = &self.peers[idx]; @@ -130,41 +130,42 @@ impl Device { match msg.get(0) { Some(&messages::TYPE_INITIATION) => { // consume the initiation - let (peer, receiver, hs, ck) = noise::consume_initiation(self, msg)?; + let (peer, st) = noise::consume_initiation(self, msg)?; - // allocate index for response + // allocate new index for response let sender = self.allocate(peer.idx); // create response - noise::create_response(self, peer, sender, receiver, hs, ck).map_err(|e| { + noise::create_response(self, peer, sender, st).map_err(|e| { self.release(sender); e }) }, - Some(&messages::TYPE_RESPONSE) => { - Err(HandshakeError::InvalidMessageFormat) - }, + Some(&messages::TYPE_RESPONSE) => noise::consume_response(self, msg), _ => Err(HandshakeError::InvalidMessageFormat) } } - pub fn lookup(&self, pk : &PublicKey) -> Result<&Peer, HandshakeError> { - match self.pkmap.get(pk.as_bytes()) { + pub(crate) fn lookup_pk(&self, pk : &PublicKey) -> Result<&Peer, HandshakeError> { + match self.pk_map.get(pk.as_bytes()) { Some(&idx) => Ok(&self.peers[idx]), _ => Err(HandshakeError::UnknownPublicKey) } } -} -impl Device { - // allocate a new index (id), for peer with idx + pub(crate) fn lookup_id(&self, id : u32) -> Result<&Peer, HandshakeError> { + match self.id_map.read().get(&id) { + Some(&idx) => Ok(&self.peers[idx]), + _ => Err(HandshakeError::UnknownReceiverId) + } + } + fn allocate(&self, idx : usize) -> u32 { let mut rng = OsRng::new().unwrap(); - let mut table = self.ids.lock().unwrap(); loop { let id = rng.gen(); - if !table.contains_key(&id) { - table.insert(id, idx); + if !self.id_map.read().contains_key(&id) { + self.id_map.write().insert(id, idx); return id; } } diff --git a/src/noise.rs b/src/noise.rs index 9f127e8..f5d1dc1 100644 --- a/src/noise.rs +++ b/src/noise.rs @@ -23,9 +23,13 @@ use crate::device::Device; use crate::messages::{Initiation, Response}; use crate::timestamp; +// HMAC hasher (generic construction) + type HMACBlake2s = Hmac; -/* Internal functions for processing and creating noise messages */ +// 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; @@ -121,6 +125,18 @@ macro_rules! KDF2 { } } +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) => { { @@ -171,8 +187,9 @@ mod tests { pub fn create_initiation( device : &Device, peer : &Peer, - id : u32 + sender : u32 ) -> Result, HandshakeError> { + let mut rng = OsRng::new().unwrap(); let mut msg : Initiation = Default::default(); @@ -182,20 +199,20 @@ pub fn create_initiation( let hs = INITIAL_HS; let hs = HASH!(&hs, peer.pk.as_bytes()); - msg.f_sender = id; + msg.f_sender = sender; // (E_priv, E_pub) := DH-Generate() - let sk = StaticSecret::new(&mut rng); - let pk = PublicKey::from(&sk); + let eph_sk = StaticSecret::new(&mut rng); + let eph_pk = PublicKey::from(&eph_sk); // C := Kdf(C, E_pub) - let ck = KDF1!(&ck, pk.as_bytes()); + let ck = KDF1!(&ck, eph_pk.as_bytes()); // msg.ephemeral := E_pub - msg.f_ephemeral = *pk.as_bytes(); + msg.f_ephemeral = *eph_pk.as_bytes(); // H := HASH(H, msg.ephemeral) @@ -203,7 +220,7 @@ pub fn create_initiation( // (C, k) := Kdf2(C, DH(E_priv, S_pub)) - let (ck, key) = KDF2!(&ck, sk.diffie_hellman(&peer.pk).as_bytes()); + let (ck, key) = KDF2!(&ck, eph_sk.diffie_hellman(&peer.pk).as_bytes()); // msg.static := Aead(k, 0, S_pub, H) @@ -239,12 +256,7 @@ pub fn create_initiation( // update state of peer - peer.set_state( - State::InitiationSent{ - hs : hs, - ck : ck - } - ); + peer.set_state(State::InitiationSent{hs, ck, eph_sk, sender}); // return message as vector @@ -254,7 +266,7 @@ pub fn create_initiation( pub fn consume_initiation<'a>( device : &'a Device, msg : &[u8] -) -> Result<(&'a Peer, u32, GenericArray, GenericArray), HandshakeError> { +) -> Result<(&'a Peer, TemporaryState), HandshakeError> { // parse message @@ -276,10 +288,10 @@ pub fn consume_initiation<'a>( // (C, k) := Kdf2(C, DH(E_priv, S_pub)) - let eph = PublicKey::from(msg.f_ephemeral); + let eph_r_pk = PublicKey::from(msg.f_ephemeral); let (ck, key) = KDF2!( &ck, - device.sk.diffie_hellman(&eph).as_bytes() + device.sk.diffie_hellman(&eph_r_pk).as_bytes() ); // msg.static := Aead(k, 0, S_pub, H) @@ -294,7 +306,7 @@ pub fn consume_initiation<'a>( &msg.f_static_tag // tag )?; - let peer = device.lookup(&PublicKey::from(pk))?; + let peer = device.lookup_pk(&PublicKey::from(pk))?; // H := Hash(H || msg.static) @@ -318,25 +330,134 @@ pub fn consume_initiation<'a>( // check and update timestamp - peer.check_timestamp(&ts)?; + peer.check_timestamp(device, &ts)?; // return state (to create response) - Ok((peer, msg.f_sender, hs, ck)) + Ok((peer, (msg.f_sender, eph_r_pk, hs, ck))) } pub fn create_response( device : &Device, peer : &Peer, - sender : u32, - receiver : u32, - hs : GenericArray, - ck : GenericArray + sender : u32, // sending identifier + state : TemporaryState // state from "consume_initiation" ) -> Result { + let mut rng = OsRng::new().unwrap(); let mut msg : Response = Default::default(); + let (receiver, eph_r_pk, hs, ck) = state; + + // (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 + ); + + // H := Hash(H || msg.empty) + + // let hs = HASH!(&hs, &msg.f_empty_tag); // not strictly needed + + // derive key-pair + + let (key_recv, key_send) = KDF2!(&ck, &[]); + + Ok(Output(None, None)) +} + +pub fn consume_response( + device : &Device, + msg : &[u8] +) -> Result { + // parse message + let msg = Response::try_from(msg)?; + + // retrieve peer and associated state + + let peer = device.lookup_id(msg.f_receiver)?; + 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, 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) + + OPEN!( + &key, + &hs, // ad + &mut [], // pt + &[], // ct + &msg.f_empty_tag // tag + ); + + // derive key-pair + + let (key_send, key_recv) = KDF2!(&ck, &[]); + Ok(Output(None, None)) } diff --git a/src/peer.rs b/src/peer.rs index e656d56..7678e85 100644 --- a/src/peer.rs +++ b/src/peer.rs @@ -4,10 +4,12 @@ use generic_array::typenum::U32; use generic_array::GenericArray; use x25519_dalek::PublicKey; +use x25519_dalek::StaticSecret; use x25519_dalek::SharedSecret; use crate::types::*; use crate::timestamp; +use crate::device::Device; /* Represents the recomputation and state of a peer. * @@ -15,32 +17,49 @@ use crate::timestamp; */ pub struct Peer { - pub idx : usize, + // internal identifier + pub(crate) idx : usize, // mutable state state : Mutex, timestamp : Mutex>, // constant state - pub pk : PublicKey, // public key of peer - pub ss : SharedSecret, // precomputed DH(static, static) - pub psk : Psk // psk of peer + pub(crate) pk : PublicKey, // public key of peer + pub(crate) ss : SharedSecret, // precomputed DH(static, static) + pub(crate) psk : Psk // psk of peer } -#[derive(Debug, Copy, Clone)] pub enum State { Reset, InitiationSent{ - hs : GenericArray, - ck : GenericArray + 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 { pub fn new( - idx : usize, - pk : PublicKey, // public key of peer - ss : SharedSecret // precomputed DH(static, static) + idx : usize, + pk : PublicKey, // public key of peer + ss : SharedSecret // precomputed DH(static, static) ) -> Self { Self { idx : idx, @@ -56,7 +75,7 @@ impl Peer { /// /// # Arguments pub fn get_state(&self) -> State { - *self.state.lock().unwrap() + self.state.lock().unwrap().clone() } /// Set the state of the peer unconditionally @@ -71,60 +90,45 @@ impl Peer { *state = state_new; } - /// # Arguments - /// - /// * ts_new - The timestamp - /// - /// # Returns - /// - /// A Boolean indicating if the state was updated - pub fn check_timestamp(&self, - timestamp_new : ×tamp::TAI64N) -> Result<(), HandshakeError> { - - let mut timestamp = self.timestamp.lock().unwrap(); - match *timestamp { - None => Ok(()), - Some(timestamp_old) => if timestamp::compare(×tamp_old, ×tamp_new) { - *timestamp = Some(*timestamp_new); - Ok(()) - } else { - Err(HandshakeError::OldTimestamp) - } - } - } - /// 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 - /// - /// # Returns - /// - /// A Boolean indicating if the state was updated - pub fn set_state_timestamp( + pub fn check_timestamp( &self, - state_new : State, + device : &Device, timestamp_new : ×tamp::TAI64N ) -> Result<(), HandshakeError> { + let mut state = self.state.lock().unwrap(); let mut timestamp = self.timestamp.lock().unwrap(); - match *timestamp { - None => { - // no prior timestamp know - *state = state_new; - *timestamp = Some(*timestamp_new); - Ok(()) - }, + + let update = match *timestamp { + None => true, Some(timestamp_old) => if timestamp::compare(×tamp_old, ×tamp_new) { - // new timestamp is strictly greater - *state = state_new; - *timestamp = Some(*timestamp_new); - Ok(()) + true } else { - Err(HandshakeError::OldTimestamp) + 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/types.rs b/src/types.rs index a2a7cdd..5500ffc 100644 --- a/src/types.rs +++ b/src/types.rs @@ -34,8 +34,10 @@ impl Error for ConfigError { pub enum HandshakeError { DecryptionFailure, UnknownPublicKey, + UnknownReceiverId, InvalidMessageFormat, - OldTimestamp + OldTimestamp, + InvalidState } impl fmt::Display for HandshakeError { @@ -45,10 +47,14 @@ impl fmt::Display for HandshakeError { 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") + write!(f, "Timestamp is less/equal to the newest"), + HandshakeError::InvalidState => + write!(f, "Message does not apply to handshake state") } } } -- cgit v1.2.3-59-g8ed1b