//! # Overview //! //! The most important types in this module probably are [PollResult] & [Server]. //! Once a [Server] was created, the server is provided with new messages via //! the [Server::handle_msg] method. The [Server::poll] method can be used to //! let the server work, which will eventually yield a [PollResult]. Said //! [PollResult] contains prescriptive activities to be carried out. //! //! TODO explain briefly the role of epki //! //! # Example Handshake //! //! TODO finish doctest example //! //! ``` //! use rosenpass::{ //! pqkem::{SKEM, KEM}, //! protocol::{SSk, SPk, MsgBuf, PeerPtr, Server, SymKey}, //! }; //! # fn main() -> Result<(), rosenpass::RosenpassError> { //! //! // always init libsodium before anything //! rosenpass::sodium::sodium_init().unwrap(); //! //! // initialize public and private key for peer a ... //! let (mut peer_a_sk, mut peer_a_pk) = (SSk::zero(), SPk::zero()); //! SKEM::keygen(peer_a_sk.secret_mut(), peer_a_pk.secret_mut())?; //! //! // ... and for peer b //! let (mut peer_b_sk, mut peer_b_pk) = (SSk::zero(), SPk::zero()); //! SKEM::keygen(peer_b_sk.secret_mut(), peer_b_pk.secret_mut())?; //! //! // initialize server and a pre-shared key //! let psk = SymKey::random(); //! let mut a = Server::new(peer_a_sk, peer_a_pk.clone()); //! let mut b = Server::new(peer_b_sk, peer_b_pk.clone()); //! //! // introduce peers to each other //! a.add_peer(Some(psk.clone()), peer_b_pk).unwrap(); //! b.add_peer(Some(psk), peer_a_pk).unwrap(); //! //! // let them talk //! let (mut a_buf, mut b_buf) = (MsgBuf::zero(), MsgBuf::zero()); //! let sz = a.initiate_handshake(PeerPtr(0), &mut *a_buf).unwrap(); //! //let (a_key, b_key) = handle(a, &mut a_buf, sz, b, &mut b_buf).unwrap(); //! //assert_eq!(a_key.unwrap().secret(), b_key.unwrap().secret(), //! // "the key exchanged failed to establish a shared secret"); //! # Ok(()) //! # } //! ``` use crate::{ coloring::*, labeled_prf as lprf, msgs::*, pqkem::*, prftree::{SecretPrfTree, SecretPrfTreeBranch}, sodium::*, util::*, }; use anyhow::{bail, ensure, Context, Result}; use std::collections::hash_map::{ Entry::{Occupied, Vacant}, HashMap, }; // CONSTANTS & SETTINGS ////////////////////////// /// Size required to fit any message in binary form pub const RTX_BUFFER_SIZE: usize = max_usize( > as LenseView>::LEN, > as LenseView>::LEN, ); /// A type for time, e.g. for backoff before re-tries pub type Timing = f64; /// Before Common Era (or more practically: Definitely so old it needs refreshing) /// /// Using this instead of Timing::MIN or Timing::INFINITY to avoid floating /// point math weirdness. pub const BCE: Timing = -3600.0 * 24.0 * 356.0 * 10_000.0; // Actually it's eight hours; This is intentional to avoid weirdness // regarding unexpectedly large numbers in system APIs as this is < i16::MAX pub const UNENDING: Timing = 3600.0 * 8.0; // From the wireguard paper; rekey every two minutes, // discard the key if no rekey is achieved within three pub const REKEY_AFTER_TIME: Timing = 120.0; pub const REJECT_AFTER_TIME: Timing = 180.0; // Seconds until the biscuit key is changed; we issue biscuits // using one biscuit key for one epoch and store the biscuit for // decryption for a second epoch pub const BISCUIT_EPOCH: Timing = 300.0; // Retransmission pub constants; will retransmit for up to _ABORT ms; starting with a delay of // _DELAY_BEG ms and increasing the delay exponentially by a factor of // _DELAY_GROWTH up to _DELAY_END. An .secretadditional jitter factor of ±_DELAY_JITTER // is added. pub const RETRANSMIT_ABORT: Timing = 120.0; pub const RETRANSMIT_DELAY_GROWTH: Timing = 2.0; pub const RETRANSMIT_DELAY_BEGIN: Timing = 0.5; pub const RETRANSMIT_DELAY_END: Timing = 10.0; pub const RETRANSMIT_DELAY_JITTER: Timing = 0.5; pub const EVENT_GRACE: Timing = 0.0025; // UTILITY FUNCTIONS ///////////////////////////// // Event handling: For an event at T we sleep for T-now // but we act on the event starting at T-EVENT_GRACE already // to avoid sleeping for very short periods. This also avoids // busy loop in case the sleep subsystem is imprecise. Our timing // is therefor generally accurate up to ±2∙EVENT_GRACE pub fn has_happened(ev: Timing, now: Timing) -> bool { (ev - now) < EVENT_GRACE } // DATA STRUCTURES & BASIC TRAITS & ACCESSORS //// pub type SPk = Secret<{ SKEM::PK_LEN }>; // Just Secret<> instead of Public<> so it gets allocated on the heap pub type SSk = Secret<{ SKEM::SK_LEN }>; pub type EPk = Public<{ EKEM::PK_LEN }>; pub type ESk = Secret<{ EKEM::SK_LEN }>; pub type SymKey = Secret; pub type SymHash = Public; pub type PeerId = Public; pub type SessionId = Public; pub type BiscuitId = Public; pub type XAEADNonce = Public; pub type MsgBuf = Public; pub type PeerNo = usize; /// Implementation of the actual cryptographic server #[derive(Debug)] pub struct Server { pub timebase: Timebase, // Server Crypto pub sskm: SSk, pub spkm: SPk, pub biscuit_ctr: BiscuitId, pub biscuit_keys: [BiscuitKey; 2], // Peer/Handshake DB pub peers: Vec, pub index: HashMap, // Tick handling pub peer_poll_off: usize, } /// A Biscuit is like a fancy cookie. To avoid state disruption attacks, /// the responder doesn't store state. Instead the state is stored in a /// Biscuit, that is encrypted using the [BiscuitKey] which is only known to /// the Respnder. Thus secrecy of the Responder state is not violated, still /// the responder can avoid storing this state. #[derive(Debug)] pub struct BiscuitKey { pub created_at: Timing, pub key: SymKey, } #[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug)] pub enum IndexKey { Peer(PeerId), Sid(SessionId), } #[derive(Debug)] pub struct Peer { pub psk: SymKey, pub spkt: SPk, pub biscuit_used: BiscuitId, pub session: Option, pub handshake: Option, pub initiation_requested: bool, } impl Peer { pub fn zero() -> Self { Self { psk: SymKey::zero(), spkt: SPk::zero(), biscuit_used: BiscuitId::zero(), session: None, initiation_requested: false, handshake: None, } } } #[derive(Debug, Clone)] pub struct HandshakeState { /// Session ID of Initiator pub sidi: SessionId, /// Session ID of Responder pub sidr: SessionId, /// Chaining Key pub ck: SecretPrfTreeBranch, } #[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug)] pub enum HandshakeRole { Initiator, Responder, } impl HandshakeRole { pub fn is_initiator(&self) -> bool { match *self { HandshakeRole::Initiator => true, HandshakeRole::Responder => false, } } } #[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Debug)] pub enum HandshakeStateMachine { RespHello, RespConf, } impl Default for HandshakeStateMachine { fn default() -> Self { HandshakeStateMachine::RespHello } } #[derive(Debug)] pub struct InitiatorHandshake { pub created_at: Timing, pub next: HandshakeStateMachine, pub core: HandshakeState, /// Ephemeral Secret Key of Initiator pub eski: ESk, /// Ephemeral Public Key of Initiator pub epki: EPk, // Retransmission // TODO: Ensure that this is correct by typing pub tx_at: Timing, pub tx_retry_at: Timing, pub tx_count: usize, pub tx_len: usize, pub tx_buf: MsgBuf, } #[derive(Debug)] pub struct Session { // Metadata pub created_at: Timing, pub sidm: SessionId, pub sidt: SessionId, // Crypto pub ck: SecretPrfTreeBranch, /// Key for Transmission ("transmission key mine") pub txkm: SymKey, /// Key for Receival ("transmission key theirs") pub txkt: SymKey, /// Nonce for Transmission ("transmission nonce mine") pub txnm: u64, /// Nonce for Receival ("transmission nonce theirs") pub txnt: u64, } /// Lifecycle of a Secret /// /// The order implies the readiness for usage of a secret, the highes/biggest /// variant ([Lifecycle::Young]) is the most preferable one in a class of /// equal-role secrets. #[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug)] enum Lifecycle { /// Not even generated Void = 0, /// Secret must be zeroized, disposal adviced Dead, /// Soon to be dead: the secret might be used for receiving /// data, but must not be used for future sending Retired, /// The secret might be used unconditionally Young, } /// Implemented for information (secret and public) that has an expire date trait Mortal { /// Time of creation, when [Lifecycle::Void] -> [Lifecycle::Young] fn created_at(&self, srv: &Server) -> Option; /// The time where [Lifecycle::Young] -> [Lifecycle::Retired] fn retire_at(&self, srv: &Server) -> Option; /// The time where [Lifecycle::Retired] -> [Lifecycle::Dead] fn die_at(&self, srv: &Server) -> Option; } // BUSINESS LOGIC DATA STRUCTURES //////////////// /// Valid index to [Server::peers] #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] pub struct PeerPtr(pub usize); /// Valid index to [Server::peers] #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] pub struct IniHsPtr(pub usize); /// Valid index to [Server::peers] #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] pub struct SessionPtr(pub usize); /// Valid index to [Server::biscuit_keys] #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] pub struct BiscuitKeyPtr(pub usize); impl PeerPtr { pub fn get<'a>(&self, srv: &'a Server) -> &'a Peer { &srv.peers[self.0] } pub fn get_mut<'a>(&self, srv: &'a mut Server) -> &'a mut Peer { &mut srv.peers[self.0] } pub fn session(&self) -> SessionPtr { SessionPtr(self.0) } pub fn hs(&self) -> IniHsPtr { IniHsPtr(self.0) } } impl IniHsPtr { pub fn get<'a>(&self, srv: &'a Server) -> &'a Option { &srv.peers[self.0].handshake } pub fn get_mut<'a>(&self, srv: &'a mut Server) -> &'a mut Option { &mut srv.peers[self.0].handshake } pub fn peer(&self) -> PeerPtr { PeerPtr(self.0) } pub fn insert<'a>( &self, srv: &'a mut Server, hs: InitiatorHandshake, ) -> Result<&'a mut InitiatorHandshake> { srv.register_session(hs.core.sidi, self.peer())?; self.take(srv); self.peer().get_mut(srv).initiation_requested = false; Ok(self.peer().get_mut(srv).handshake.insert(hs)) } pub fn take(&self, srv: &mut Server) -> Option { let r = self.peer().get_mut(srv).handshake.take(); if let Some(ref stale) = r { srv.unregister_session_if_vacant(stale.core.sidi, self.peer()); } r } } impl SessionPtr { pub fn get<'a>(&self, srv: &'a Server) -> &'a Option { &srv.peers[self.0].session } pub fn get_mut<'a>(&self, srv: &'a mut Server) -> &'a mut Option { &mut srv.peers[self.0].session } pub fn peer(&self) -> PeerPtr { PeerPtr(self.0) } pub fn insert<'a>(&self, srv: &'a mut Server, ses: Session) -> Result<&'a mut Session> { self.take(srv); srv.register_session(ses.sidm, self.peer())?; Ok(self.peer().get_mut(srv).session.insert(ses)) } pub fn take(&self, srv: &mut Server) -> Option { let r = self.peer().get_mut(srv).session.take(); if let Some(ref stale) = r { srv.unregister_session_if_vacant(stale.sidm, self.peer()); } r } } impl BiscuitKeyPtr { pub fn get<'a>(&self, srv: &'a Server) -> &'a BiscuitKey { &srv.biscuit_keys[self.0] } pub fn get_mut<'a>(&self, srv: &'a mut Server) -> &'a mut BiscuitKey { &mut srv.biscuit_keys[self.0] } } // DATABASE ////////////////////////////////////// impl Server { /// Initiate a new [Server] based on a secret key (`sk`) and a public key /// (`pk`) pub fn new(sk: SSk, pk: SPk) -> Server { let tb = Timebase::default(); Server { sskm: sk, spkm: pk, // Defaults timebase: tb, biscuit_ctr: BiscuitId::new([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), // 1, LSB biscuit_keys: [BiscuitKey::new(), BiscuitKey::new()], peers: Vec::new(), index: HashMap::new(), peer_poll_off: 0, } } /// Iterate over the many (2) biscuit keys pub fn biscuit_key_ptrs(&self) -> impl Iterator { (0..self.biscuit_keys.len()).map(BiscuitKeyPtr) } pub fn pidm(&self) -> Result { Ok(Public::new( lprf::peerid()? .mix(self.spkm.secret())? .into_value())) } /// Iterate over all peers, starting with the `n`th peer, wrapping at the /// end of the peers vec so that also all peers from index 0 to `n - 1` are /// yielded pub fn peer_ptrs_off(&self, n: usize) -> impl Iterator { let l = self.peers.len(); (0..l).map(move |i| PeerPtr((i + n) % l)) } /// Add a peer with an optional pre shared key (`psk`) and its public key (`pk`) pub fn add_peer(&mut self, psk: Option, pk: SPk) -> Result { let peer = Peer { psk: psk.unwrap_or_else(SymKey::zero), spkt: pk, biscuit_used: BiscuitId::zero(), session: None, handshake: None, initiation_requested: false, }; let peerid = peer.pidt()?; let peerno = self.peers.len(); match self.index.entry(IndexKey::Peer(peerid)) { Occupied(_) => bail!( "Cannot insert peer with id {:?}; peer with this id already registered.", peerid ), Vacant(e) => e.insert(peerno), }; self.peers.push(peer); Ok(PeerPtr(peerno)) } /// Register a new session (during a sucesfull handshake, persisting longer /// than the handshake). Might return an error on session id collision pub fn register_session(&mut self, id: SessionId, peer: PeerPtr) -> Result<()> { match self.index.entry(IndexKey::Sid(id)) { Occupied(p) if PeerPtr(*p.get()) == peer => {} // Already registered Occupied(_) => bail!("Cannot insert session with id {:?}; id is in use.", id), Vacant(e) => { e.insert(peer.0); } }; Ok(()) } pub fn unregister_session(&mut self, id: SessionId) { self.index.remove(&IndexKey::Sid(id)); } /// Remove the given session if it is neither an active session nor in /// handshake phase pub fn unregister_session_if_vacant(&mut self, id: SessionId, peer: PeerPtr) { match (peer.session().get(self), peer.hs().get(self)) { (Some(ses), _) if ses.sidm == id => {} /* nop */ (_, Some(hs)) if hs.core.sidi == id => {} /* nop */ _ => self.unregister_session(id), }; } pub fn find_peer(&self, id: PeerId) -> Option { self.index.get(&IndexKey::Peer(id)).map(|no| PeerPtr(*no)) } // lookup_session in whitepaper pub fn lookup_handshake(&self, id: SessionId) -> Option { self.index .get(&IndexKey::Sid(id)) // lookup the session in the index .map(|no| IniHsPtr(*no)) // convert to peer pointer .filter(|hsptr| { hsptr .get(self) // lookup in the server .as_ref() .map(|hs| hs.core.sidi == id) // check that handshake id matches as well .unwrap_or(false) // it didn't match?! }) } // also lookup_session in the whitepaper pub fn lookup_session(&self, id: SessionId) -> Option { self.index .get(&IndexKey::Sid(id)) .map(|no| SessionPtr(*no)) .filter(|sptr| { sptr.get(self) .as_ref() .map(|ses| ses.sidm == id) .unwrap_or(false) }) } /// Swap the biscuit keys, also advancing both biscuit key's mortality pub fn active_biscuit_key(&mut self) -> BiscuitKeyPtr { let (a, b) = (BiscuitKeyPtr(0), BiscuitKeyPtr(1)); let (t, u) = (a.get(self).created_at, b.get(self).created_at); // Return the youngest but only if it's youthful // first being returned in case of a tie let r = if t >= u { a } else { b }; if r.lifecycle(self) == Lifecycle::Young { return r; } // Reap the oldest biscuit key and spawn a new young one otherwise // last one being reaped in case of a tie let r = if t < u { a } else { b }; let tb = self.timebase.clone(); r.get_mut(self).randomize(&tb); r } } impl Peer { pub fn new(psk: SymKey, pk: SPk) -> Peer { Peer { psk, spkt: pk, biscuit_used: BiscuitId::zero(), session: None, handshake: None, initiation_requested: false, } } pub fn pidt(&self) -> Result { Ok(Public::new( lprf::peerid()? .mix(self.spkt.secret())? .into_value())) } } impl Session { pub fn zero() -> Self { Self { created_at: 0.0, sidm: SessionId::zero(), sidt: SessionId::zero(), ck: SecretPrfTree::zero().dup(), txkm: SymKey::zero(), txkt: SymKey::zero(), txnm: 0, txnt: 0, } } } // BISCUIT KEY /////////////////////////////////// /// Biscuit Keys are always randomized, so that even if through a bug some /// secrete is encrypted with an unitialized [BiscuitKey], nobody instead of /// everybody may read the secret. impl BiscuitKey { // new creates a random value, that might be counterintuitive for a Default // impl #[allow(clippy::new_without_default)] pub fn new() -> Self { Self { created_at: BCE, key: SymKey::random(), } } pub fn erase(&mut self) { self.key.randomize(); self.created_at = BCE; } pub fn randomize(&mut self, tb: &Timebase) { self.key.randomize(); self.created_at = tb.now(); } } // LIFECYCLE MANAGEMENT ////////////////////////// impl Mortal for IniHsPtr { fn created_at(&self, srv: &Server) -> Option { self.get(srv).as_ref().map(|hs| hs.created_at) } fn retire_at(&self, srv: &Server) -> Option { self.die_at(srv) } fn die_at(&self, srv: &Server) -> Option { self.created_at(srv).map(|t| t + REJECT_AFTER_TIME) } } impl Mortal for SessionPtr { fn created_at(&self, srv: &Server) -> Option { self.get(srv).as_ref().map(|p| p.created_at) } fn retire_at(&self, srv: &Server) -> Option { self.created_at(srv).map(|t| t + REKEY_AFTER_TIME) } fn die_at(&self, srv: &Server) -> Option { self.created_at(srv).map(|t| t + REJECT_AFTER_TIME) } } impl Mortal for BiscuitKeyPtr { fn created_at(&self, srv: &Server) -> Option { let t = self.get(srv).created_at; if t < 0.0 { None } else { Some(t) } } fn retire_at(&self, srv: &Server) -> Option { self.created_at(srv).map(|t| t + BISCUIT_EPOCH) } fn die_at(&self, srv: &Server) -> Option { self.retire_at(srv).map(|t| t + BISCUIT_EPOCH) } } /// Trait extension to the [Mortal] Trait, that enables nicer access to timing /// information trait MortalExt: Mortal { fn life_left(&self, srv: &Server) -> Option; fn youth_left(&self, srv: &Server) -> Option; fn lifecycle(&self, srv: &Server) -> Lifecycle; } impl MortalExt for T { fn life_left(&self, srv: &Server) -> Option { self.die_at(srv).map(|t| t - srv.timebase.now()) } fn youth_left(&self, srv: &Server) -> Option { self.retire_at(srv).map(|t| t - srv.timebase.now()) } fn lifecycle(&self, srv: &Server) -> Lifecycle { match (self.youth_left(srv), self.life_left(srv)) { (_, Some(t)) if has_happened(t, 0.0) => Lifecycle::Dead, (Some(t), _) if has_happened(t, 0.0) => Lifecycle::Retired, (Some(_), Some(_)) => Lifecycle::Young, _ => Lifecycle::Void, } } } // MESSAGE HANDLING ////////////////////////////// impl Server { /// Initiate a new handshake, put it to the `tx_buf` __and__ to the /// retransmission storage // NOTE retransmission? yes if initiator, no if responder // TODO remove unecessary copying between global tx_buf and per-peer buf // TODO move retransmission storage to io server pub fn initiate_handshake(&mut self, peer: PeerPtr, tx_buf: &mut [u8]) -> Result { let mut msg = tx_buf.envelope::>()?; // Envelope::::default(); // TODO self.handle_initiation(peer, msg.payload_mut().init_hello()?)?; let len = self.seal_and_commit_msg(peer, MsgType::InitHello, msg)?; peer.hs() .store_msg_for_retransmission(self, &tx_buf[..len])?; Ok(len) } } #[derive(Debug)] pub struct HandleMsgResult { pub exchanged_with: Option, pub resp: Option, } impl Server { /// Repsond to an incoming message /// /// # Overview /// /// The response is only dependent on the incoming message, thus this /// function is called regardless of whether the the the calling context is /// an iniator, a responder or both. The flow of is as follows: /// /// 1. check incoming message for valid [MsgType] /// 2. check that the seal is intact, e.g. that the message is /// authenticated /// 3. call the respective handler function for this message (for example /// [Self::handle_init_hello]) /// 4. if the protocol foresees a response to this message, generate one /// 5. seal the response with cryptographic authentication /// 6. if the response is a ResponseHello, store the sealed response for /// further retransmision /// 7. return some peerpointer if the exchange completed with this message /// 8. return the length of the response generated /// /// This is the sequence of a succesful handshake: /// /// | time | Initiator | direction | Responder | /// | --- | ---: | :---: | :--- | /// | t0 | `InitHello` | -> | | /// | t1 | | <- | `RespHello` | /// | t2 | `InitConf` | -> | | /// | t3 | | <- | `EmptyData` | pub fn handle_msg(&mut self, rx_buf: &[u8], tx_buf: &mut [u8]) -> Result { let seal_broken = "Message seal broken!"; // lengt of the response. We assume no response, so None for now let mut len = 0; let mut exchanged = false; let peer = match rx_buf[0].try_into() { Ok(MsgType::InitHello) => { let msg_in = rx_buf.envelope::>()?; ensure!(msg_in.check_seal(self)?, seal_broken); let mut msg_out = tx_buf.envelope::>()?; let peer = self.handle_init_hello( msg_in.payload().init_hello()?, msg_out.payload_mut().resp_hello()?, )?; len = self.seal_and_commit_msg(peer, MsgType::RespHello, msg_out)?; peer } Ok(MsgType::RespHello) => { let msg_in = rx_buf.envelope::>()?; ensure!(msg_in.check_seal(self)?, seal_broken); let mut msg_out = tx_buf.envelope::>()?; let peer = self.handle_resp_hello( msg_in.payload().resp_hello()?, msg_out.payload_mut().init_conf()?, )?; len = self.seal_and_commit_msg(peer, MsgType::InitConf, msg_out)?; peer.hs() .store_msg_for_retransmission(self, &tx_buf[..len])?; exchanged = true; peer } Ok(MsgType::InitConf) => { let msg_in = rx_buf.envelope::>()?; ensure!(msg_in.check_seal(self)?, seal_broken); let mut msg_out = tx_buf.envelope::>()?; let peer = self.handle_init_conf( msg_in.payload().init_conf()?, msg_out.payload_mut().empty_data()?, )?; len = self.seal_and_commit_msg(peer, MsgType::EmptyData, msg_out)?; exchanged = true; peer } Ok(MsgType::EmptyData) => { let msg_in = rx_buf.envelope::>()?; ensure!(msg_in.check_seal(self)?, seal_broken); self.handle_resp_conf(msg_in.payload().empty_data()?)? } Ok(MsgType::DataMsg) => bail!("DataMsg handling not implemented!"), Ok(MsgType::CookieReply) => bail!("CookieReply handling not implemented!"), Err(_) => { bail!("CookieReply handling not implemented!") } }; Ok(HandleMsgResult { exchanged_with: exchanged.then_some(peer), resp: if len == 0 { None } else { Some(len) }, }) } /// Serialize message to `tx_buf`, generating the `mac` in the process of /// doing so /// /// The message type is explicitly required here because it is very easy to /// forget setting that, which creates subtle but far ranging errors. pub fn seal_and_commit_msg( &mut self, peer: PeerPtr, msg_type: MsgType, mut msg: Envelope<&mut [u8], M>, ) -> Result { msg.msg_type_mut()[0] = msg_type as u8; msg.seal(peer, self)?; Ok( as LenseView>::LEN) } } // EVENT POLLING ///////////////////////////////// #[derive(Debug, Copy, Clone)] pub struct Wait(Timing); impl Wait { fn immediate() -> Self { Wait(0.0) } fn hibernate() -> Self { Wait(UNENDING) } fn immediate_unless(cond: bool) -> Self { if cond { Self::hibernate() } else { Self::immediate() } } fn or_hibernate(t: Option) -> Self { match t { Some(u) => Wait(u), None => Wait::hibernate(), } } fn or_immediate(t: Option) -> Self { match t { Some(u) => Wait(u), None => Wait::immediate(), } } fn and>(&self, o: T) -> Self { let (a, b) = (self.0, o.into().0); Wait(if a > b { a } else { b }) } } impl From for Wait { fn from(t: Timing) -> Wait { Wait(t) } } impl From> for Wait { fn from(t: Option) -> Wait { Wait::or_hibernate(t) } } /// Result of a poll operation, containing prescriptive action for the outer /// event loop #[derive(Debug, Copy, Clone)] pub enum PollResult { Sleep(Timing), DeleteKey(PeerPtr), SendInitiation(PeerPtr), SendRetransmission(PeerPtr), } impl Default for PollResult { fn default() -> Self { Self::hibernate() } } impl PollResult { pub fn hibernate() -> Self { Self::Sleep(UNENDING) // Avoid excessive sleep times (might trigger bugs on some platforms) } pub fn peer(&self) -> Option { use PollResult::*; match *self { DeleteKey(p) | SendInitiation(p) | SendRetransmission(p) => Some(p), _ => None, } } pub fn fold(&self, otr: PollResult) -> PollResult { use PollResult::*; match (*self, otr) { (Sleep(a), Sleep(b)) => Sleep(f64::min(a, b)), (a, Sleep(_b)) if a.saturated() => a, (Sleep(_a), b) if b.saturated() => b, _ => panic!( "Do not fold two saturated poll results! It doesn't make sense; \ we would have to discard one of the events. \ As soon as some result that requires an action (i.e. something other than sleep \ is reached you should just return and have the API consumer poll again." ), } } pub fn try_fold_with Result>(&self, f: F) -> Result { if self.saturated() { Ok(*self) } else { Ok(self.fold(f()?)) } } pub fn poll_child(&self, srv: &mut Server, p: &P) -> Result { self.try_fold_with(|| p.poll(srv)) } pub fn poll_children(&self, srv: &mut Server, iter: I) -> Result where P: Pollable, I: Iterator, { let mut acc = *self; for e in iter { if acc.saturated() { break; } acc = acc.fold(e.poll(srv)?); } Ok(acc) } /// Execute `f` if it is ready, as indicated by `wait` pub fn sched, F: FnOnce() -> PollResult>(&self, wait: W, f: F) -> PollResult { let wait = wait.into().0; if self.saturated() { *self } else if has_happened(wait, 0.0) { self.fold(f()) } else { self.fold(Self::Sleep(wait)) } } pub fn try_sched, F: FnOnce() -> Result>( &self, wait: W, f: F, ) -> Result { let wait = wait.into().0; if self.saturated() { Ok(*self) } else if has_happened(wait, 0.0) { Ok(self.fold(f()?)) } else { Ok(self.fold(Self::Sleep(wait))) } } pub fn ok(&self) -> Result { Ok(*self) } pub fn saturated(&self) -> bool { use PollResult::*; !matches!(self, Sleep(_)) } } pub fn begin_poll() -> PollResult { PollResult::default() } /// Takes a closure `f`, returns another closure which internally calls f and /// then returns a default [PollResult] pub fn void_poll T>(f: F) -> impl FnOnce() -> PollResult { || { f(); PollResult::default() } } pub trait Pollable { fn poll(&self, srv: &mut Server) -> Result; } impl Server { /// Implements something like [Pollable::poll] for the server, with a /// notable difference: since `self` already is the server, the signature /// has to be different; `self` must be a `&mut` and already is a borrow to /// the server, eluding the need for a second arg. pub fn poll(&mut self) -> Result { let r = begin_poll() // Poll each biscuit and peer until an event is found .poll_children(self, self.biscuit_key_ptrs())? .poll_children(self, self.peer_ptrs_off(self.peer_poll_off))?; self.peer_poll_off = match r.peer() { Some(p) => p.0 + 1, // Event found while polling peer p; will poll peer p+1 next None => 0, // No peer ev found. Resetting to 0 out of an irrational fear of non-zero numbers }; r.ok() } } impl Pollable for BiscuitKeyPtr { fn poll(&self, srv: &mut Server) -> Result { begin_poll() .sched(self.life_left(srv), void_poll(|| self.get_mut(srv).erase())) // Erase stale biscuits .ok() } } impl Pollable for PeerPtr { fn poll(&self, srv: &mut Server) -> Result { let (ses, hs) = (self.session(), self.hs()); begin_poll() .sched(hs.life_left(srv), void_poll(|| hs.take(srv))) // Silently erase old handshakes .sched(ses.life_left(srv), || { // Erase old sessions ses.take(srv); PollResult::DeleteKey(*self) }) // Initialize the handshake // IF if initiation hasn't been requested (consumer of the API is free to // ignore the request hence there is a need to do record keeping on that) // AND after the existing session becomes stale or if there is session at all // AND after the current handshake becomes stale or there is no handshake at all .sched( Wait::immediate_unless(self.get(srv).initiation_requested) .and(Wait::or_immediate(ses.youth_left(srv))) .and(Wait::or_immediate(hs.youth_left(srv))), || { self.get_mut(srv).initiation_requested = true; PollResult::SendInitiation(*self) }, ) .poll_child(srv, &hs) // Defer to the handshake for polling (retransmissions) } } impl Pollable for IniHsPtr { fn poll(&self, srv: &mut Server) -> Result { begin_poll().try_sched(self.retransmission_in(srv), || { // Registering retransmission even if app does not retransmit. // This explicitly permits applications to ignore the event. self.register_retransmission(srv)?; Ok(PollResult::SendRetransmission(self.peer())) }) } } // MESSAGE RETRANSMISSION //////////////////////// impl Server { pub fn retransmit_handshake(&mut self, peer: PeerPtr, tx_buf: &mut [u8]) -> Result { peer.hs().apply_retransmission(self, tx_buf) } } impl IniHsPtr { pub fn store_msg_for_retransmission(&self, srv: &mut Server, msg: &[u8]) -> Result<()> { let ih = self .get_mut(srv) .as_mut() .with_context(|| format!("No current handshake for peer {:?}", self.peer()))?; cpy_min(msg, &mut *ih.tx_buf); ih.tx_count = 0; ih.tx_len = msg.len(); self.register_retransmission(srv)?; Ok(()) } pub fn apply_retransmission(&self, srv: &mut Server, tx_buf: &mut [u8]) -> Result { let ih = self .get_mut(srv) .as_mut() .with_context(|| format!("No current handshake for peer {:?}", self.peer()))?; cpy_min(&ih.tx_buf[..ih.tx_len], tx_buf); Ok(ih.tx_len) } pub fn register_retransmission(&self, srv: &mut Server) -> Result<()> { let tb = srv.timebase.clone(); let ih = self .get_mut(srv) .as_mut() .with_context(|| format!("No current handshake for peer {:?}", self.peer()))?; // Base delay, exponential increase, ±50% jitter ih.tx_retry_at = tb.now() + RETRANSMIT_DELAY_BEGIN * RETRANSMIT_DELAY_GROWTH.powf( (RETRANSMIT_DELAY_END / RETRANSMIT_DELAY_BEGIN) .log(RETRANSMIT_DELAY_GROWTH) .min(ih.tx_count as f64), ) * RETRANSMIT_DELAY_JITTER * (rand_f64() + 1.0); ih.tx_count += 1; Ok(()) } pub fn retransmission_in(&self, srv: &mut Server) -> Option { self.get(srv) .as_ref() .map(|hs| hs.tx_retry_at - srv.timebase.now()) } } // CRYPTO/HANDSHAKE HANDLING ///////////////////// impl Envelope<&mut [u8], M> where M: LenseView, { /// Calculate the message authentication code (`mac`) pub fn seal(&mut self, peer: PeerPtr, srv: &Server) -> Result<()> { let mac = lprf::mac()? .mix(peer.get(srv).spkt.secret())? .mix(self.until_mac())?; self.mac_mut() .copy_from_slice(mac.into_value()[..16].as_ref()); Ok(()) } } impl Envelope<&[u8], M> where M: LenseView, { /// Check the message authentication code pub fn check_seal(&self, srv: &Server) -> Result { let expected = lprf::mac()?.mix(srv.spkm.secret())?.mix(self.until_mac())?; Ok(sodium_memcmp(self.mac(), &expected.into_value()[..16])) } } impl InitiatorHandshake { pub fn zero_with_timestamp(srv: &Server) -> Self { InitiatorHandshake { created_at: srv.timebase.now(), next: HandshakeStateMachine::RespHello, core: HandshakeState::zero(), eski: ESk::zero(), epki: EPk::zero(), tx_at: 0.0, tx_retry_at: 0.0, tx_count: 0, tx_len: 0, tx_buf: MsgBuf::zero(), } } } impl HandshakeState { pub fn zero() -> Self { Self { sidi: SessionId::zero(), sidr: SessionId::zero(), ck: SecretPrfTree::zero().dup(), } } pub fn erase(&mut self) { self.ck = SecretPrfTree::zero().dup(); } pub fn init(&mut self, spkr: &[u8]) -> Result<&mut Self> { self.ck = lprf::ckinit()?.mix(spkr)?.into_secret_prf_tree().dup(); Ok(self) } pub fn mix(&mut self, a: &[u8]) -> Result<&mut Self> { self.ck = self.ck.mix(&lprf::mix()?)?.mix(a)?.dup(); Ok(self) } pub fn encrypt_and_mix(&mut self, ct: &mut [u8], pt: &[u8]) -> Result<&mut Self> { let k = self.ck.mix(&lprf::hs_enc()?)?.into_secret(); aead_enc_into(ct, k.secret(), &NONCE0, &NOTHING, pt)?; self.mix(ct) } pub fn decrypt_and_mix(&mut self, pt: &mut [u8], ct: &[u8]) -> Result<&mut Self> { let k = self.ck.mix(&lprf::hs_enc()?)?.into_secret(); aead_dec_into(pt, k.secret(), &NONCE0, &NOTHING, ct)?; self.mix(ct) } // I loathe "error: constant expression depends on a generic parameter" pub fn encaps_and_mix( &mut self, ct: &mut [u8], pk: &[u8], ) -> Result<&mut Self> { let mut shk = Secret::::zero(); T::encaps(shk.secret_mut(), ct, pk)?; self.mix(pk)?.mix(shk.secret())?.mix(ct) } pub fn decaps_and_mix( &mut self, sk: &[u8], pk: &[u8], ct: &[u8], ) -> Result<&mut Self> { let mut shk = Secret::::zero(); T::decaps(shk.secret_mut(), sk, ct)?; self.mix(pk)?.mix(shk.secret())?.mix(ct) } pub fn store_biscuit( &mut self, srv: &mut Server, peer: PeerPtr, biscuit_ct: &mut [u8], ) -> Result<&mut Self> { let mut biscuit = Secret::::zero(); // pt buffer let mut biscuit = (&mut biscuit.secret_mut()[..]).biscuit()?; // lens view // calculate pt contents biscuit .pidi_mut() .copy_from_slice(peer.get(srv).pidt()?.as_slice()); biscuit.biscuit_no_mut().copy_from_slice(&*srv.biscuit_ctr); biscuit .ck_mut() .copy_from_slice(self.ck.clone().danger_into_secret().secret()); // calculate ad contents let ad = lprf::biscuit_ad()? .mix(srv.spkm.secret())? .mix(self.sidi.as_slice())? .mix(self.sidr.as_slice())? .into_value(); // consume biscuit no sodium_bigint_inc(&mut *srv.biscuit_ctr); // The first bit of the nonce indicates which biscuit key was used // TODO: This is premature optimiaztion. Remove! let bk = srv.active_biscuit_key(); let mut n = XAEADNonce::random(); n[0] &= 0b0111_1111; n[0] |= (bk.0 as u8 & 0x1) << 7; let k = bk.get(srv).key.secret(); let pt = biscuit.all_bytes(); xaead_enc_into(biscuit_ct, k, &*n, &ad, pt)?; self.mix(biscuit_ct) } /// Takes an encrypted biscuit and tries to decrypt the contained /// information pub fn load_biscuit( srv: &Server, biscuit_ct: &[u8], sidi: SessionId, sidr: SessionId, ) -> Result<(PeerPtr, BiscuitId, HandshakeState)> { // The first bit of the biscuit indicates which biscuit key was used let bk = BiscuitKeyPtr(((biscuit_ct[0] & 0b1000_0000) >> 7) as usize); // Calculate addtional data fields let ad = lprf::biscuit_ad()? .mix(srv.spkm.secret())? .mix(sidi.as_slice())? .mix(sidr.as_slice())? .into_value(); // Allocate and decrypt the biscuit data let mut biscuit = Secret::::zero(); // pt buf let mut biscuit = (&mut biscuit.secret_mut()[..]).biscuit()?; // slice xaead_dec_into( biscuit.all_bytes_mut(), bk.get(srv).key.secret(), &ad, biscuit_ct, )?; // Reconstruct the biscuit fields let no = BiscuitId::from_slice(biscuit.biscuit_no()); let ck = SecretPrfTree::danger_from_secret(Secret::from_slice(biscuit.ck())).dup(); let pid = PeerId::from_slice(biscuit.pidi()); // Reconstruct the handshake state let mut hs = Self { sidi, sidr, ck }; hs.mix(biscuit_ct)?; // Look up the associated peer let peer = srv .find_peer(pid) // TODO: FindPeer should return a Result<()> .with_context(|| format!("Could not decode biscuit for peer {pid:?}: No such peer."))?; // Defense against replay attacks; implementations may accept // the most recent biscuit no again (bn = peer.bn_{prev}) which // indicates retransmission // TODO: Handle retransmissions without involving the crypto code ensure!( sodium_bigint_cmp(biscuit.biscuit_no(), &*peer.get(srv).biscuit_used) >= 0, "Rejecting biscuit: Outdated biscuit number" ); Ok((peer, no, hs)) } pub fn enter_live(self, srv: &Server, role: HandshakeRole) -> Result { let HandshakeState { ck, sidi, sidr } = self; let tki = ck.mix(&lprf::ini_enc()?)?.into_secret(); let tkr = ck.mix(&lprf::res_enc()?)?.into_secret(); let created_at = srv.timebase.now(); let (ntx, nrx) = (0, 0); let (mysid, peersid, ktx, krx) = match role { HandshakeRole::Initiator => (sidi, sidr, tki, tkr), HandshakeRole::Responder => (sidr, sidi, tkr, tki), }; Ok(Session { created_at, sidm: mysid, sidt: peersid, ck, txkm: ktx, txkt: krx, txnm: ntx, txnt: nrx, }) } } impl Server { pub fn osk(&self, peer: PeerPtr) -> Result { let session = peer .session() .get(self) .as_ref() .with_context(|| format!("No current session for peer {:?}", peer))?; Ok(session.ck.mix(&lprf::osk()?)?.into_secret()) } } impl Server { /// Implementation of the cryptographic protocol using the already /// established primitives pub fn handle_initiation( &mut self, peer: PeerPtr, mut ih: InitHello<&mut [u8]>, ) -> Result { let mut hs = InitiatorHandshake::zero_with_timestamp(self); hs.core.init(peer.get(self).spkt.secret())?; // IHI1 hs.core.sidi.randomize(); // IHI2 ih.sidi_mut().copy_from_slice(&hs.core.sidi.value); EKEM::keygen(hs.eski.secret_mut(), &mut *hs.epki)?; // IHI3 ih.epki_mut().copy_from_slice(&hs.epki.value); hs.core.mix(ih.sidi())?.mix(ih.epki())?; // IHI4 hs.core.encaps_and_mix::( // IHI5 ih.sctr_mut(), peer.get(self).spkt.secret(), )?; hs.core // IHI6 .encrypt_and_mix(ih.pidic_mut(), self.pidm()?.as_ref())?; hs.core // IHI7 .mix(self.spkm.secret())? .mix(peer.get(self).psk.secret())?; hs.core.encrypt_and_mix(ih.auth_mut(), &NOTHING)?; // IHI8 // Update the handshake hash last (not changing any state on prior error) peer.hs().insert(self, hs)?; Ok(peer) } pub fn handle_init_hello( &mut self, ih: InitHello<&[u8]>, mut rh: RespHello<&mut [u8]>, ) -> Result { let mut core = HandshakeState::zero(); core.sidi = SessionId::from_slice(ih.sidi()); core.init(self.spkm.secret())?; // IHR1 core.mix(ih.sidi())?.mix(ih.epki())?; // IHR4 core.decaps_and_mix::( // IHR5 self.sskm.secret(), self.spkm.secret(), ih.sctr(), )?; let peer = { // IHR6 let mut peerid = PeerId::zero(); core.decrypt_and_mix(&mut *peerid, ih.pidic())?; self.find_peer(peerid) .with_context(|| format!("No such peer {peerid:?}."))? }; core.mix(peer.get(self).spkt.secret())? // IHR7 .mix(peer.get(self).psk.secret())?; core.decrypt_and_mix(&mut [0u8; 0], ih.auth())?; // IHR8 core.sidr.randomize(); // RHR1 rh.sidi_mut().copy_from_slice(core.sidi.as_ref()); rh.sidr_mut().copy_from_slice(core.sidr.as_ref()); core.mix(rh.sidr())?.mix(rh.sidi())?; // RHR3 core.encaps_and_mix::( // RHR4 rh.ecti_mut(), ih.epki())?; core.encaps_and_mix::( // RHR5 rh.scti_mut(), peer.get(self).spkt.secret(), )?; core.store_biscuit(self, peer, rh.biscuit_mut())?; // RHR6 core.encrypt_and_mix(rh.auth_mut(), &NOTHING)?; // RHR7 Ok(peer) } pub fn handle_resp_hello( &mut self, rh: RespHello<&[u8]>, mut ic: InitConf<&mut [u8]>, ) -> Result { let peer = self // RHI2 .lookup_handshake(SessionId::from_slice(rh.sidi())) .with_context(|| { format!( "Got RespHello packet for non-existent session {:?}", rh.sidi() ) })? .peer(); macro_rules! hs { () => { peer.hs().get(self).as_ref().unwrap() }; } macro_rules! hs_mut { () => { peer.hs().get_mut(self).as_mut().unwrap() }; } // TODO: Is this really necessary? The only possible state is "awaits resp hello"; // no initiation created should be modeled as an Null option and a Session means // we will not be able to find the handshake let exp = hs!().next; let got = HandshakeStateMachine::RespHello; ensure!( exp == got, "Unexpected package in session {:?}. Expected {:?}, got {:?}.", SessionId::from_slice(rh.sidi()), exp, got ); let mut core = hs!().core.clone(); core.sidr.copy_from_slice(rh.sidr()); // TODO: decaps_and_mix should take Secret<> directly // to save us from the repetitive secret unwrapping core.mix(rh.sidr())?.mix(rh.sidi())?; // RHI3 core.decaps_and_mix::( // RHI4 hs!().eski.secret(), &*hs!().epki, rh.ecti(), )?; core.decaps_and_mix::( // RHI5 self.sskm.secret(), self.spkm.secret(), rh.scti(), )?; core.mix(rh.biscuit())?; // RHI6 core.decrypt_and_mix(&mut [0u8; 0], rh.auth())?; // RHI7 // TODO: We should just authenticate the entire network package up to the auth // tag as a pattern instead of mixing in fields separately ic.sidi_mut().copy_from_slice(rh.sidi()); ic.sidr_mut().copy_from_slice(rh.sidr()); core.mix(ic.sidi())?.mix(ic.sidr())?; // ICI3 ic.biscuit_mut().copy_from_slice(rh.biscuit()); core.encrypt_and_mix(ic.auth_mut(), &NOTHING)?; // ICI4 // Split() – We move the secrets into the session; we do not // delete the InitiatorHandshake, just clear it's secrets because // we still need it for InitConf message retransmission to function. peer.session() // ICI7 .insert(self, core.enter_live(self, HandshakeRole::Initiator)?)?; hs_mut!().core.erase(); hs_mut!().next = HandshakeStateMachine::RespConf; Ok(peer) } pub fn handle_init_conf( &mut self, ic: InitConf<&[u8]>, mut rc: EmptyData<&mut [u8]>, ) -> Result { // (peer, bn) ← LoadBiscuit(InitConf.biscuit) let (peer, biscuit_no, mut core) = HandshakeState::load_biscuit( // ICR1 self, ic.biscuit(), SessionId::from_slice(ic.sidi()), SessionId::from_slice(ic.sidr()), )?; core.encrypt_and_mix(&mut [0u8; AEAD_TAG_LEN], &NOTHING)?; // ICR2 core.mix(ic.sidi())?.mix(ic.sidr())?; // ICR3 core.decrypt_and_mix(&mut [0u8; 0], ic.auth())?; // ICR4 if sodium_bigint_cmp(&*biscuit_no, &*peer.get(self).biscuit_used) > 0 { // ICR5 peer.get_mut(self).biscuit_used = biscuit_no; // ICR6 peer.session() // ICR7 .insert(self, core.enter_live(self, HandshakeRole::Responder)?)?; } // TODO: Implementing RP should be possible without touching the live session stuff // Send ack – Implementing sending the empty acknowledgement here // instead of a generic PeerPtr::send(&Server, Option<&[u8]>) -> Either // because data transmission is a stub currently. This software is supposed to be used // as a key exchange service feeding a PSK into some classical (i.e. non post quantum) let ses = peer .session() .get_mut(self) .as_mut() .context("Cannot send acknoledgement. No session.")?; rc.sid_mut().copy_from_slice(&ses.sidt.value); rc.ctr_mut().copy_from_slice(&ses.txnm.to_le_bytes()); ses.txnm += 1; // Increment nonce before encryption, just in case an error is raised let n = cat!(AEAD_NONCE_LEN; rc.ctr(), &[0u8; 4]); let k = ses.txkm.secret(); aead_enc_into(rc.auth_mut(), k, &n, &NOTHING, &NOTHING)?; // ct, k, n, ad, pt Ok(peer) } pub fn handle_resp_conf(&mut self, rc: EmptyData<&[u8]>) -> Result { let sid = SessionId::from_slice(rc.sid()); let hs = self .lookup_handshake(sid) .with_context(|| format!("Got RespConf packet for non-existent session {sid:?}"))?; let ses = hs.peer().session(); let exp = hs.get(self).as_ref().map(|h| h.next); let got = Some(HandshakeStateMachine::RespConf); ensure!( exp == got, "Unexpected package in session {:?}. Expected {:?}, got {:?}.", sid, exp, got ); // Validate the message { let s = ses.get_mut(self).as_mut().with_context(|| { format!("Cannot validate EmptyData message. Missing encryption session for {sid:?}") })?; // the unwrap can not fail, because the slice returned by ctr() is // guaranteed to have the correct size let n = u64::from_le_bytes(rc.ctr().try_into().unwrap()); ensure!(n >= s.txnt, "Stale nonce"); s.txnt = n; aead_dec_into( // pt, k, n, ad, ct &mut [0u8; 0], s.txkt.secret(), &cat!(AEAD_NONCE_LEN; rc.ctr(), &[0u8; 4]), &NOTHING, rc.auth(), )?; } // We can now stop retransmitting RespConf hs.take(self); Ok(hs.peer()) } }