From 2d2d109246e4d2030a18cd8e0431e750c9f08904 Mon Sep 17 00:00:00 2001 From: David Niehues <7667041+DavidNiehues@users.noreply.github.com> Date: Fri, 14 Feb 2025 16:29:56 +0100 Subject: [PATCH] dev(rosenpass): add support for the shake256 hash function in the rosenpass crate --- rosenpass/benches/handshake.rs | 26 +- rosenpass/src/app_server.rs | 6 +- rosenpass/src/bin/gen-ipc-msg-types.rs | 61 +-- rosenpass/src/cli.rs | 1 + rosenpass/src/config.rs | 12 + rosenpass/src/hash_domains.rs | 39 +- rosenpass/src/protocol/build_crypto_server.rs | 30 +- rosenpass/src/protocol/mod.rs | 5 +- rosenpass/src/protocol/protocol.rs | 353 +++++++++++++----- .../tests/api-integration-tests-api-setup.rs | 15 +- rosenpass/tests/api-integration-tests.rs | 14 +- rosenpass/tests/app_server_example.rs | 14 +- rosenpass/tests/integration_test.rs | 2 +- rosenpass/tests/poll_example.rs | 41 +- 14 files changed, 435 insertions(+), 184 deletions(-) diff --git a/rosenpass/benches/handshake.rs b/rosenpass/benches/handshake.rs index bcb9899..28ca235 100644 --- a/rosenpass/benches/handshake.rs +++ b/rosenpass/benches/handshake.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use rosenpass::protocol::{CryptoServer, HandleMsgResult, MsgBuf, PeerPtr, SPk, SSk, SymKey}; +use rosenpass::protocol::{CryptoServer, HandleMsgResult, MsgBuf, PeerPtr, ProtocolVersion, SPk, SSk, SymKey}; use std::ops::DerefMut; use rosenpass_cipher_traits::Kem; @@ -45,21 +45,30 @@ fn keygen() -> Result<(SSk, SPk)> { Ok((sk, pk)) } -fn make_server_pair() -> Result<(CryptoServer, CryptoServer)> { +fn make_server_pair(protocol_version: ProtocolVersion) -> Result<(CryptoServer, CryptoServer)> { let psk = SymKey::random(); let ((ska, pka), (skb, pkb)) = (keygen()?, keygen()?); let (mut a, mut b) = ( CryptoServer::new(ska, pka.clone()), CryptoServer::new(skb, pkb.clone()), ); - a.add_peer(Some(psk.clone()), pkb)?; - b.add_peer(Some(psk), pka)?; + // TODO: Adapt this to both protocol versions + a.add_peer(Some(psk.clone()), pkb, protocol_version.clone())?; + b.add_peer(Some(psk), pka, protocol_version)?; Ok((a, b)) } -fn criterion_benchmark(c: &mut Criterion) { +fn criterion_benchmark_v02(c: &mut Criterion) { + criterion_benchmark(c, ProtocolVersion::V02) +} + +fn criterion_benchmark_v03(c: &mut Criterion) { + criterion_benchmark(c, ProtocolVersion::V03) +} + +fn criterion_benchmark(c: &mut Criterion, protocol_version: ProtocolVersion) { secret_policy_try_use_memfd_secrets(); - let (mut a, mut b) = make_server_pair().unwrap(); + let (mut a, mut b) = make_server_pair(protocol_version).unwrap(); c.bench_function("cca_secret_alloc", |bench| { bench.iter(|| { SSk::zero(); @@ -82,5 +91,6 @@ fn criterion_benchmark(c: &mut Criterion) { }); } -criterion_group!(benches, criterion_benchmark); -criterion_main!(benches); +criterion_group!(benches_v02, criterion_benchmark_v02); +criterion_group!(benches_v03, criterion_benchmark_v03); +criterion_main!(benches_v02, benches_v03); diff --git a/rosenpass/src/app_server.rs b/rosenpass/src/app_server.rs index 75798b8..39e042b 100644 --- a/rosenpass/src/app_server.rs +++ b/rosenpass/src/app_server.rs @@ -50,6 +50,7 @@ use crate::{ }; use rosenpass_util::attempt; use rosenpass_util::b64::B64Display; +use crate::config::ProtocolVersion; /// The maximum size of a base64 encoded symmetric key (estimate) pub const MAX_B64_KEY_SIZE: usize = 32 * 5 / 3; @@ -1042,11 +1043,12 @@ impl AppServer { outfile: Option, broker_peer: Option, hostname: Option, + protocol_version: ProtocolVersion ) -> anyhow::Result { let PeerPtr(pn) = match &mut self.crypto_site { ConstructionSite::Void => bail!("Crypto server construction site is void"), - ConstructionSite::Builder(builder) => builder.add_peer(psk, pk), - ConstructionSite::Product(srv) => srv.add_peer(psk, pk)?, + ConstructionSite::Builder(builder) => builder.add_peer(psk, pk, protocol_version), + ConstructionSite::Product(srv) => srv.add_peer(psk, pk, protocol_version.into())?, }; assert!(pn == self.peers.len()); diff --git a/rosenpass/src/bin/gen-ipc-msg-types.rs b/rosenpass/src/bin/gen-ipc-msg-types.rs index 028113b..b85e059 100644 --- a/rosenpass/src/bin/gen-ipc-msg-types.rs +++ b/rosenpass/src/bin/gen-ipc-msg-types.rs @@ -2,6 +2,9 @@ use anyhow::{Context, Result}; use heck::ToShoutySnakeCase; use rosenpass_ciphers::{hash_domain::HashDomain, KEY_LEN}; +use rosenpass_ciphers::subtle::either_hash::EitherShakeOrBlake; +use rosenpass_ciphers::subtle::incorrect_hmac_blake2b::Blake2bCore; +use rosenpass_ciphers::subtle::keyed_shake256::SHAKE256Core; /// Recursively calculate a concrete hash value for an API message type fn calculate_hash_value(hd: HashDomain, values: &[&str]) -> Result<[u8; KEY_LEN]> { @@ -12,8 +15,8 @@ fn calculate_hash_value(hd: HashDomain, values: &[&str]) -> Result<[u8; KEY_LEN] } /// Print a hash literal for pasting into the Rosenpass source code -fn print_literal(path: &[&str]) -> Result<()> { - let val = calculate_hash_value(HashDomain::zero(), path)?; +fn print_literal(path: &[&str], shake_or_blake: EitherShakeOrBlake) -> Result<()> { + let val = calculate_hash_value(HashDomain::zero(shake_or_blake), path)?; let (last, prefix) = path.split_last().context("developer error!")?; let var_name = last.to_shouty_snake_case(); @@ -51,47 +54,53 @@ impl Tree { } } - fn gen_code_inner(&self, prefix: &[&str]) -> Result<()> { + fn gen_code_inner(&self, prefix: &[&str], shake_or_blake: EitherShakeOrBlake) -> Result<()> { let mut path = prefix.to_owned(); path.push(self.name()); match self { Self::Branch(_, ref children) => { for c in children.iter() { - c.gen_code_inner(&path)? + c.gen_code_inner(&path, shake_or_blake.clone())? } } - Self::Leaf(_) => print_literal(&path)?, + Self::Leaf(_) => print_literal(&path, shake_or_blake)?, }; Ok(()) } - fn gen_code(&self) -> Result<()> { - self.gen_code_inner(&[]) + fn gen_code(&self, shake_or_blake: EitherShakeOrBlake) -> Result<()> { + self.gen_code_inner(&[], shake_or_blake) } } /// Helper for generating hash-based message IDs for the IPC API fn main() -> Result<()> { - let tree = Tree::Branch( - "Rosenpass IPC API".to_owned(), - vec![Tree::Branch( - "Rosenpass Protocol Server".to_owned(), - vec![ - Tree::Leaf("Ping Request".to_owned()), - Tree::Leaf("Ping Response".to_owned()), - Tree::Leaf("Supply Keypair Request".to_owned()), - Tree::Leaf("Supply Keypair Response".to_owned()), - Tree::Leaf("Add Listen Socket Request".to_owned()), - Tree::Leaf("Add Listen Socket Response".to_owned()), - Tree::Leaf("Add Psk Broker Request".to_owned()), - Tree::Leaf("Add Psk Broker Response".to_owned()), - ], - )], - ); + + fn print_IPC_API_info(shake_or_blake: EitherShakeOrBlake, name: String) -> Result<()> { + let tree = Tree::Branch( + format!("Rosenpass IPC API {}", name).to_owned(), + vec![Tree::Branch( + "Rosenpass Protocol Server".to_owned(), + vec![ + Tree::Leaf("Ping Request".to_owned()), + Tree::Leaf("Ping Response".to_owned()), + Tree::Leaf("Supply Keypair Request".to_owned()), + Tree::Leaf("Supply Keypair Response".to_owned()), + Tree::Leaf("Add Listen Socket Request".to_owned()), + Tree::Leaf("Add Listen Socket Response".to_owned()), + Tree::Leaf("Add Psk Broker Request".to_owned()), + Tree::Leaf("Add Psk Broker Response".to_owned()), + ], + )], + ); - println!("type RawMsgType = u128;"); - println!(); - tree.gen_code() + println!("type RawMsgType = u128;"); + println!(); + tree.gen_code(shake_or_blake) + } + + print_IPC_API_info(EitherShakeOrBlake::Left(SHAKE256Core), " (SHAKE256)".to_owned())?; + print_IPC_API_info(EitherShakeOrBlake::Right(Blake2bCore), " (Blake2b)".to_owned()) } diff --git a/rosenpass/src/cli.rs b/rosenpass/src/cli.rs index 870045f..68e8e41 100644 --- a/rosenpass/src/cli.rs +++ b/rosenpass/src/cli.rs @@ -490,6 +490,7 @@ impl CliArgs { cfg_peer.key_out, broker_peer, cfg_peer.endpoint.clone(), + cfg_peer.protocol_version.into(), )?; } diff --git a/rosenpass/src/config.rs b/rosenpass/src/config.rs index fedddeb..82397d2 100644 --- a/rosenpass/src/config.rs +++ b/rosenpass/src/config.rs @@ -109,6 +109,14 @@ pub enum Verbosity { Verbose, } +/// TODO: Documentation +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Copy, Clone, Default)] +pub enum ProtocolVersion { + #[default] + V02, + V03, +} + /// Configuration data for a single Rosenpass peer #[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct RosenpassPeer { @@ -138,6 +146,10 @@ pub struct RosenpassPeer { /// Information for supplying exchanged keys directly to WireGuard #[serde(flatten)] pub wg: Option, + + #[serde(default)] + /// The protocol version to use for the exchange + pub protocol_version: ProtocolVersion, } /// Information for supplying exchanged keys directly to WireGuard diff --git a/rosenpass/src/hash_domains.rs b/rosenpass/src/hash_domains.rs index b37981b..b9ed261 100644 --- a/rosenpass/src/hash_domains.rs +++ b/rosenpass/src/hash_domains.rs @@ -13,6 +13,8 @@ //! use rosenpass::{hash_domain, hash_domain_ns}; //! use rosenpass::hash_domains::protocol; //! +//! use rosenpass_ciphers::subtle::either_hash::EitherShakeOrBlake; +//! //! // Declaring a custom hash domain //! hash_domain_ns!(protocol, custom_domain, "my custom hash domain label"); //! @@ -26,15 +28,18 @@ //! hash_domain!(domain_separators, sep1, "1"); //! hash_domain!(domain_separators, sep2, "2"); //! +//! // We use the SHAKE256 hash function for this example +//! let hash_choice = EitherShakeOrBlake::Left(rosenpass_ciphers::subtle::keyed_shake256::SHAKE256Core); +//! //! // Generating values under hasher1 with both domain separators -//! let h1 = hasher1()?.mix(b"some data")?.dup(); -//! let h1v1 = h1.mix(&sep1()?)?.mix(b"More data")?.into_value(); -//! let h1v2 = h1.mix(&sep2()?)?.mix(b"More data")?.into_value(); +//! let h1 = hasher1(hash_choice.clone())?.mix(b"some data")?.dup(); +//! let h1v1 = h1.mix(&sep1(hash_choice.clone())?)?.mix(b"More data")?.into_value(); +//! let h1v2 = h1.mix(&sep2(hash_choice.clone())?)?.mix(b"More data")?.into_value(); //! //! // Generating values under hasher2 with both domain separators -//! let h2 = hasher2()?.mix(b"some data")?.dup(); -//! let h2v1 = h2.mix(&sep1()?)?.mix(b"More data")?.into_value(); -//! let h2v2 = h2.mix(&sep2()?)?.mix(b"More data")?.into_value(); +//! let h2 = hasher2(hash_choice.clone())?.mix(b"some data")?.dup(); +//! let h2v1 = h2.mix(&sep1(hash_choice.clone())?)?.mix(b"More data")?.into_value(); +//! let h2v2 = h2.mix(&sep2(hash_choice.clone())?)?.mix(b"More data")?.into_value(); //! //! // All of the domain separators are now different, random strings //! let values = [h1v1, h1v2, h2v1, h2v2]; @@ -49,6 +54,9 @@ use anyhow::Result; use rosenpass_ciphers::hash_domain::HashDomain; +use rosenpass_ciphers::subtle::either_hash::EitherShakeOrBlake; +use rosenpass_ciphers::subtle::incorrect_hmac_blake2b::Blake2bCore; +use rosenpass_ciphers::subtle::keyed_shake256::SHAKE256Core; /// Declare a hash function /// @@ -62,8 +70,8 @@ use rosenpass_ciphers::hash_domain::HashDomain; macro_rules! hash_domain_ns { ($(#[$($attrss:tt)*])* $base:ident, $name:ident, $($lbl:expr),+ ) => { $(#[$($attrss)*])* - pub fn $name() -> ::anyhow::Result<::rosenpass_ciphers::hash_domain::HashDomain> { - let t = $base()?; + pub fn $name(hash_choice: EitherShakeOrBlake) -> ::anyhow::Result<::rosenpass_ciphers::hash_domain::HashDomain> { + let t = $base(hash_choice)?; $( let t = t.mix($lbl.as_bytes())?; )* Ok(t) } @@ -81,8 +89,8 @@ macro_rules! hash_domain_ns { macro_rules! hash_domain { ($(#[$($attrss:tt)*])* $base:ident, $name:ident, $($lbl:expr),+ ) => { $(#[$($attrss)*])* - pub fn $name() -> ::anyhow::Result<[u8; ::rosenpass_ciphers::KEY_LEN]> { - let t = $base()?; + pub fn $name(hash_choice: EitherShakeOrBlake) -> ::anyhow::Result<[u8; ::rosenpass_ciphers::KEY_LEN]> { + let t = $base(hash_choice)?; $( let t = t.mix($lbl.as_bytes())?; )* Ok(t.into_value()) } @@ -95,14 +103,21 @@ macro_rules! hash_domain { /// used in various places in the rosenpass protocol. /// /// This is generally used to create further hash-domains for specific purposes. See +/// +/// TODO: Update documentation /// /// # Examples /// /// See the source file for details about how this is used concretely. /// /// See the [module](self) documentation on how to use the hash domains in general -pub fn protocol() -> Result { - HashDomain::zero().mix("Rosenpass v1 mceliece460896 Kyber512 ChaChaPoly1305 BLAKE2s".as_bytes()) +pub fn protocol(hash_choice: EitherShakeOrBlake) -> Result { + // TODO: Update this string that is mixed in? + match hash_choice { + EitherShakeOrBlake::Left(SHAKE256Core) => HashDomain::zero(hash_choice).mix("Rosenpass v1 mceliece460896 Kyber512 ChaChaPoly1305 SHAKE256".as_bytes()), + EitherShakeOrBlake::Right(Blake2bCore) => HashDomain::zero(hash_choice).mix("Rosenpass v1 mceliece460896 Kyber512 ChaChaPoly1305 Blake2b".as_bytes()), + } + } hash_domain_ns!( diff --git a/rosenpass/src/protocol/build_crypto_server.rs b/rosenpass/src/protocol/build_crypto_server.rs index fd4cce9..835cfc1 100644 --- a/rosenpass/src/protocol/build_crypto_server.rs +++ b/rosenpass/src/protocol/build_crypto_server.rs @@ -4,7 +4,7 @@ use rosenpass_util::{ result::ensure_or, }; use thiserror::Error; - +use crate::config::ProtocolVersion; use super::{CryptoServer, PeerPtr, SPk, SSk, SymKey}; #[derive(Debug, Clone)] @@ -146,17 +146,18 @@ pub struct MissingKeypair; /// ```rust /// use rosenpass_util::build::Build; /// use rosenpass::protocol::{BuildCryptoServer, Keypair, PeerParams, SPk, SymKey}; +/// use rosenpass::config::ProtocolVersion; /// /// // We have to define the security policy before using Secrets. /// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets; /// secret_policy_use_only_malloc_secrets(); /// /// let keypair = Keypair::random(); -/// let peer1 = PeerParams { psk: Some(SymKey::random()), pk: SPk::random() }; -/// let peer2 = PeerParams { psk: None, pk: SPk::random() }; +/// let peer1 = PeerParams { psk: Some(SymKey::random()), pk: SPk::random(), protocol_version: ProtocolVersion::V02 }; +/// let peer2 = PeerParams { psk: None, pk: SPk::random(), protocol_version: ProtocolVersion::V02 }; /// /// let mut builder = BuildCryptoServer::new(Some(keypair.clone()), vec![peer1]); -/// builder.add_peer(peer2.psk.clone(), peer2.pk); +/// builder.add_peer(peer2.psk.clone(), peer2.pk, ProtocolVersion::V02); /// /// let server = builder.build().expect("build failed"); /// assert_eq!(server.peers.len(), 2); @@ -186,8 +187,8 @@ impl Build for BuildCryptoServer { let mut srv = CryptoServer::new(sk, pk); - for (idx, PeerParams { psk, pk }) in self.peers.into_iter().enumerate() { - let PeerPtr(idx2) = srv.add_peer(psk, pk)?; + for (idx, PeerParams { psk, pk , protocol_version}) in self.peers.into_iter().enumerate() { + let PeerPtr(idx2) = srv.add_peer(psk, pk, protocol_version.into())?; assert!(idx == idx2, "Peer id changed during CryptoServer construction from {idx} to {idx2}. This is a developer error.") } @@ -208,6 +209,8 @@ pub struct PeerParams { pub psk: Option, /// Public key identifying the peer. pub pk: SPk, + /// The used protocol version. + pub protocol_version: ProtocolVersion, } impl BuildCryptoServer { @@ -305,6 +308,7 @@ impl BuildCryptoServer { /// Adding peers to an existing builder: /// /// ```rust + /// use rosenpass::config::ProtocolVersion; /// // We have to define the security policy before using Secrets. /// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets; /// secret_policy_use_only_malloc_secrets(); @@ -323,7 +327,7 @@ impl BuildCryptoServer { /// // Now we've found a peer that should be added to the configuration /// let pre_shared_key = SymKey::random(); /// let public_key = SPk::random(); - /// builder.with_added_peer(Some(pre_shared_key.clone()), public_key.clone()); + /// builder.with_added_peer(Some(pre_shared_key.clone()), public_key.clone(), ProtocolVersion::V02); /// /// // New server instances will then start with the peer being registered already /// let server = builder.build().expect("build failed"); @@ -333,16 +337,16 @@ impl BuildCryptoServer { /// assert_eq!(peer.spkt, public_key); /// assert_eq!(peer_psk.secret(), pre_shared_key.secret()); /// ``` - pub fn with_added_peer(&mut self, psk: Option, pk: SPk) -> &mut Self { + pub fn with_added_peer(&mut self, psk: Option, pk: SPk, protocol_version: ProtocolVersion) -> &mut Self { // TODO: Check here already whether peer was already added - self.peers.push(PeerParams { psk, pk }); + self.peers.push(PeerParams { psk, pk, protocol_version }); self } /// Add a new entry to the list of registered peers, with or without a pre-shared key. - pub fn add_peer(&mut self, psk: Option, pk: SPk) -> PeerPtr { + pub fn add_peer(&mut self, psk: Option, pk: SPk, protocol_version: ProtocolVersion) -> PeerPtr { let id = PeerPtr(self.peers.len()); - self.with_added_peer(psk, pk); + self.with_added_peer(psk, pk, protocol_version); id } @@ -356,6 +360,8 @@ impl BuildCryptoServer { /// /// ```rust /// // We have to define the security policy before using Secrets. + /// use rosenpass::config::ProtocolVersion; + /// use rosenpass::hash_domains::protocol; /// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets; /// secret_policy_use_only_malloc_secrets(); /// @@ -365,7 +371,7 @@ impl BuildCryptoServer { /// let keypair = Keypair::random(); /// let peer_pk = SPk::random(); /// let mut builder = BuildCryptoServer::new(Some(keypair.clone()), vec![]); - /// builder.add_peer(None, peer_pk); + /// builder.add_peer(None, peer_pk, ProtocolVersion::V02); /// /// // Extract configuration parameters from the decomissioned builder /// let (keypair_option, peers) = builder.take_parts(); diff --git a/rosenpass/src/protocol/mod.rs b/rosenpass/src/protocol/mod.rs index e807930..9d50108 100644 --- a/rosenpass/src/protocol/mod.rs +++ b/rosenpass/src/protocol/mod.rs @@ -33,6 +33,7 @@ //! # fn main() -> anyhow::Result<()> { //! // Set security policy for storing secrets //! +//! use rosenpass::protocol::ProtocolVersion; //! secret_policy_try_use_memfd_secrets(); //! //! // initialize secret and public key for peer a ... @@ -49,8 +50,8 @@ //! let mut b = CryptoServer::new(peer_b_sk, peer_b_pk.clone()); //! //! // introduce peers to each other -//! a.add_peer(Some(psk.clone()), peer_b_pk)?; -//! b.add_peer(Some(psk), peer_a_pk)?; +//! a.add_peer(Some(psk.clone()), peer_b_pk, ProtocolVersion::V03)?; +//! b.add_peer(Some(psk), peer_a_pk, ProtocolVersion::V03)?; //! //! // declare buffers for message exchange //! let (mut a_buf, mut b_buf) = (MsgBuf::zero(), MsgBuf::zero()); diff --git a/rosenpass/src/protocol/protocol.rs b/rosenpass/src/protocol/protocol.rs index 52e4844..7a3680c 100644 --- a/rosenpass/src/protocol/protocol.rs +++ b/rosenpass/src/protocol/protocol.rs @@ -33,7 +33,9 @@ use rosenpass_util::functional::ApplyExt; use rosenpass_util::mem::DiscardResultExt; use rosenpass_util::{cat, mem::cpy_min, time::Timebase}; use zerocopy::{AsBytes, FromBytes, Ref}; - +use rosenpass_ciphers::subtle::either_hash::{EitherShakeOrBlake}; +use rosenpass_ciphers::subtle::incorrect_hmac_blake2b::Blake2bCore; +use rosenpass_ciphers::subtle::keyed_shake256::SHAKE256Core; use crate::{hash_domains, msgs::*, RosenpassError}; // CONSTANTS & SETTINGS ////////////////////////// @@ -364,6 +366,30 @@ pub enum IndexKey { KnownInitConfResponse(KnownResponseHash), } +#[derive(Debug, Clone)] +pub enum ProtocolVersion { + V02, + V03 +} + +impl ProtocolVersion { + pub fn shake_or_blake(&self) -> EitherShakeOrBlake { + match self { + ProtocolVersion::V02 => EitherShakeOrBlake::Right(Blake2bCore), + ProtocolVersion::V03 => EitherShakeOrBlake::Left(SHAKE256Core), + } + } +} + +impl From for ProtocolVersion { + fn from(v: crate::config::ProtocolVersion) -> Self { + match v { + crate::config::ProtocolVersion::V02 => ProtocolVersion::V02, + crate::config::ProtocolVersion::V03 => ProtocolVersion::V03, + } + } +} + /// A peer that the server can execute a key exchange with. /// /// Peers generally live in [CryptoServer::peers]. [PeerNo] captures an array @@ -375,7 +401,7 @@ pub enum IndexKey { /// /// ``` /// use std::ops::DerefMut; -/// use rosenpass::protocol::{SSk, SPk, SymKey, Peer}; +/// use rosenpass::protocol::{SSk, SPk, SymKey, Peer, ProtocolVersion}; /// use rosenpass_ciphers::kem::StaticKem; /// use rosenpass_cipher_traits::Kem; /// @@ -390,13 +416,13 @@ pub enum IndexKey { /// let psk = SymKey::random(); /// /// // Creation with a PSK -/// let peer_psk = Peer::new(psk, spkt.clone()); +/// let peer_psk = Peer::new(psk, spkt.clone(), ProtocolVersion::V03); /// /// // Creation without a PSK -/// let peer_nopsk = Peer::new(SymKey::zero(), spkt); +/// let peer_nopsk = Peer::new(SymKey::zero(), spkt, ProtocolVersion::V03); /// /// // Create a second peer -/// let peer_psk_2 = Peer::new(SymKey::zero(), spkt2); +/// let peer_psk_2 = Peer::new(SymKey::zero(), spkt2, ProtocolVersion::V03); /// /// // Peer ID does not depend on PSK, but it does depend on the public key /// assert_eq!(peer_psk.pidt()?, peer_nopsk.pidt()?); @@ -452,6 +478,9 @@ pub struct Peer { /// This allows us to perform retransmission for the purpose of dealing with packet loss /// on the network without having to account for it in the cryptographic code itself. pub known_init_conf_response: Option, + + /// TODO: Documentation + pub protocol_version: ProtocolVersion, } impl Peer { @@ -460,14 +489,14 @@ impl Peer { /// This is dirty but allows us to perform easy incremental construction of [Self]. /// /// ``` - /// use rosenpass::protocol::{Peer, SymKey, SPk}; + /// use rosenpass::protocol::{Peer, SymKey, SPk, ProtocolVersion}; /// rosenpass_secret_memory::secret_policy_try_use_memfd_secrets(); - /// let p = Peer::zero(); + /// let p = Peer::zero(ProtocolVersion::V03); /// assert_eq!(p.psk.secret(), SymKey::zero().secret()); /// assert_eq!(p.spkt, SPk::zero()); /// // etc. /// ``` - pub fn zero() -> Self { + pub fn zero(protocol_version: ProtocolVersion) -> Self { Self { psk: SymKey::zero(), spkt: SPk::zero(), @@ -476,6 +505,7 @@ impl Peer { initiation_requested: false, handshake: None, known_init_conf_response: None, + protocol_version: protocol_version, } } } @@ -625,7 +655,7 @@ fn known_response_format() { response: Envelope::new_zeroed(), }; let s = format!("{v:?}"); - assert!(s.contains("response")); // Smoke test only, its a formatter + assert!(s.contains("response")); // Smoke test only, it's a formatter } /// Known [EmptyData] response to an [InitConf] message @@ -804,11 +834,11 @@ pub trait Mortal { /// ``` /// use std::ops::DerefMut; /// use rosenpass_ciphers::kem::StaticKem; -/// use rosenpass::protocol::{SSk, SPk, testutils::ServerForTesting}; +/// use rosenpass::protocol::{SSk, SPk, testutils::ServerForTesting, ProtocolVersion}; /// /// rosenpass_secret_memory::secret_policy_try_use_memfd_secrets(); /// -/// let (peer, (_, spkt), mut srv) = ServerForTesting::new()?.tuple(); +/// let (peer, (_, spkt), mut srv) = ServerForTesting::new(ProtocolVersion::V03)?.tuple(); /// /// // Immutable access /// assert_eq!(peer.get(&srv).spkt, spkt); @@ -1344,7 +1374,7 @@ impl CryptoServer { /// /// ``` /// use std::ops::DerefMut; - /// use rosenpass::protocol::{SSk, SPk, CryptoServer}; + /// use rosenpass::protocol::{SSk, SPk, CryptoServer, ProtocolVersion}; /// use rosenpass_ciphers::kem::StaticKem; /// use rosenpass_cipher_traits::Kem; /// @@ -1388,9 +1418,9 @@ impl CryptoServer { /// Calculate the peer ID of this CryptoServer #[rustfmt::skip] - pub fn pidm(&self) -> Result { + pub fn pidm(&self, shake_or_blake: EitherShakeOrBlake) -> Result { Ok(Public::new( - hash_domains::peerid()? + hash_domains::peerid(shake_or_blake)? .mix(self.spkm.deref())? .into_value())) } @@ -1404,10 +1434,12 @@ impl CryptoServer { } /// Add a peer with an optional pre shared key (`psk`) and its public key (`pk`) + /// + /// TODO: Adapt documentation /// /// ``` /// use std::ops::DerefMut; - /// use rosenpass::protocol::{SSk, SPk, SymKey, CryptoServer}; + /// use rosenpass::protocol::{SSk, SPk, SymKey, CryptoServer, ProtocolVersion}; /// use rosenpass_ciphers::kem::StaticKem; /// use rosenpass_cipher_traits::Kem; /// @@ -1422,13 +1454,13 @@ impl CryptoServer { /// /// let psk = SymKey::random(); /// - /// let peer = srv.add_peer(Some(psk), spkt.clone())?; + /// let peer = srv.add_peer(Some(psk), spkt.clone(), ProtocolVersion::V03)?; /// /// assert_eq!(peer.get(&srv).spkt, spkt); /// /// Ok::<(), anyhow::Error>(()) /// ``` - pub fn add_peer(&mut self, psk: Option, pk: SPk) -> Result { + pub fn add_peer(&mut self, psk: Option, pk: SPk, protocol_version: ProtocolVersion) -> Result { let peer = Peer { psk: psk.unwrap_or_else(SymKey::zero), spkt: pk, @@ -1437,6 +1469,7 @@ impl CryptoServer { handshake: None, known_init_conf_response: None, initiation_requested: false, + protocol_version: protocol_version, }; let peerid = peer.pidt()?; let peerno = self.peers.len(); @@ -1549,7 +1582,7 @@ impl CryptoServer { /// Retrieve the active biscuit key, cycling biscuit keys if necessary. /// /// Two biscuit keys are maintained inside [Self::biscuit_keys]; they are - /// considered fresh ([Lifecycle::Young]) for one [BISCUIT_EPOCH] after creation + /// considered fresh ([Lifecycle::Young]) for one [BISCUIT_EPOCH] after creation, /// and they are considered stale ([Lifecycle::Retired]) for another [BISCUIT_EPOCH]. /// /// While young, they are used for encryption of biscuits and while retired they are @@ -1624,7 +1657,7 @@ impl Peer { /// # Examples /// /// See example in [Self]. - pub fn new(psk: SymKey, pk: SPk) -> Peer { + pub fn new(psk: SymKey, pk: SPk, protocol_version: ProtocolVersion) -> Peer { Peer { psk, spkt: pk, @@ -1633,11 +1666,12 @@ impl Peer { handshake: None, known_init_conf_response: None, initiation_requested: false, + protocol_version: protocol_version, } } /// Compute the peer ID of the peer, - /// as specified in the the [whitepaper](https://rosenpass.eu/whitepaper.pdf). + /// as specified in the [whitepaper](https://rosenpass.eu/whitepaper.pdf). /// /// # Examples /// @@ -1645,7 +1679,7 @@ impl Peer { #[rustfmt::skip] pub fn pidt(&self) -> Result { Ok(Public::new( - hash_domains::peerid()? + hash_domains::peerid(self.protocol_version.shake_or_blake())? .mix(self.spkt.deref())? .into_value())) } @@ -1658,20 +1692,22 @@ impl Session { /// /// ``` /// use rosenpass::protocol::{Session, HandshakeRole}; + /// use rosenpass_ciphers::subtle::either_hash::EitherShakeOrBlake; + /// use rosenpass_ciphers::subtle::keyed_shake256::SHAKE256Core; /// /// rosenpass_secret_memory::secret_policy_try_use_memfd_secrets(); /// - /// let s = Session::zero(); + /// let s = Session::zero(EitherShakeOrBlake::Left(SHAKE256Core)); /// assert_eq!(s.created_at, 0.0); /// assert_eq!(s.handshake_role, HandshakeRole::Initiator); /// ``` - pub fn zero() -> Self { + pub fn zero(shake_or_blake: EitherShakeOrBlake) -> Self { Self { created_at: 0.0, sidm: SessionId::zero(), sidt: SessionId::zero(), handshake_role: HandshakeRole::Initiator, - ck: SecretHashDomain::zero().dup(), + ck: SecretHashDomain::zero(shake_or_blake).dup(), txkm: SymKey::zero(), txkt: SymKey::zero(), txnm: 0, @@ -1886,7 +1922,7 @@ impl Mortal for KnownInitConfResponsePtr { /// # Examples /// /// ``` -/// use rosenpass::protocol::{Timing, Mortal, MortalExt, Lifecycle, CryptoServer}; +/// use rosenpass::protocol::{Timing, Mortal, MortalExt, Lifecycle, CryptoServer, ProtocolVersion}; /// use rosenpass::protocol::testutils::{ServerForTesting, time_travel_forward}; /// /// rosenpass_secret_memory::secret_policy_try_use_memfd_secrets(); @@ -1896,7 +1932,7 @@ impl Mortal for KnownInitConfResponsePtr { /// const D : Timing = 24.0 * H; /// const Y : Timing = 356.0 * D; /// -/// let mut ts = ServerForTesting::new()?; +/// let mut ts = ServerForTesting::new(ProtocolVersion::V03)?; /// /// fn eq_up_to_minute(a: Timing, b: Timing) -> bool { /// (a - b) < M @@ -2136,7 +2172,7 @@ impl CryptoServer { let cookie_secret = cookie_secret.get(self).value.secret(); let mut cookie_value = [0u8; 16]; cookie_value.copy_from_slice( - &hash_domains::cookie_value()? + &hash_domains::cookie_value(EitherShakeOrBlake::Left(SHAKE256Core))? .mix(cookie_secret)? .mix(host_identification.encode())? .into_value()[..16], @@ -2152,7 +2188,7 @@ impl CryptoServer { let msg_in = Ref::<&[u8], Envelope>::new(rx_buf) .ok_or(RosenpassError::BufferSizeMismatch)?; expected.copy_from_slice( - &hash_domains::cookie()? + &hash_domains::cookie(EitherShakeOrBlake::Left(SHAKE256Core))? .mix(&cookie_value)? .mix(&msg_in.as_bytes()[span_of!(Envelope, msg_type..cookie)])? .into_value()[..16], @@ -2189,7 +2225,7 @@ impl CryptoServer { ); let cookie_value = active_cookie_value.unwrap(); - let cookie_key = hash_domains::cookie_key()? + let cookie_key = hash_domains::cookie_key(EitherShakeOrBlake::Left(SHAKE256Core))? .mix(self.spkm.deref())? .into_value(); @@ -2269,24 +2305,43 @@ impl CryptoServer { log::debug!("Rx {:?}, processing", msg_type); + let mut msg_out = truncating_cast_into::>(tx_buf)?; + let peer = match msg_type { Ok(MsgType::InitHello) => { let msg_in: Ref<&[u8], Envelope> = Ref::new(rx_buf).ok_or(RosenpassError::BufferSizeMismatch)?; - ensure!(msg_in.check_seal(self)?, seal_broken); - let mut msg_out = truncating_cast_into::>(tx_buf)?; - let peer = self.handle_init_hello(&msg_in.payload, &mut msg_out.payload)?; + // At this point, we do not know the hash functon used by the peer, thus we try both, + // with a preference for SHAKE256. + let peer_shake256 = self.handle_init_hello(&msg_in.payload, &mut msg_out.payload, EitherShakeOrBlake::Left(SHAKE256Core)); + let (peer, peer_hash_choice) = match peer_shake256 { + Ok(peer ) => (peer, EitherShakeOrBlake::Left(SHAKE256Core)), + Err(_) => { + let peer_blake2b = self.handle_init_hello(&msg_in.payload, &mut msg_out.payload, EitherShakeOrBlake::Right(Blake2bCore)); + match peer_blake2b { + Ok(peer) => (peer, EitherShakeOrBlake::Right(Blake2bCore)), + Err(_) => bail!("No valid hash function found for InitHello") + } + } + }; + // Now, we make sure that the hash function used by the peer is the same as the one + // that is specified in the local configuration. + self.verify_hash_choice_match(peer, peer_hash_choice.clone())?; + + ensure!(msg_in.check_seal(self, peer_hash_choice)?, seal_broken); + len = self.seal_and_commit_msg(peer, MsgType::RespHello, &mut msg_out)?; peer } Ok(MsgType::RespHello) => { let msg_in: Ref<&[u8], Envelope> = Ref::new(rx_buf).ok_or(RosenpassError::BufferSizeMismatch)?; - ensure!(msg_in.check_seal(self)?, seal_broken); let mut msg_out = truncating_cast_into::>(tx_buf)?; let peer = self.handle_resp_hello(&msg_in.payload, &mut msg_out.payload)?; + ensure!(msg_in.check_seal(self, peer.get(self).protocol_version.shake_or_blake())?, seal_broken); + len = self.seal_and_commit_msg(peer, MsgType::InitConf, &mut msg_out)?; peer.hs() .store_msg_for_retransmission(self, &msg_out.as_bytes()[..len])?; @@ -2296,7 +2351,7 @@ impl CryptoServer { Ok(MsgType::InitConf) => { let msg_in: Ref<&[u8], Envelope> = Ref::new(rx_buf).ok_or(RosenpassError::BufferSizeMismatch)?; - ensure!(msg_in.check_seal(self)?, seal_broken); + let mut msg_out = truncating_cast_into::>(tx_buf)?; @@ -2305,6 +2360,7 @@ impl CryptoServer { // Cached response; copy out of cache Some(cached) => { let peer = cached.peer(); + ensure!(msg_in.check_seal(self, peer.get(self).protocol_version.shake_or_blake())?, seal_broken); let cached = cached .get(self) .map(|v| v.response.borrow()) @@ -2316,8 +2372,24 @@ impl CryptoServer { // No cached response, actually call cryptographic handler None => { - let peer = self.handle_init_conf(&msg_in.payload, &mut msg_out.payload)?; - + // At this point, we do not know the hash functon used by the peer, thus we try both, + // with a preference for SHAKE256. + let peer_shake256 = self.handle_init_conf(&msg_in.payload, &mut msg_out.payload, EitherShakeOrBlake::Left(SHAKE256Core)); + let (peer, peer_hash_choice) = match peer_shake256 { + Ok(peer) => (peer, EitherShakeOrBlake::Left(SHAKE256Core)), + Err(_) => { + let peer_blake2b = self.handle_init_conf(&msg_in.payload, &mut msg_out.payload, EitherShakeOrBlake::Right(Blake2bCore)); + match peer_blake2b { + Ok(peer) => (peer, EitherShakeOrBlake::Right(Blake2bCore)), + Err(_) => bail!("No valid hash function found for InitHello") + } + } + }; + // Now, we make sure that the hash function used by the peer is the same as the one + // that is specified in the local configuration. + self.verify_hash_choice_match(peer, peer_hash_choice.clone())?; + ensure!(msg_in.check_seal(self, peer_hash_choice)?, seal_broken); + KnownInitConfResponsePtr::insert_for_request_msg( self, peer, @@ -2336,9 +2408,8 @@ impl CryptoServer { Ok(MsgType::EmptyData) => { let msg_in: Ref<&[u8], Envelope> = Ref::new(rx_buf).ok_or(RosenpassError::BufferSizeMismatch)?; - ensure!(msg_in.check_seal(self)?, seal_broken); - self.handle_resp_conf(&msg_in.payload)? + self.handle_resp_conf(&msg_in, seal_broken.to_string())? } Ok(MsgType::CookieReply) => { let msg_in: Ref<&[u8], CookieReply> = @@ -2357,6 +2428,20 @@ impl CryptoServer { resp: if len == 0 { None } else { Some(len) }, }) } + + /// TODO documentation + fn verify_hash_choice_match(&self, peer: PeerPtr, peer_hash_choice: EitherShakeOrBlake) -> Result<()> { + match peer.get(self).protocol_version.shake_or_blake() { + EitherShakeOrBlake::Left(SHAKE256Core) => match peer_hash_choice { + EitherShakeOrBlake::Left(SHAKE256Core) => Ok(()), + EitherShakeOrBlake::Right(Blake2bCore) => bail!("Hash function mismatch"), + }, + EitherShakeOrBlake::Right(Blake2bCore) => match peer_hash_choice { + EitherShakeOrBlake::Left(SHAKE256Core) => bail!("Hash function mismatch"), + EitherShakeOrBlake::Right(Blake2bCore) => Ok(()), + } + } + } /// This is used to finalize a message in a transmission buffer /// while ensuring that the [Envelope::mac] and [Envelope::cookie] @@ -3096,7 +3181,7 @@ where { /// Internal business logic: Calculate the message authentication code (`mac`) and also append cookie value pub fn seal(&mut self, peer: PeerPtr, srv: &CryptoServer) -> Result<()> { - let mac = hash_domains::mac()? + let mac = hash_domains::mac(peer.get(srv).protocol_version.shake_or_blake())? .mix(peer.get(srv).spkt.deref())? .mix(&self.as_bytes()[span_of!(Self, msg_type..mac)])?; self.mac.copy_from_slice(mac.into_value()[..16].as_ref()); @@ -3109,7 +3194,7 @@ where /// This is called inside [Self::seal] and does not need to be called again separately. pub fn seal_cookie(&mut self, peer: PeerPtr, srv: &CryptoServer) -> Result<()> { if let Some(cookie_key) = &peer.cv().get(srv) { - let cookie = hash_domains::cookie()? + let cookie = hash_domains::cookie(peer.get(srv).protocol_version.shake_or_blake())? .mix(cookie_key.value.secret())? .mix(&self.as_bytes()[span_of!(Self, msg_type..cookie)])?; self.cookie @@ -3124,8 +3209,8 @@ where M: AsBytes + FromBytes, { /// Internal business logic: Check the message authentication code produced by [Self::seal] - pub fn check_seal(&self, srv: &CryptoServer) -> Result { - let expected = hash_domains::mac()? + pub fn check_seal(&self, srv: &CryptoServer, shake_or_blake: EitherShakeOrBlake) -> Result { + let expected = hash_domains::mac(shake_or_blake)? .mix(srv.spkm.deref())? .mix(&self.as_bytes()[span_of!(Self, msg_type..mac)])?; Ok(constant_time::memcmp( @@ -3137,11 +3222,11 @@ where impl InitiatorHandshake { /// Zero initialization of an InitiatorHandshake, with up to date timestamp - pub fn zero_with_timestamp(srv: &CryptoServer) -> Self { + pub fn zero_with_timestamp(srv: &CryptoServer, shake_or_blake: EitherShakeOrBlake) -> Self { InitiatorHandshake { created_at: srv.timebase.now(), next: HandshakeStateMachine::RespHello, - core: HandshakeState::zero(), + core: HandshakeState::zero(shake_or_blake), eski: ESk::zero(), epki: EPk::zero(), tx_at: 0.0, @@ -3156,37 +3241,37 @@ impl InitiatorHandshake { impl HandshakeState { /// Zero initialization of an HandshakeState - pub fn zero() -> Self { + pub fn zero(shake_or_blake: EitherShakeOrBlake) -> Self { Self { sidi: SessionId::zero(), sidr: SessionId::zero(), - ck: SecretHashDomain::zero().dup(), + ck: SecretHashDomain::zero(shake_or_blake).dup(), } } /// Securely erase the chaining key pub fn erase(&mut self) { - self.ck = SecretHashDomain::zero().dup(); + self.ck = SecretHashDomain::zero(self.ck.shake_or_blake().clone()).dup(); } /// Initialize the handshake state with the responder public key and the protocol domain /// separator pub fn init(&mut self, spkr: &[u8]) -> Result<&mut Self> { - self.ck = hash_domains::ckinit()?.turn_secret().mix(spkr)?.dup(); + self.ck = hash_domains::ckinit(self.ck.shake_or_blake().clone())?.turn_secret().mix(spkr)?.dup(); Ok(self) } /// Mix some data into the chaining key. This is used for mixing cryptographic keys and public /// data alike into the chaining key pub fn mix(&mut self, a: &[u8]) -> Result<&mut Self> { - self.ck = self.ck.mix(&hash_domains::mix()?)?.mix(a)?.dup(); + self.ck = self.ck.mix(&hash_domains::mix(self.ck.shake_or_blake().clone())?)?.mix(a)?.dup(); Ok(self) } /// Encrypt some data with a value derived from the current chaining key and mix that data /// into the protocol state. pub fn encrypt_and_mix(&mut self, ct: &mut [u8], pt: &[u8]) -> Result<&mut Self> { - let k = self.ck.mix(&hash_domains::hs_enc()?)?.into_secret(); + let k = self.ck.mix(&hash_domains::hs_enc(self.ck.shake_or_blake().clone())?)?.into_secret(); aead::encrypt(ct, k.secret(), &[0u8; aead::NONCE_LEN], &[], pt)?; self.mix(ct) } @@ -3196,7 +3281,7 @@ impl HandshakeState { /// Makes sure that the same values are mixed into the chaining that where mixed in on the /// sender side. pub fn decrypt_and_mix(&mut self, pt: &mut [u8], ct: &[u8]) -> Result<&mut Self> { - let k = self.ck.mix(&hash_domains::hs_enc()?)?.into_secret(); + let k = self.ck.mix(&hash_domains::hs_enc(self.ck.shake_or_blake().clone())?)?.into_secret(); aead::decrypt(pt, k.secret(), &[0u8; aead::NONCE_LEN], &[], ct)?; self.mix(ct) } @@ -3259,7 +3344,7 @@ impl HandshakeState { .copy_from_slice(self.ck.clone().danger_into_secret().secret()); // calculate ad contents - let ad = hash_domains::biscuit_ad()? + let ad = hash_domains::biscuit_ad(peer.get(srv).protocol_version.shake_or_blake())? .mix(srv.spkm.deref())? .mix(self.sidi.as_slice())? .mix(self.sidr.as_slice())? @@ -3288,12 +3373,13 @@ impl HandshakeState { biscuit_ct: &[u8], sidi: SessionId, sidr: SessionId, + shake_or_blake: EitherShakeOrBlake ) -> 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 additional data fields - let ad = hash_domains::biscuit_ad()? + let ad = hash_domains::biscuit_ad(shake_or_blake)? .mix(srv.spkm.deref())? .mix(sidi.as_slice())? .mix(sidr.as_slice())? @@ -3312,18 +3398,17 @@ impl HandshakeState { // Reconstruct the biscuit fields let no = BiscuitId::from_slice(&biscuit.biscuit_no); - let ck = SecretHashDomain::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."))?; + let ck = SecretHashDomain::danger_from_secret(Secret::from_slice(&biscuit.ck), peer.get(srv).protocol_version.shake_or_blake()).dup(); + // Reconstruct the handshake state + let mut hs = Self { sidi, sidr, ck }; + hs.mix(biscuit_ct)?; + Ok((peer, no, hs)) } @@ -3332,10 +3417,10 @@ impl HandshakeState { /// This called by either party. /// /// `role` indicates whether the local peer was an initiator or responder in the handshake. - pub fn enter_live(self, srv: &CryptoServer, role: HandshakeRole) -> Result { + pub fn enter_live(self, srv: &CryptoServer, role: HandshakeRole, either_shake_or_blake: EitherShakeOrBlake) -> Result { let HandshakeState { ck, sidi, sidr } = self; - let tki = ck.mix(&hash_domains::ini_enc()?)?.into_secret(); - let tkr = ck.mix(&hash_domains::res_enc()?)?.into_secret(); + let tki = ck.mix(&hash_domains::ini_enc(either_shake_or_blake.clone())?)?.into_secret(); + let tkr = ck.mix(&hash_domains::res_enc(either_shake_or_blake)?)?.into_secret(); let created_at = srv.timebase.now(); let (ntx, nrx) = (0, 0); let (mysid, peersid, ktx, krx) = match role { @@ -3373,7 +3458,7 @@ impl CryptoServer { .get(self) .as_ref() .with_context(|| format!("No current session for peer {:?}", peer))?; - Ok(session.ck.mix(&hash_domains::osk()?)?.into_secret()) + Ok(session.ck.mix(&hash_domains::osk(peer.get(self).protocol_version.shake_or_blake())?)?.into_secret()) } } @@ -3381,7 +3466,7 @@ impl CryptoServer { /// Core cryptographic protocol implementation: Kicks of the handshake /// on the initiator side, producing the InitHello message. pub fn handle_initiation(&mut self, peer: PeerPtr, ih: &mut InitHello) -> Result { - let mut hs = InitiatorHandshake::zero_with_timestamp(self); + let mut hs = InitiatorHandshake::zero_with_timestamp(self, peer.get(self).protocol_version.shake_or_blake()); // IHI1 hs.core.init(peer.get(self).spkt.deref())?; @@ -3406,7 +3491,7 @@ impl CryptoServer { // IHI6 hs.core - .encrypt_and_mix(ih.pidic.as_mut_slice(), self.pidm()?.as_ref())?; + .encrypt_and_mix(ih.pidic.as_mut_slice(), self.pidm(peer.get(self).protocol_version.shake_or_blake())?.as_ref())?; // IHI7 hs.core @@ -3424,8 +3509,9 @@ impl CryptoServer { /// Core cryptographic protocol implementation: Parses an [InitHello] message and produces a /// [RespHello] message on the responder side. - pub fn handle_init_hello(&mut self, ih: &InitHello, rh: &mut RespHello) -> Result { - let mut core = HandshakeState::zero(); + /// TODO: Document Hash Functon usage + pub fn handle_init_hello(&mut self, ih: &InitHello, rh: &mut RespHello, shake_or_blake: EitherShakeOrBlake) -> Result { + let mut core = HandshakeState::zero(shake_or_blake); core.sidi = SessionId::from_slice(&ih.sidi); @@ -3570,7 +3656,7 @@ impl CryptoServer { // ICI7 peer.session() - .insert(self, core.enter_live(self, HandshakeRole::Initiator)?)?; + .insert(self, core.enter_live(self, HandshakeRole::Initiator, peer.get(self).protocol_version.shake_or_blake())?)?; hs_mut!().core.erase(); hs_mut!().next = HandshakeStateMachine::RespConf; @@ -3578,11 +3664,12 @@ impl CryptoServer { } /// Core cryptographic protocol implementation: Parses an [InitConf] message and produces an - /// [EmptyData] (responder confimation) message on the responder side. + /// [EmptyData] (responder confirmation) message on the responder side. /// /// This concludes the handshake on the cryptographic level; the [EmptyData] message is just /// an acknowledgement message telling the initiator to stop performing retransmissions. - pub fn handle_init_conf(&mut self, ic: &InitConf, rc: &mut EmptyData) -> Result { + /// TODO: documentation + pub fn handle_init_conf(&mut self, ic: &InitConf, rc: &mut EmptyData, shake_or_blake: EitherShakeOrBlake) -> Result { // (peer, bn) ← LoadBiscuit(InitConf.biscuit) // ICR1 let (peer, biscuit_no, mut core) = HandshakeState::load_biscuit( @@ -3590,6 +3677,7 @@ impl CryptoServer { &ic.biscuit, SessionId::from_slice(&ic.sidi), SessionId::from_slice(&ic.sidr), + shake_or_blake, )?; // ICR2 @@ -3615,7 +3703,7 @@ impl CryptoServer { // ICR7 peer.session() - .insert(self, core.enter_live(self, HandshakeRole::Responder)?)?; + .insert(self, core.enter_live(self, HandshakeRole::Responder, peer.get(self).protocol_version.shake_or_blake())?)?; // TODO: This should be part of the protocol specification. // Abort any ongoing handshake from initiator role peer.hs().take(self); @@ -3663,11 +3751,13 @@ impl CryptoServer { /// message then terminates the handshake. /// /// The EmptyData message is just there to tell the initiator to abort retransmissions. - pub fn handle_resp_conf(&mut self, rc: &EmptyData) -> Result { + pub fn handle_resp_conf(&mut self, msg_in: &Ref<&[u8], Envelope>, seal_broken: String) -> Result { + let rc: &EmptyData = &msg_in.payload; let sid = SessionId::from_slice(&rc.sid); let hs = self .lookup_handshake(sid) .with_context(|| format!("Got RespConf packet for non-existent session {sid:?}"))?; + ensure!(msg_in.check_seal(self, hs.peer().get(self).protocol_version.shake_or_blake())?, seal_broken); let ses = hs.peer().session(); let exp = hs.get(self).as_ref().map(|h| h.next); @@ -3685,7 +3775,7 @@ impl CryptoServer { 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 + // the unwrapping can not fail, because the slice returned by ctr() is // guaranteed to have the correct size let n = u64::from_le_bytes(rc.ctr); ensure!(n >= s.txnt, "Stale nonce"); @@ -3750,7 +3840,7 @@ impl CryptoServer { }?; let spkt = peer.get(self).spkt.deref(); - let cookie_key = hash_domains::cookie_key()?.mix(spkt)?.into_value(); + let cookie_key = hash_domains::cookie_key(EitherShakeOrBlake::Left(SHAKE256Core))?.mix(spkt)?.into_value(); let cookie_value = peer.cv().update_mut(self).unwrap(); xaead::decrypt(cookie_value, &cookie_key, &mac, &cr.inner.cookie_encrypted)?; @@ -3795,15 +3885,16 @@ pub mod testutils { pub srv: CryptoServer, } + /// TODO: Document that the protocol verson s only used for creating the peer for testing impl ServerForTesting { - pub fn new() -> anyhow::Result { + pub fn new(protocol_version: ProtocolVersion) -> anyhow::Result { let (mut sskm, mut spkm) = (SSk::zero(), SPk::zero()); StaticKem::keygen(sskm.secret_mut(), spkm.deref_mut())?; let mut srv = CryptoServer::new(sskm, spkm); let (mut sskt, mut spkt) = (SSk::zero(), SPk::zero()); StaticKem::keygen(sskt.secret_mut(), spkt.deref_mut())?; - let peer = srv.add_peer(None, spkt.clone())?; + let peer = srv.add_peer(None, spkt.clone(), protocol_version)?; let peer_keys = (sskt, spkt); Ok(ServerForTesting { @@ -3868,6 +3959,17 @@ mod test { #[test] #[serial] + fn handles_incorrect_size_messages_v02() { + handles_incorrect_size_messages(ProtocolVersion::V02) + } + + #[test] + #[serial] + fn handles_incorrect_size_messages_v03() { + handles_incorrect_size_messages(ProtocolVersion::V03) + } + + /// Ensure that the protocol implementation can deal with truncated /// messages and with overlong messages. /// @@ -3882,7 +3984,7 @@ mod test { /// /// Through all this, the handshake should still successfully terminate; /// i.e. an exchanged key must be produced in both servers. - fn handles_incorrect_size_messages() { + fn handles_incorrect_size_messages(protocol_version: ProtocolVersion) { setup_logging(); rosenpass_secret_memory::secret_policy_try_use_memfd_secrets(); stacker::grow(8 * 1024 * 1024, || { @@ -3891,7 +3993,7 @@ mod test { const PEER0: PeerPtr = PeerPtr(0); - let (mut me, mut they) = make_server_pair().unwrap(); + let (mut me, mut they) = make_server_pair(protocol_version).unwrap(); let (mut msgbuf, mut resbuf) = (MsgBufPlus::zero(), MsgBufPlus::zero()); // Process the entire handshake @@ -3941,7 +4043,7 @@ mod test { Ok((sk, pk)) } - fn make_server_pair() -> Result<(CryptoServer, CryptoServer)> { + fn make_server_pair(protocol_version: ProtocolVersion) -> Result<(CryptoServer, CryptoServer)> { // TODO: Copied from the benchmark; deduplicate let psk = SymKey::random(); let ((ska, pka), (skb, pkb)) = (keygen()?, keygen()?); @@ -3949,19 +4051,29 @@ mod test { CryptoServer::new(ska, pka.clone()), CryptoServer::new(skb, pkb.clone()), ); - a.add_peer(Some(psk.clone()), pkb)?; - b.add_peer(Some(psk), pka)?; + a.add_peer(Some(psk.clone()), pkb, protocol_version.clone())?; + b.add_peer(Some(psk), pka, protocol_version)?; Ok((a, b)) } #[test] #[serial] - fn test_regular_exchange() { + fn test_regular_exchange_v02() { + test_regular_exchange(ProtocolVersion::V02) + } + + #[test] + #[serial] + fn test_regular_exchange_v03() { + test_regular_exchange(ProtocolVersion::V03) + } + + fn test_regular_exchange(protocol_version: ProtocolVersion) { setup_logging(); rosenpass_secret_memory::secret_policy_try_use_memfd_secrets(); stacker::grow(8 * 1024 * 1024, || { type MsgBufPlus = Public; - let (mut a, mut b) = make_server_pair().unwrap(); + let (mut a, mut b) = make_server_pair(protocol_version).unwrap(); let mut a_to_b_buf = MsgBufPlus::zero(); let mut b_to_a_buf = MsgBufPlus::zero(); @@ -4017,12 +4129,22 @@ mod test { #[test] #[serial] - fn test_regular_init_conf_retransmit() { + fn test_regular_init_conf_retransmit_v02() { + test_regular_init_conf_retransmit(ProtocolVersion::V02) + } + + #[test] + #[serial] + fn test_regular_init_conf_retransmit_v03() { + test_regular_init_conf_retransmit(ProtocolVersion::V03) + } + + fn test_regular_init_conf_retransmit(protocol_version: ProtocolVersion) { setup_logging(); rosenpass_secret_memory::secret_policy_try_use_memfd_secrets(); stacker::grow(8 * 1024 * 1024, || { type MsgBufPlus = Public; - let (mut a, mut b) = make_server_pair().unwrap(); + let (mut a, mut b) = make_server_pair(protocol_version).unwrap(); let mut a_to_b_buf = MsgBufPlus::zero(); let mut b_to_a_buf = MsgBufPlus::zero(); @@ -4092,12 +4214,22 @@ mod test { #[test] #[serial] - fn cookie_reply_mechanism_responder_under_load() { + fn cookie_reply_mechanism_responder_under_load_v02() { + cookie_reply_mechanism_initiator_bails_on_message_under_load(ProtocolVersion::V02) + } + + #[test] + #[serial] + fn cookie_reply_mechanism_responder_under_load_v03() { + cookie_reply_mechanism_initiator_bails_on_message_under_load(ProtocolVersion::V03) + } + + fn cookie_reply_mechanism_responder_under_load(protocol_version: ProtocolVersion) { setup_logging(); rosenpass_secret_memory::secret_policy_try_use_memfd_secrets(); stacker::grow(8 * 1024 * 1024, || { type MsgBufPlus = Public; - let (mut a, mut b) = make_server_pair().unwrap(); + let (mut a, mut b) = make_server_pair(protocol_version.clone()).unwrap(); let mut a_to_b_buf = MsgBufPlus::zero(); let mut b_to_a_buf = MsgBufPlus::zero(); @@ -4136,7 +4268,7 @@ mod test { assert_eq!(PeerPtr(0).cv().lifecycle(&a), Lifecycle::Young); - let expected_cookie_value = hash_domains::cookie_value() + let expected_cookie_value = hash_domains::cookie_value(protocol_version.shake_or_blake()) .unwrap() .mix( b.active_or_retired_cookie_secrets()[0] @@ -4189,12 +4321,22 @@ mod test { #[test] #[serial] - fn cookie_reply_mechanism_initiator_bails_on_message_under_load() { + fn cookie_reply_mechanism_initiator_bails_on_message_under_load_v02() { + cookie_reply_mechanism_initiator_bails_on_message_under_load(ProtocolVersion::V02) + } + + #[test] + #[serial] + fn cookie_reply_mechanism_initiator_bails_on_message_under_load_v03() { + cookie_reply_mechanism_initiator_bails_on_message_under_load(ProtocolVersion::V03) + } + + fn cookie_reply_mechanism_initiator_bails_on_message_under_load(protocol_version: ProtocolVersion) { setup_logging(); rosenpass_secret_memory::secret_policy_try_use_memfd_secrets(); stacker::grow(8 * 1024 * 1024, || { type MsgBufPlus = Public; - let (mut a, mut b) = make_server_pair().unwrap(); + let (mut a, mut b) = make_server_pair(protocol_version).unwrap(); let mut a_to_b_buf = MsgBufPlus::zero(); let mut b_to_a_buf = MsgBufPlus::zero(); @@ -4249,10 +4391,19 @@ mod test { } #[test] - fn init_conf_retransmission() -> anyhow::Result<()> { + fn init_conf_retransmission_v02() -> Result<()> { + init_conf_retransmission(ProtocolVersion::V02) + } + + #[test] + fn init_conf_retransmission_v03() -> Result<()> { + init_conf_retransmission(ProtocolVersion::V03) + } + + fn init_conf_retransmission(protocol_version: ProtocolVersion) -> anyhow::Result<()> { rosenpass_secret_memory::secret_policy_try_use_memfd_secrets(); - fn keypair() -> anyhow::Result<(SSk, SPk)> { + fn keypair() -> Result<(SSk, SPk)> { let (mut sk, mut pk) = (SSk::zero(), SPk::zero()); StaticKem::keygen(sk.secret_mut(), pk.deref_mut())?; Ok((sk, pk)) @@ -4261,7 +4412,7 @@ mod test { fn proc_initiation( srv: &mut CryptoServer, peer: PeerPtr, - ) -> anyhow::Result> { + ) -> Result> { let mut buf = MsgBuf::zero(); srv.initiate_handshake(peer, buf.as_mut_slice())? .discard_result(); @@ -4331,12 +4482,8 @@ mod test { assert!(res.is_err()); } - fn check_retransmission( - srv: &mut CryptoServer, - ic: &Envelope, - ic_broken: &Envelope, - rc: &Envelope, - ) -> anyhow::Result<()> { + // we this as a closure in orer to use the protocol_version variable in it. + let check_retransmission = |srv: &mut CryptoServer, ic: &Envelope, ic_broken: &Envelope, rc: &Envelope| -> Result<()> { // Processing the same RespHello package again leads to retransmission (i.e. exactly the // same output) let rc_dup = proc_init_conf(srv, ic)?; @@ -4345,14 +4492,14 @@ mod test { // Though if we directly call handle_resp_hello() we get an error since // retransmission is not being handled by the cryptographic code let mut discard_resp_conf = EmptyData::new_zeroed(); - let res = srv.handle_init_conf(&ic.payload, &mut discard_resp_conf); + let res = srv.handle_init_conf(&ic.payload, &mut discard_resp_conf, protocol_version.clone().shake_or_blake()); assert!(res.is_err()); // Obviously, a broken InitConf message should still be rejected check_faulty_proc_init_conf(srv, ic_broken); Ok(()) - } + }; let (ska, pka) = keypair()?; let (skb, pkb) = keypair()?; @@ -4362,8 +4509,8 @@ mod test { let mut b = CryptoServer::new(skb, pkb.clone()); // introduce peers to each other - let b_peer = a.add_peer(None, pkb)?; - let a_peer = b.add_peer(None, pka)?; + let b_peer = a.add_peer(None, pkb, protocol_version.clone())?; + let a_peer = b.add_peer(None, pka, protocol_version.clone())?; // Execute protocol up till the responder confirmation (EmptyData) let ih1 = proc_initiation(&mut a, b_peer)?; diff --git a/rosenpass/tests/api-integration-tests-api-setup.rs b/rosenpass/tests/api-integration-tests-api-setup.rs index bb5ff8c..e70e77a 100644 --- a/rosenpass/tests/api-integration-tests-api-setup.rs +++ b/rosenpass/tests/api-integration-tests-api-setup.rs @@ -26,7 +26,7 @@ use rosenpass_util::{ use std::os::fd::{AsFd, AsRawFd}; use tempfile::TempDir; use zerocopy::AsBytes; - +use rosenpass::config::ProtocolVersion; use rosenpass::protocol::SymKey; struct KillChild(std::process::Child); @@ -48,7 +48,16 @@ impl Drop for KillChild { } #[test] -fn api_integration_api_setup() -> anyhow::Result<()> { +fn api_integration_api_setup_v02() -> anyhow::Result<()> { + api_integration_api_setup(ProtocolVersion::V02) +} + +#[test] +fn api_integration_api_setup_v03() -> anyhow::Result<()> { + api_integration_api_setup(ProtocolVersion::V03) +} + +fn api_integration_api_setup(protocol_version: ProtocolVersion) -> anyhow::Result<()> { rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets(); let dir = TempDir::with_prefix("rosenpass-api-integration-test")?; @@ -96,6 +105,7 @@ fn api_integration_api_setup() -> anyhow::Result<()> { peer: format!("{}", peer_b_wg_peer_id.fmt_b64::<8129>()), extra_params: vec![], }), + protocol_version: protocol_version.clone(), }], }; @@ -116,6 +126,7 @@ fn api_integration_api_setup() -> anyhow::Result<()> { endpoint: Some(peer_a_endpoint.to_owned()), pre_shared_key: None, wg: None, + protocol_version: protocol_version.clone(), }], }; diff --git a/rosenpass/tests/api-integration-tests.rs b/rosenpass/tests/api-integration-tests.rs index e7e0259..99761e2 100644 --- a/rosenpass/tests/api-integration-tests.rs +++ b/rosenpass/tests/api-integration-tests.rs @@ -17,6 +17,7 @@ use tempfile::TempDir; use zerocopy::AsBytes; use rosenpass::protocol::SymKey; +use rosenpass::config::ProtocolVersion; struct KillChild(std::process::Child); @@ -37,7 +38,16 @@ impl Drop for KillChild { } #[test] -fn api_integration_test() -> anyhow::Result<()> { +fn api_integration_test_v02() -> anyhow::Result<()> { + api_integration_test(ProtocolVersion::V02) +} + +fn api_integration_test_v03() -> anyhow::Result<()> { + api_integration_test(ProtocolVersion::V03) +} + + +fn api_integration_test(protocol_version: ProtocolVersion) -> anyhow::Result<()> { rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets(); let dir = TempDir::with_prefix("rosenpass-api-integration-test")?; @@ -73,6 +83,7 @@ fn api_integration_test() -> anyhow::Result<()> { endpoint: None, pre_shared_key: None, wg: None, + protocol_version: protocol_version.clone(), }], }; @@ -93,6 +104,7 @@ fn api_integration_test() -> anyhow::Result<()> { endpoint: Some(peer_a_endpoint.to_owned()), pre_shared_key: None, wg: None, + protocol_version: protocol_version.clone(), }], }; diff --git a/rosenpass/tests/app_server_example.rs b/rosenpass/tests/app_server_example.rs index 23ac462..294898f 100644 --- a/rosenpass/tests/app_server_example.rs +++ b/rosenpass/tests/app_server_example.rs @@ -13,13 +13,23 @@ use rosenpass::{ app_server::{ipv4_any_binding, ipv6_any_binding, AppServer, AppServerTest, MAX_B64_KEY_SIZE}, protocol::{SPk, SSk, SymKey}, }; +use rosenpass::config::ProtocolVersion; use rosenpass_cipher_traits::Kem; use rosenpass_ciphers::kem::StaticKem; use rosenpass_secret_memory::Secret; use rosenpass_util::{file::LoadValueB64, functional::run, mem::DiscardResultExt, result::OkExt}; #[test] -fn key_exchange_with_app_server() -> anyhow::Result<()> { +fn key_exchange_with_app_server_v02() -> anyhow::Result<()> { + key_exchange_with_app_server(ProtocolVersion::V02) +} + +#[test] +fn key_exchange_with_app_server_v03() -> anyhow::Result<()> { + key_exchange_with_app_server(ProtocolVersion::V03) +} + +fn key_exchange_with_app_server(protocol_version: ProtocolVersion) -> anyhow::Result<()> { let tmpdir = tempfile::tempdir()?; let outfile_a = tmpdir.path().join("osk_a"); let outfile_b = tmpdir.path().join("osk_b"); @@ -57,7 +67,7 @@ fn key_exchange_with_app_server() -> anyhow::Result<()> { let port = otr_port; let hostname = is_client.then(|| format!("[::1]:{port}")); srv.app_srv - .add_peer(psk, pk, outfile, broker_peer, hostname)?; + .add_peer(psk, pk, outfile, broker_peer, hostname, protocol_version.clone())?; srv.app_srv.event_loop() }) diff --git a/rosenpass/tests/integration_test.rs b/rosenpass/tests/integration_test.rs index 67ddc78..b45dbd6 100644 --- a/rosenpass/tests/integration_test.rs +++ b/rosenpass/tests/integration_test.rs @@ -251,7 +251,7 @@ fn check_exchange_under_normal() { fs::remove_dir_all(&tmpdir).unwrap(); } -// check that we can trigger a DoS condition and we can exchange keys under DoS +// check that we can trigger a DoS condition, and we can exchange keys under DoS // This test creates a responder (server) with the feature flag "integration_test_always_under_load" to always be under load condition for the test. #[test] #[serial] diff --git a/rosenpass/tests/poll_example.rs b/rosenpass/tests/poll_example.rs index 56faf79..a7d7eb2 100644 --- a/rosenpass/tests/poll_example.rs +++ b/rosenpass/tests/poll_example.rs @@ -9,20 +9,26 @@ use rosenpass_cipher_traits::Kem; use rosenpass_ciphers::kem::StaticKem; use rosenpass_util::result::OkExt; -use rosenpass::protocol::{ - testutils::time_travel_forward, CryptoServer, HostIdentification, MsgBuf, PeerPtr, PollResult, - SPk, SSk, SymKey, Timing, UNENDING, -}; +use rosenpass::protocol::{testutils::time_travel_forward, CryptoServer, HostIdentification, MsgBuf, PeerPtr, PollResult, ProtocolVersion, SPk, SSk, SymKey, Timing, UNENDING}; // TODO: Most of the utility functions in here should probably be moved to // rosenpass::protocol::testutils; #[test] -fn test_successful_exchange_with_poll() -> anyhow::Result<()> { +fn test_successful_exchange_with_poll_v02() -> anyhow::Result<()> { + test_successful_exchange_with_poll(ProtocolVersion::V02) +} + +#[test] +fn test_successful_exchange_with_poll_v03() -> anyhow::Result<()> { + test_successful_exchange_with_poll(ProtocolVersion::V03) +} + +fn test_successful_exchange_with_poll(protocol_version: ProtocolVersion) -> anyhow::Result<()> { // Set security policy for storing secrets; choose the one that is faster for testing rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets(); - let mut sim = RosenpassSimulator::new()?; + let mut sim = RosenpassSimulator::new(protocol_version)?; sim.poll_loop(150)?; // Poll 75 times let transcript = sim.transcript; @@ -79,12 +85,21 @@ fn test_successful_exchange_with_poll() -> anyhow::Result<()> { } #[test] -fn test_successful_exchange_under_packet_loss() -> anyhow::Result<()> { +fn test_successful_exchange_under_packet_loss_v02() -> anyhow::Result<()> { + test_successful_exchange_under_packet_loss(ProtocolVersion::V02) +} + +#[test] +fn test_successful_exchange_under_packet_loss_v03() -> anyhow::Result<()> { + test_successful_exchange_under_packet_loss(ProtocolVersion::V03) +} + +fn test_successful_exchange_under_packet_loss(protocol_version: ProtocolVersion) -> anyhow::Result<()> { // Set security policy for storing secrets; choose the one that is faster for testing rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets(); // Create the simulator - let mut sim = RosenpassSimulator::new()?; + let mut sim = RosenpassSimulator::new(protocol_version)?; // Make sure the servers are set to under load condition sim.srv_a.under_load = true; @@ -272,7 +287,7 @@ struct SimulatorServer { impl RosenpassSimulator { /// Set up the simulator - fn new() -> anyhow::Result { + fn new(protocol_version: ProtocolVersion) -> anyhow::Result { // Set up the first server let (mut peer_a_sk, mut peer_a_pk) = (SSk::zero(), SPk::zero()); StaticKem::keygen(peer_a_sk.secret_mut(), peer_a_pk.deref_mut())?; @@ -285,8 +300,8 @@ impl RosenpassSimulator { // Generate a PSK and introduce the Peers to each other. let psk = SymKey::random(); - let peer_a = srv_a.add_peer(Some(psk.clone()), peer_b_pk)?; - let peer_b = srv_b.add_peer(Some(psk), peer_a_pk)?; + let peer_a = srv_a.add_peer(Some(psk.clone()), peer_b_pk, protocol_version.clone())?; + let peer_b = srv_b.add_peer(Some(psk), peer_a_pk, protocol_version.clone())?; // Set up the individual server data structures let srv_a = SimulatorServer::new(srv_a, peer_b); @@ -314,8 +329,8 @@ impl RosenpassSimulator { Ok(()) } - /// Every call to poll produces one [TranscriptEvent] and - /// and implicitly adds it to [Self:::transcript] + /// Every call to poll produces one [TranscriptEvent] + /// and implicitly adds it to [Self::transcript] fn poll(&mut self) -> anyhow::Result<&TranscriptEvent> { let ev = TranscriptEvent::begin_poll() .try_fold_with(|| self.poll_focus.poll(self))?