From b1a7d942954ce6a41decc5a9f93c95cffafde801 Mon Sep 17 00:00:00 2001 From: Karolin Varner Date: Wed, 25 Jun 2025 19:27:19 +0200 Subject: [PATCH] feat: Support for custom osk (output key) domain separators in Rosenpass app This allows for custom protocol extensions with custom domain separators to be used without modifying the Rosenpass source code --- ciphers/src/hash_domain.rs | 67 ++++++++ papers/whitepaper.md | 22 +++ rosenpass/benches/handshake.rs | 15 +- rosenpass/benches/trace_handshake.rs | 15 +- rosenpass/src/app_server.rs | 11 +- rosenpass/src/cli.rs | 1 + rosenpass/src/config.rs | 72 +++++++++ rosenpass/src/hash_domains.rs | 10 +- rosenpass/src/protocol/basic_types.rs | 2 + rosenpass/src/protocol/build_crypto_server.rs | 50 +++--- rosenpass/src/protocol/mod.rs | 6 +- .../src/protocol/osk_domain_separator.rs | 91 +++++++++++ rosenpass/src/protocol/protocol.rs | 81 ++++++++-- rosenpass/src/protocol/test.rs | 29 +++- rosenpass/src/protocol/testutils.rs | 8 +- .../tests/api-integration-tests-api-setup.rs | 2 + rosenpass/tests/api-integration-tests.rs | 2 + rosenpass/tests/app_server_example.rs | 5 +- rosenpass/tests/poll_example.rs | 150 ++++++++++++++++-- rp/src/exchange.rs | 6 +- util/src/fd.rs | 2 +- util/src/length_prefix_encoding/encoder.rs | 2 +- util/src/mem.rs | 2 +- 23 files changed, 579 insertions(+), 72 deletions(-) create mode 100644 rosenpass/src/protocol/osk_domain_separator.rs diff --git a/ciphers/src/hash_domain.rs b/ciphers/src/hash_domain.rs index 4a56cfb..ad11a4b 100644 --- a/ciphers/src/hash_domain.rs +++ b/ciphers/src/hash_domain.rs @@ -83,6 +83,33 @@ impl HashDomain { Ok(Self(new_key, self.1)) } + /// Version of [Self::mix] that accepts an iterator and mixes all values from the iterator into + /// this hash domain. + /// + /// # Examples + /// + /// ```rust + /// use rosenpass_ciphers::{hash_domain::HashDomain, KeyedHash}; + /// + /// let hasher = HashDomain::zero(KeyedHash::keyed_shake256()); + /// assert_eq!( + /// hasher.clone().mix(b"Hello")?.mix(b"World")?.into_value(), + /// hasher.clone().mix_many([b"Hello", b"World"])?.into_value() + /// ); + /// + /// Ok::<(), anyhow::Error>(()) + /// ``` + pub fn mix_many(mut self, it: I) -> Result + where + I: IntoIterator, + T: AsRef<[u8]>, + { + for e in it { + self = self.mix(e.as_ref())?; + } + Ok(self) + } + /// Creates a new [SecretHashDomain] by mixing in a new key `v` /// by calling [SecretHashDomain::invoke_primitive] with this /// [HashDomain]'s key as `k` and `v` as `d`. @@ -161,6 +188,46 @@ impl SecretHashDomain { Self::invoke_primitive(self.0.secret(), v, self.1) } + /// Version of [Self::mix] that accepts an iterator and mixes all values from the iterator into + /// this hash domain. + /// + /// # Examples + /// + /// ```rust + /// use rosenpass_ciphers::{hash_domain::HashDomain, KeyedHash}; + /// + /// rosenpass_secret_memory::secret_policy_use_only_malloc_secrets(); + /// + /// let hasher = HashDomain::zero(KeyedHash::keyed_shake256()); + /// assert_eq!( + /// hasher + /// .clone() + /// .turn_secret() + /// .mix(b"Hello")? + /// .mix(b"World")? + /// .into_secret() + /// .secret(), + /// hasher + /// .clone() + /// .turn_secret() + /// .mix_many([b"Hello", b"World"])? + /// .into_secret() + /// .secret(), + /// ); + + /// Ok::<(), anyhow::Error>(()) + /// ``` + pub fn mix_many(mut self, it: I) -> Result + where + I: IntoIterator, + T: AsRef<[u8]>, + { + for e in it { + self = self.mix(e.as_ref())?; + } + Ok(self) + } + /// Creates a new [SecretHashDomain] by mixing in a new key `v` /// by calling [SecretHashDomain::invoke_primitive] with the key of this /// [HashDomainNamespace] as `k` and `v` as `d`. diff --git a/papers/whitepaper.md b/papers/whitepaper.md index b41d9a0..0f81f20 100644 --- a/papers/whitepaper.md +++ b/papers/whitepaper.md @@ -548,6 +548,28 @@ When the responder is under load and it recieves an InitConf message, the messag The main extension point for the Rosenpass protocol is to generate `osk`s (speak output shared keys, see Sec. \ref{symmetric-keys}) for purposes other than using them to secure WireGuard. By default, the Rosenpass application generates keys for the WireGuard PSK (see \ref{protocol-extension-wireguard-psk}). It would not be impossible to use the keys generated for WireGuard in other use cases, but this might lead to attacks[@oraclecloning]. Specifying a custom protocol extension in practice just means settling on alternative domain separators (see Sec. \ref{symmetric-keys}, Fig. \ref{img:HashingTree}). +## Using custom domain separators in the Rosenpass application + +The Rosenpass application supports protocol extensions to change the OSK domain separator without modification of the source code. + +The following example configuration file can be used to execute Rosenpass in outfile mode with custom domain separators. +In this mode, the Rosenpass application will write keys to the file specified with `key_out` and send notifications when new keys are exchanged via standard out. +This can be used to embed Rosenpass into third-party application. + +```toml +# peer-a.toml +public_key = "peer-a.pk" +secret_key = "peer-a.sk" +listen = ["[::1]:6789"] +verbosity = "Verbose" + +[[peers]] +public_key = "peer-b.pk" +key_out = "peer-a.osk" # path to store the key +osk_organization = "myorg.com" +osk_label = ["My Custom Messenger app", "Backend VPN Example Subusecase"] +``` + ## Extension: WireGuard PSK {#protocol-extension-wireguard-psk} The WireGuard PSK protocol extension is active by default; this is the mode where Rosenpass is used to provide post-quantum security for WireGuard. Hybrid security (i.e. redundant pre-quantum and post-quantum security) is achieved because WireGuard provides pre-quantum security, with or without Rosenpass. diff --git a/rosenpass/benches/handshake.rs b/rosenpass/benches/handshake.rs index e04c71c..cceafcb 100644 --- a/rosenpass/benches/handshake.rs +++ b/rosenpass/benches/handshake.rs @@ -8,6 +8,7 @@ use rosenpass_ciphers::StaticKem; use rosenpass_secret_memory::secret_policy_try_use_memfd_secrets; use rosenpass::protocol::basic_types::{MsgBuf, SPk, SSk, SymKey}; +use rosenpass::protocol::osk_domain_separator::OskDomainSeparator; use rosenpass::protocol::{CryptoServer, HandleMsgResult, PeerPtr, ProtocolVersion}; fn handle( @@ -54,8 +55,18 @@ fn make_server_pair(protocol_version: ProtocolVersion) -> Result<(CryptoServer, CryptoServer::new(ska, pka.clone()), CryptoServer::new(skb, pkb.clone()), ); - a.add_peer(Some(psk.clone()), pkb, protocol_version.clone())?; - b.add_peer(Some(psk), pka, protocol_version)?; + a.add_peer( + Some(psk.clone()), + pkb, + protocol_version.clone(), + OskDomainSeparator::default(), + )?; + b.add_peer( + Some(psk), + pka, + protocol_version, + OskDomainSeparator::default(), + )?; Ok((a, b)) } diff --git a/rosenpass/benches/trace_handshake.rs b/rosenpass/benches/trace_handshake.rs index 37ad1a6..8f55ed5 100644 --- a/rosenpass/benches/trace_handshake.rs +++ b/rosenpass/benches/trace_handshake.rs @@ -12,6 +12,7 @@ use rosenpass_secret_memory::secret_policy_try_use_memfd_secrets; use rosenpass_util::trace_bench::RpEventType; use rosenpass::protocol::basic_types::{MsgBuf, SPk, SSk, SymKey}; +use rosenpass::protocol::osk_domain_separator::OskDomainSeparator; use rosenpass::protocol::{CryptoServer, HandleMsgResult, PeerPtr, ProtocolVersion}; const ITERATIONS: usize = 100; @@ -73,8 +74,18 @@ fn make_server_pair(protocol_version: ProtocolVersion) -> Result<(CryptoServer, CryptoServer::new(ska, pka.clone()), CryptoServer::new(skb, pkb.clone()), ); - a.add_peer(Some(psk.clone()), pkb, protocol_version.clone())?; - b.add_peer(Some(psk), pka, protocol_version)?; + a.add_peer( + Some(psk.clone()), + pkb, + protocol_version.clone(), + OskDomainSeparator::default(), + )?; + b.add_peer( + Some(psk), + pka, + protocol_version, + OskDomainSeparator::default(), + )?; Ok((a, b)) } diff --git a/rosenpass/src/app_server.rs b/rosenpass/src/app_server.rs index ba984bb..cd406f9 100644 --- a/rosenpass/src/app_server.rs +++ b/rosenpass/src/app_server.rs @@ -26,6 +26,7 @@ use rosenpass_wireguard_broker::{WireguardBrokerCfg, WireguardBrokerMio, WG_KEY_ use crate::config::{ProtocolVersion, Verbosity}; use crate::protocol::basic_types::{MsgBuf, SPk, SSk, SymKey}; +use crate::protocol::osk_domain_separator::OskDomainSeparator; use crate::protocol::timing::Timing; use crate::protocol::{BuildCryptoServer, CryptoServer, HostIdentification, PeerPtr}; @@ -1013,6 +1014,7 @@ impl AppServer { /// # Examples /// /// See [Self::new]. + #[allow(clippy::too_many_arguments)] pub fn add_peer( &mut self, psk: Option, @@ -1021,11 +1023,16 @@ impl AppServer { broker_peer: Option, hostname: Option, protocol_version: ProtocolVersion, + osk_domain_separator: OskDomainSeparator, ) -> 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, protocol_version), - ConstructionSite::Product(srv) => srv.add_peer(psk, pk, protocol_version.into())?, + ConstructionSite::Builder(builder) => { + builder.add_peer(psk, pk, protocol_version, osk_domain_separator) + } + ConstructionSite::Product(srv) => { + srv.add_peer(psk, pk, protocol_version.into(), osk_domain_separator)? + } }; assert!(pn == self.peers.len()); diff --git a/rosenpass/src/cli.rs b/rosenpass/src/cli.rs index 107ec9b..186f617 100644 --- a/rosenpass/src/cli.rs +++ b/rosenpass/src/cli.rs @@ -491,6 +491,7 @@ impl CliArgs { broker_peer, cfg_peer.endpoint.clone(), cfg_peer.protocol_version.into(), + cfg_peer.osk_domain_separator.try_into()?, )?; } diff --git a/rosenpass/src/config.rs b/rosenpass/src/config.rs index 403c2c8..d74c776 100644 --- a/rosenpass/src/config.rs +++ b/rosenpass/src/config.rs @@ -18,6 +18,7 @@ use serde::{Deserialize, Serialize}; use rosenpass_util::file::{fopen_w, LoadValue, Visibility}; use crate::protocol::basic_types::{SPk, SSk}; +use crate::protocol::osk_domain_separator::OskDomainSeparator; use crate::app_server::AppServer; @@ -153,6 +154,73 @@ pub struct RosenpassPeer { #[serde(default)] /// The protocol version to use for the exchange pub protocol_version: ProtocolVersion, + + /// Allows using a custom domain separator + #[serde(flatten)] + pub osk_domain_separator: RosenpassPeerOskDomainSeparator, +} + +/// Configuration for [crate::protocol::osk_domain_separator::OskDomainSeparator] +/// +/// Refer to its documentation for more information and examples of how to use this. +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct RosenpassPeerOskDomainSeparator { + /// If Rosenpass is used for purposes other then securing WireGuard, + /// a custom domain separator and domain separator must be specified. + /// + /// Use `osk_organization` to indicate the organization who specifies the use case + /// and `osk_label` for a specific purpose within that organization. + /// + /// ```toml + /// [[peer]] + /// public_key = "my_public_key" + /// ... + /// osk_organization = "myorg.com" + /// osk_label = ["My Custom Messenger app"] + /// ``` + pub osk_organization: Option, + // If Rosenpass is used for purposes other then securing WireGuard, + /// a custom domain separator and domain separator must be specified. + /// + /// Use `osk_organization` to indicate the organization who specifies the use case + /// and `osk_label` for a specific purpose within that organization. + /// + /// ```toml + /// [[peer]] + /// public_key = "my_public_key" + /// ... + /// osk_namespace = "myorg.com" + /// osk_label = ["My Custom Messenger app"] + /// ``` + pub osk_label: Option>, +} + +impl RosenpassPeerOskDomainSeparator { + pub fn org_and_label(&self) -> anyhow::Result)>> { + match (&self.osk_organization, &self.osk_label) { + (None, None) => Ok(None), + (Some(org), Some(label)) => Ok(Some((&org, &label))), + (Some(_), None) => bail!("Specified osk_organization but not osk_label in config file. You need to specify both, or none."), + (None, Some(_)) => bail!("Specified osk_label but not osk_organization in config file. You need to specify both, or none."), + } + } + + pub fn validate(&self) -> anyhow::Result<()> { + let _org_and_label: Option<(_, _)> = self.org_and_label()?; + Ok(()) + } +} + +impl TryFrom for OskDomainSeparator { + type Error = anyhow::Error; + + fn try_from(val: RosenpassPeerOskDomainSeparator) -> anyhow::Result { + match val.org_and_label()? { + None => Ok(OskDomainSeparator::default()), + Some((org, label)) => Ok(OskDomainSeparator::custom_utf8(org, label)), + } + } } /// Information for supplying exchanged keys directly to WireGuard @@ -341,6 +409,10 @@ impl Rosenpass { ); } } + + if let Err(e) = peer.osk_domain_separator.validate() { + bail!("Invalid OSK domain separation configuration for peer {i}: {e}"); + } } Ok(()) diff --git a/rosenpass/src/hash_domains.rs b/rosenpass/src/hash_domains.rs index a1ac2b2..cc0333b 100644 --- a/rosenpass/src/hash_domains.rs +++ b/rosenpass/src/hash_domains.rs @@ -295,25 +295,21 @@ hash_domain_ns!( /// We do recommend that third parties base their specific domain separators /// on a internet domain and/or mix in much more specific information. /// - /// We only really use this to derive a output key for wireguard; see [osk]. - /// /// See [_ckextract]. /// /// # Examples /// /// See the [module](self) documentation on how to use the hash domains in general. - _ckextract, _user, "user"); + _ckextract, cke_user, "user"); hash_domain_ns!( /// Chaining key domain separator for any rosenpass specific purposes. /// - /// We only really use this to derive a output key for wireguard; see [osk]. - /// /// See [_ckextract]. /// /// # Examples /// /// See the [module](self) documentation on how to use the hash domains in general. - _user, _rp, "rosenpass.eu"); + cke_user, cke_user_rosenpass, "rosenpass.eu"); hash_domain!( /// Chaining key domain separator for deriving the key sent to WireGuard. /// @@ -325,4 +321,4 @@ hash_domain!( /// Check out its source code! /// /// See the [module](self) documentation on how to use the hash domains in general. - _rp, osk, "wireguard psk"); + cke_user_rosenpass, ext_wireguard_psk_osk, "wireguard psk"); diff --git a/rosenpass/src/protocol/basic_types.rs b/rosenpass/src/protocol/basic_types.rs index f3acfab..00078ac 100644 --- a/rosenpass/src/protocol/basic_types.rs +++ b/rosenpass/src/protocol/basic_types.rs @@ -18,6 +18,8 @@ pub type ESk = Secret<{ EphemeralKem::SK_LEN }>; /// Symmetric key pub type SymKey = Secret; +/// Variant of [SymKey] for use cases where the value is public +pub type PublicSymKey = [u8; 32]; /// Peer ID (derived from the public key, see the hash derivations in the [whitepaper](https://rosenpass.eu/whitepaper.pdf)) pub type PeerId = Public; diff --git a/rosenpass/src/protocol/build_crypto_server.rs b/rosenpass/src/protocol/build_crypto_server.rs index 73243b3..7929a47 100644 --- a/rosenpass/src/protocol/build_crypto_server.rs +++ b/rosenpass/src/protocol/build_crypto_server.rs @@ -6,6 +6,7 @@ use rosenpass_util::{build::Build, result::ensure_or}; use crate::config::ProtocolVersion; use super::basic_types::{SPk, SSk, SymKey}; +use super::osk_domain_separator::OskDomainSeparator; use super::{CryptoServer, PeerPtr}; #[derive(Debug, Clone)] @@ -148,20 +149,23 @@ pub struct MissingKeypair; /// /// ```rust /// use rosenpass_util::build::Build; -/// use rosenpass::protocol::basic_types::{SPk, SymKey}; -/// use rosenpass::protocol::{BuildCryptoServer, Keypair, PeerParams}; +/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets; +/// /// use rosenpass::config::ProtocolVersion; /// +/// use rosenpass::protocol::basic_types::{SPk, SymKey}; +/// use rosenpass::protocol::{BuildCryptoServer, Keypair, PeerParams}; +/// use rosenpass::protocol::osk_domain_separator::OskDomainSeparator; +/// /// // 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(), protocol_version: ProtocolVersion::V02 }; -/// let peer2 = PeerParams { psk: None, pk: SPk::random(), protocol_version: ProtocolVersion::V02 }; +/// let peer1 = PeerParams { psk: Some(SymKey::random()), pk: SPk::random(), protocol_version: ProtocolVersion::V02, osk_domain_separator: OskDomainSeparator::default() }; +/// let peer2 = PeerParams { psk: None, pk: SPk::random(), protocol_version: ProtocolVersion::V02, osk_domain_separator: OskDomainSeparator::default() }; /// /// let mut builder = BuildCryptoServer::new(Some(keypair.clone()), vec![peer1]); -/// builder.add_peer(peer2.psk.clone(), peer2.pk, ProtocolVersion::V02); +/// builder.add_peer(peer2.psk.clone(), peer2.pk, ProtocolVersion::V02, OskDomainSeparator::default()); /// /// let server = builder.build().expect("build failed"); /// assert_eq!(server.peers.len(), 2); @@ -191,16 +195,17 @@ impl Build for BuildCryptoServer { let mut srv = CryptoServer::new(sk, pk); - for ( - idx, - PeerParams { + for (idx, params) in self.peers.into_iter().enumerate() { + let PeerParams { psk, pk, protocol_version, - }, - ) in self.peers.into_iter().enumerate() - { - let PeerPtr(idx2) = srv.add_peer(psk, pk, protocol_version.into())?; + osk_domain_separator, + } = params; + + let PeerPtr(idx2) = + srv.add_peer(psk, pk, protocol_version.into(), osk_domain_separator)?; + assert!(idx == idx2, "Peer id changed during CryptoServer construction from {idx} to {idx2}. This is a developer error.") } @@ -223,6 +228,7 @@ pub struct PeerParams { pub pk: SPk, /// The used protocol version. pub protocol_version: ProtocolVersion, + pub osk_domain_separator: OskDomainSeparator, } impl BuildCryptoServer { @@ -321,13 +327,15 @@ impl BuildCryptoServer { /// /// ```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(); /// /// use rosenpass_util::build::Build; /// use rosenpass::protocol::basic_types::{SymKey, SPk}; /// use rosenpass::protocol::{BuildCryptoServer, Keypair}; + /// use rosenpass::protocol::osk_domain_separator::OskDomainSeparator; + /// + /// // 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(); /// /// // Deferred initialization: Create builder first, add some peers later /// let keypair_option = Some(Keypair::random()); @@ -340,7 +348,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(), ProtocolVersion::V02); + /// builder.with_added_peer(Some(pre_shared_key.clone()), public_key.clone(), ProtocolVersion::V02, OskDomainSeparator::default()); /// /// // New server instances will then start with the peer being registered already /// let server = builder.build().expect("build failed"); @@ -355,12 +363,14 @@ impl BuildCryptoServer { psk: Option, pk: SPk, protocol_version: ProtocolVersion, + osk_domain_separator: OskDomainSeparator, ) -> &mut Self { // TODO: Check here already whether peer was already added self.peers.push(PeerParams { psk, pk, protocol_version, + osk_domain_separator, }); self } @@ -371,9 +381,10 @@ impl BuildCryptoServer { psk: Option, pk: SPk, protocol_version: ProtocolVersion, + osk_domain_separator: OskDomainSeparator, ) -> PeerPtr { let id = PeerPtr(self.peers.len()); - self.with_added_peer(psk, pk, protocol_version); + self.with_added_peer(psk, pk, protocol_version, osk_domain_separator); id } @@ -394,6 +405,7 @@ impl BuildCryptoServer { /// /// use rosenpass::protocol::basic_types::{SymKey, SPk}; /// use rosenpass::protocol::{BuildCryptoServer, Keypair}; + /// use rosenpass::protocol::osk_domain_separator::OskDomainSeparator; /// /// // We have to define the security policy before using Secrets. /// secret_policy_use_only_malloc_secrets(); @@ -401,7 +413,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, ProtocolVersion::V02); + /// builder.add_peer(None, peer_pk, ProtocolVersion::V02, OskDomainSeparator::default()); /// /// // 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 72bead0..7de618f 100644 --- a/rosenpass/src/protocol/mod.rs +++ b/rosenpass/src/protocol/mod.rs @@ -31,6 +31,7 @@ //! //! use rosenpass::protocol::basic_types::{SSk, SPk, MsgBuf, SymKey}; //! use rosenpass::protocol::{PeerPtr, CryptoServer}; +//! use rosenpass::protocol::osk_domain_separator::OskDomainSeparator; //! //! # fn main() -> anyhow::Result<()> { //! // Set security policy for storing secrets @@ -52,8 +53,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, ProtocolVersion::V03)?; -//! b.add_peer(Some(psk), peer_a_pk, ProtocolVersion::V03)?; +//! a.add_peer(Some(psk.clone()), peer_b_pk, ProtocolVersion::V03, OskDomainSeparator::default())?; +//! b.add_peer(Some(psk), peer_a_pk, ProtocolVersion::V03, OskDomainSeparator::default())?; //! //! // declare buffers for message exchange //! let (mut a_buf, mut b_buf) = (MsgBuf::zero(), MsgBuf::zero()); @@ -84,6 +85,7 @@ pub mod basic_types; pub mod constants; pub mod cookies; pub mod index; +pub mod osk_domain_separator; pub mod testutils; pub mod timing; pub mod zerocopy; diff --git a/rosenpass/src/protocol/osk_domain_separator.rs b/rosenpass/src/protocol/osk_domain_separator.rs new file mode 100644 index 0000000..e8f2b9e --- /dev/null +++ b/rosenpass/src/protocol/osk_domain_separator.rs @@ -0,0 +1,91 @@ +//! Management of domain separators for the OSK (output key) in the rosenpass protocol +//! +//! The domain separator is there to ensure that keys are bound to the purpose they are used for. +//! +//! See the whitepaper section on protocol extensions for more details on how this is used. +//! +//! # See also +//! +//! - [crate::protocol::Peer] +//! - [crate::protocol::CryptoServer::add_peer] +//! - [crate::protocol::CryptoServer::osk] +//! +//! # Examples +//! +//! There are some basic examples of using custom domain separators in the examples of +//! [super::CryptoServer::poll]. Look for the test function `test_osk_label_mismatch()` +//! in particular. + +use rosenpass_ciphers::subtle::keyed_hash::KeyedHash; +use rosenpass_util::result::OkExt; + +use crate::hash_domains; + +use super::basic_types::PublicSymKey; + +/// The OSK (output shared key) domain separator to use for a specific peer +/// +#[derive(Clone, PartialEq, Eq, Debug, PartialOrd, Ord, Default)] +pub enum OskDomainSeparator { + /// By default we use the domain separator that indicates that the resulting keys + /// are used by WireGuard to establish a connection + #[default] + ExtensionWireguardPsk, + /// Used for user-defined domain separators + Custom { + /// A globally unique string identifying the vendor or group who defines this domain + /// separator (we use our domain ourselves – "rosenpass.eu") + namespace: Vec, + /// Any custom labels within that namespace. Could be descriptive prose. + labels: Vec>, + }, +} + +impl OskDomainSeparator { + /// Construct [OskDomainSeparator::ExtensionWireguardPsk] + pub fn for_wireguard_psk() -> Self { + Self::ExtensionWireguardPsk + } + + /// Construct [OskDomainSeparator::Custom] from strings + pub fn custom_utf8(namespace: &str, label: I) -> Self + where + I: IntoIterator, + T: AsRef, + { + let namespace = namespace.as_bytes().to_owned(); + let labels = label + .into_iter() + .map(|e| e.as_ref().as_bytes().to_owned()) + .collect::>(); + Self::Custom { namespace, labels } + } + + /// Variant of [Self::custom_utf8] that takes just one label (instead of a sequence) + pub fn custom_utf8_single_label(namespace: &str, label: &str) -> Self { + Self::custom_utf8(namespace, std::iter::once(label)) + } + + /// The domain separator is not just an encoded string, it instead uses + /// [rosenpass_ciphers::hash_domain::HashDomain], starting from [hash_domains::cke_user]. + /// + /// This means, that the domain separator is really a sequence of multiple different domain + /// separators, each of which is allowed to be quite long. This is very useful as it allows + /// users to avoid specifying complex, prosaic domain separators. To ensure that this does not + /// force us create extra overhead when the protocol is executed, this sequence of strings is + /// compressed into a single, fixed-length hash of all the inputs. This hash could be created + /// at program startup and cached. + /// + /// This function generates this fixed-length hash. + pub fn compress_with(&self, hash_choice: KeyedHash) -> anyhow::Result { + use OskDomainSeparator as O; + match &self { + O::ExtensionWireguardPsk => hash_domains::ext_wireguard_psk_osk(hash_choice), + O::Custom { namespace, labels } => hash_domains::cke_user(hash_choice)? + .mix(namespace)? + .mix_many(labels)? + .into_value() + .ok(), + } + } +} diff --git a/rosenpass/src/protocol/protocol.rs b/rosenpass/src/protocol/protocol.rs index a40f83d..778a1be 100644 --- a/rosenpass/src/protocol/protocol.rs +++ b/rosenpass/src/protocol/protocol.rs @@ -36,7 +36,8 @@ use rosenpass_util::{ use crate::{hash_domains, msgs::*, RosenpassError}; use super::basic_types::{ - BiscuitId, EPk, ESk, MsgBuf, PeerId, PeerNo, SPk, SSk, SessionId, SymKey, XAEADNonce, + BiscuitId, EPk, ESk, MsgBuf, PeerId, PeerNo, PublicSymKey, SPk, SSk, SessionId, SymKey, + XAEADNonce, }; use super::constants::{ BISCUIT_EPOCH, COOKIE_SECRET_EPOCH, COOKIE_SECRET_LEN, COOKIE_VALUE_LEN, @@ -46,6 +47,7 @@ use super::constants::{ }; use super::cookies::{BiscuitKey, CookieSecret, CookieStore}; use super::index::{PeerIndex, PeerIndexKey}; +use super::osk_domain_separator::OskDomainSeparator; use super::timing::{has_happened, Timing, BCE, UNENDING}; use super::zerocopy::{truncating_cast_into, truncating_cast_into_nomut}; @@ -179,6 +181,7 @@ impl From for ProtocolVersion { /// /// use rosenpass::protocol::basic_types::{SSk, SPk, SymKey}; /// use rosenpass::protocol::{Peer, ProtocolVersion}; +/// use rosenpass::protocol::osk_domain_separator::OskDomainSeparator; /// /// rosenpass_secret_memory::secret_policy_try_use_memfd_secrets(); /// @@ -191,13 +194,13 @@ impl From for ProtocolVersion { /// let psk = SymKey::random(); /// /// // Creation with a PSK -/// let peer_psk = Peer::new(psk, spkt.clone(), ProtocolVersion::V03); +/// let peer_psk = Peer::new(psk, spkt.clone(), ProtocolVersion::V03, OskDomainSeparator::default()); /// /// // Creation without a PSK -/// let peer_nopsk = Peer::new(SymKey::zero(), spkt, ProtocolVersion::V03); +/// let peer_nopsk = Peer::new(SymKey::zero(), spkt, ProtocolVersion::V03, OskDomainSeparator::default()); /// /// // Create a second peer -/// let peer_psk_2 = Peer::new(SymKey::zero(), spkt2, ProtocolVersion::V03); +/// let peer_psk_2 = Peer::new(SymKey::zero(), spkt2, ProtocolVersion::V03, OskDomainSeparator::default()); /// /// // Peer ID does not depend on PSK, but it does depend on the public key /// assert_eq!(peer_psk.pidt()?, peer_nopsk.pidt()?); @@ -253,9 +256,10 @@ 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, - /// The protocol version used by with this peer. pub protocol_version: ProtocolVersion, + /// Domain separator for generated OSKs + pub osk_domain_separator: OskDomainSeparator, } impl Peer { @@ -282,6 +286,7 @@ impl Peer { handshake: None, known_init_conf_response: None, protocol_version, + osk_domain_separator: OskDomainSeparator::default(), } } } @@ -1222,6 +1227,7 @@ impl CryptoServer { /// ``` /// use std::ops::DerefMut; /// use rosenpass::protocol::basic_types::{SSk, SPk, SymKey}; + /// use rosenpass::protocol::osk_domain_separator::OskDomainSeparator; /// use rosenpass::protocol::{CryptoServer, ProtocolVersion}; /// use rosenpass_ciphers::StaticKem; /// use rosenpass_cipher_traits::primitives::Kem; @@ -1237,7 +1243,7 @@ impl CryptoServer { /// /// let psk = SymKey::random(); /// // We use the latest protocol version for the example. - /// let peer = srv.add_peer(Some(psk), spkt.clone(), ProtocolVersion::V03)?; + /// let peer = srv.add_peer(Some(psk), spkt.clone(), ProtocolVersion::V03, OskDomainSeparator::for_wireguard_psk())?; /// /// assert_eq!(peer.get(&srv).spkt, spkt); /// @@ -1248,6 +1254,7 @@ impl CryptoServer { psk: Option, pk: SPk, protocol_version: ProtocolVersion, + osk_domain_separator: OskDomainSeparator, ) -> Result { let peer = Peer { psk: psk.unwrap_or_else(SymKey::zero), @@ -1258,6 +1265,7 @@ impl CryptoServer { known_init_conf_response: None, initiation_requested: false, protocol_version, + osk_domain_separator, }; let peerid = peer.pidt()?; let peerno = self.peers.len(); @@ -1447,7 +1455,12 @@ impl Peer { /// # Examples /// /// See example in [Self]. - pub fn new(psk: SymKey, pk: SPk, protocol_version: ProtocolVersion) -> Peer { + pub fn new( + psk: SymKey, + pk: SPk, + protocol_version: ProtocolVersion, + osk_domain_separator: OskDomainSeparator, + ) -> Peer { Peer { psk, spkt: pk, @@ -1457,6 +1470,7 @@ impl Peer { known_init_conf_response: None, initiation_requested: false, protocol_version, + osk_domain_separator, } } @@ -3310,28 +3324,61 @@ impl HandshakeState { } impl CryptoServer { + /// Variant of [Self::osk] that allows a custom, already compressed domain separator to be specified + /// + /// Refer to the documentation of [Self::osk] for more information. + pub fn osk_with_compressed_domain_separator( + &self, + peer: PeerPtr, + compressed_domain_separator: &PublicSymKey, + ) -> Result { + let session = peer + .session() + .get(self) + .as_ref() + .with_context(|| format!("No current session for peer {:?}", peer))?; + + Ok(session.ck.mix(compressed_domain_separator)?.into_secret()) + } + + /// Variant of [Self::osk] that allows a custom domain separator to be specified + /// + /// Refer to the documentation of [Self::osk] for more information. + pub fn osk_with_domain_separator( + &self, + peer: PeerPtr, + domain_separator: &OskDomainSeparator, + ) -> Result { + let hash_choice = peer.get(self).protocol_version.keyed_hash(); + let compressed_domain_separator = domain_separator.compress_with(hash_choice)?; + self.osk_with_compressed_domain_separator(peer, &compressed_domain_separator) + } + /// Get the shared key that was established with given peer /// /// Fail if no session is available with the peer /// + /// # See also + /// + /// - [Self::osk_with_domain_separator] + /// - [Self::osk_with_compressed_domain_separator] + /// /// # Examples /// /// See the example in [crate::protocol] for an incomplete but working example /// of how to perform a key exchange using Rosenpass. /// /// See the example in [CryptoServer::poll] for a complete example. + /// + /// See the documentation and examples in [super::osk_domain_separator] for more information + /// about using custom domain separators. pub fn osk(&self, peer: PeerPtr) -> Result { - let session = peer - .session() + let hash_choice = peer.get(self).protocol_version.keyed_hash(); + let compressed_domain_separator = peer .get(self) - .as_ref() - .with_context(|| format!("No current session for peer {:?}", peer))?; - Ok(session - .ck - .mix(&hash_domains::osk( - peer.get(self).protocol_version.keyed_hash(), - )?)? - .into_secret()) + .osk_domain_separator + .compress_with(hash_choice)?; + self.osk_with_compressed_domain_separator(peer, &compressed_domain_separator) } } diff --git a/rosenpass/src/protocol/test.rs b/rosenpass/src/protocol/test.rs index c525c7e..8bd17f9 100644 --- a/rosenpass/src/protocol/test.rs +++ b/rosenpass/src/protocol/test.rs @@ -13,6 +13,7 @@ use crate::msgs::{EmptyData, Envelope, InitConf, InitHello, MsgType, RespHello, use super::basic_types::{MsgBuf, SPk, SSk, SymKey}; use super::constants::REKEY_AFTER_TIME_RESPONDER; +use super::osk_domain_separator::OskDomainSeparator; use super::zerocopy::{truncating_cast_into, truncating_cast_into_nomut}; use super::{ CryptoServer, HandleMsgResult, HostIdentification, KnownInitConfResponsePtr, PeerPtr, @@ -145,8 +146,18 @@ fn make_server_pair(protocol_version: ProtocolVersion) -> Result<(CryptoServer, CryptoServer::new(ska, pka.clone()), CryptoServer::new(skb, pkb.clone()), ); - a.add_peer(Some(psk.clone()), pkb, protocol_version.clone())?; - b.add_peer(Some(psk), pka, protocol_version)?; + a.add_peer( + Some(psk.clone()), + pkb, + protocol_version.clone(), + OskDomainSeparator::default(), + )?; + b.add_peer( + Some(psk), + pka, + protocol_version, + OskDomainSeparator::default(), + )?; Ok((a, b)) } @@ -619,8 +630,18 @@ fn init_conf_retransmission(protocol_version: ProtocolVersion) -> anyhow::Result let mut b = CryptoServer::new(skb, pkb.clone()); // introduce peers to each other - let b_peer = a.add_peer(None, pkb, protocol_version.clone())?; - let a_peer = b.add_peer(None, pka, protocol_version.clone())?; + let b_peer = a.add_peer( + None, + pkb, + protocol_version.clone(), + OskDomainSeparator::default(), + )?; + let a_peer = b.add_peer( + None, + pka, + protocol_version.clone(), + OskDomainSeparator::default(), + )?; // Execute protocol up till the responder confirmation (EmptyData) let ih1 = proc_initiation(&mut a, b_peer)?; diff --git a/rosenpass/src/protocol/testutils.rs b/rosenpass/src/protocol/testutils.rs index 2cc85a6..8ba5183 100644 --- a/rosenpass/src/protocol/testutils.rs +++ b/rosenpass/src/protocol/testutils.rs @@ -7,6 +7,7 @@ use rosenpass_ciphers::StaticKem; use super::{ basic_types::{SPk, SSk}, + osk_domain_separator::OskDomainSeparator, CryptoServer, PeerPtr, ProtocolVersion, }; @@ -26,7 +27,12 @@ impl ServerForTesting { 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(), protocol_version)?; + let peer = srv.add_peer( + None, + spkt.clone(), + protocol_version, + OskDomainSeparator::default(), + )?; let peer_keys = (sskt, spkt); Ok(ServerForTesting { diff --git a/rosenpass/tests/api-integration-tests-api-setup.rs b/rosenpass/tests/api-integration-tests-api-setup.rs index 8313bb8..ee9341a 100644 --- a/rosenpass/tests/api-integration-tests-api-setup.rs +++ b/rosenpass/tests/api-integration-tests-api-setup.rs @@ -106,6 +106,7 @@ fn api_integration_api_setup(protocol_version: ProtocolVersion) -> anyhow::Resul extra_params: vec![], }), protocol_version: protocol_version.clone(), + osk_domain_separator: Default::default(), }], }; @@ -127,6 +128,7 @@ fn api_integration_api_setup(protocol_version: ProtocolVersion) -> anyhow::Resul pre_shared_key: None, wg: None, protocol_version: protocol_version.clone(), + osk_domain_separator: Default::default(), }], }; diff --git a/rosenpass/tests/api-integration-tests.rs b/rosenpass/tests/api-integration-tests.rs index b3b638d..18380c2 100644 --- a/rosenpass/tests/api-integration-tests.rs +++ b/rosenpass/tests/api-integration-tests.rs @@ -83,6 +83,7 @@ fn api_integration_test(protocol_version: ProtocolVersion) -> anyhow::Result<()> pre_shared_key: None, wg: None, protocol_version: protocol_version.clone(), + osk_domain_separator: Default::default(), }], }; @@ -104,6 +105,7 @@ fn api_integration_test(protocol_version: ProtocolVersion) -> anyhow::Result<()> pre_shared_key: None, wg: None, protocol_version: protocol_version.clone(), + osk_domain_separator: Default::default(), }], }; diff --git a/rosenpass/tests/app_server_example.rs b/rosenpass/tests/app_server_example.rs index bd5a77e..4b8169a 100644 --- a/rosenpass/tests/app_server_example.rs +++ b/rosenpass/tests/app_server_example.rs @@ -6,8 +6,8 @@ use rosenpass_ciphers::StaticKem; use rosenpass_util::{file::LoadValueB64, functional::run, mem::DiscardResultExt, result::OkExt}; use rosenpass::app_server::{AppServer, AppServerTest, MAX_B64_KEY_SIZE}; -use rosenpass::config::ProtocolVersion; use rosenpass::protocol::basic_types::{SPk, SSk, SymKey}; +use rosenpass::{config::ProtocolVersion, protocol::osk_domain_separator::OskDomainSeparator}; #[test] fn key_exchange_with_app_server_v02() -> anyhow::Result<()> { @@ -62,7 +62,8 @@ fn key_exchange_with_app_server(protocol_version: ProtocolVersion) -> anyhow::Re outfile, broker_peer, hostname, - protocol_version.clone(), + protocol_version, + OskDomainSeparator::default(), )?; srv.app_srv.event_loop() diff --git a/rosenpass/tests/poll_example.rs b/rosenpass/tests/poll_example.rs index 62fe4f8..10cc875 100644 --- a/rosenpass/tests/poll_example.rs +++ b/rosenpass/tests/poll_example.rs @@ -10,6 +10,7 @@ use rosenpass_ciphers::StaticKem; use rosenpass_util::result::OkExt; use rosenpass::protocol::basic_types::{MsgBuf, SPk, SSk, SymKey}; +use rosenpass::protocol::osk_domain_separator::OskDomainSeparator; use rosenpass::protocol::testutils::time_travel_forward; use rosenpass::protocol::timing::{Timing, UNENDING}; use rosenpass::protocol::{CryptoServer, HostIdentification, PeerPtr, PollResult, ProtocolVersion}; @@ -19,19 +20,38 @@ use rosenpass::protocol::{CryptoServer, HostIdentification, PeerPtr, PollResult, #[test] fn test_successful_exchange_with_poll_v02() -> anyhow::Result<()> { - test_successful_exchange_with_poll(ProtocolVersion::V02) + test_successful_exchange_with_poll(ProtocolVersion::V02, OskDomainSeparator::default()) } #[test] fn test_successful_exchange_with_poll_v03() -> anyhow::Result<()> { - test_successful_exchange_with_poll(ProtocolVersion::V03) + test_successful_exchange_with_poll(ProtocolVersion::V03, OskDomainSeparator::default()) } -fn test_successful_exchange_with_poll(protocol_version: ProtocolVersion) -> anyhow::Result<()> { +#[test] +fn test_successful_exchange_with_poll_v02_custom_domain_separator() -> anyhow::Result<()> { + test_successful_exchange_with_poll( + ProtocolVersion::V02, + OskDomainSeparator::custom_utf8_single_label("example.org", "Example Label"), + ) +} + +#[test] +fn test_successful_exchange_with_poll_v03_custom_domain_separator() -> anyhow::Result<()> { + test_successful_exchange_with_poll( + ProtocolVersion::V03, + OskDomainSeparator::custom_utf8_single_label("example.org", "Example Label"), + ) +} + +fn test_successful_exchange_with_poll( + protocol_version: ProtocolVersion, + osk_domain_separator: OskDomainSeparator, +) -> 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(protocol_version)?; + let mut sim = RosenpassSimulator::new(protocol_version, osk_domain_separator)?; sim.poll_loop(150)?; // Poll 75 times let transcript = sim.transcript; @@ -104,7 +124,7 @@ fn test_successful_exchange_under_packet_loss( rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets(); // Create the simulator - let mut sim = RosenpassSimulator::new(protocol_version)?; + let mut sim = RosenpassSimulator::new(protocol_version, OskDomainSeparator::default())?; // Make sure the servers are set to under load condition sim.srv_a.under_load = true; @@ -181,6 +201,94 @@ fn test_successful_exchange_under_packet_loss( Ok(()) } +#[test] +fn test_osk_label_mismatch() -> 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 ds_wg = OskDomainSeparator::for_wireguard_psk(); + let ds_custom1 = OskDomainSeparator::custom_utf8("example.com", ["Example Label"]); + let ds_custom2 = + OskDomainSeparator::custom_utf8("example.com", ["Example Label", "Second Token"]); + + // Create the simulator + let mut sim = RosenpassSimulator::new(ProtocolVersion::V03, ds_custom1.clone())?; + assert_eq!(sim.srv_a.srv.peers[0].osk_domain_separator, ds_custom1); + assert_eq!(sim.srv_b.srv.peers[0].osk_domain_separator, ds_custom1); + + // Deliberately produce a label mismatch + sim.srv_b.srv.peers[0].osk_domain_separator = ds_custom2.clone(); + assert_eq!(sim.srv_a.srv.peers[0].osk_domain_separator, ds_custom1); + assert_eq!(sim.srv_b.srv.peers[0].osk_domain_separator, ds_custom2); + + // Perform the key exchanges + for _ in 0..300 { + let ev = sim.poll()?; + + assert!(!matches!(ev, TranscriptEvent::CompletedExchange(_)), + "We deliberately provoked a mismatch in OSK domain separator, but still saw a successfully completed key exchange"); + + // Wait for a key exchange that failed with a KeyMismatch event + let (osk_a_custom1, osk_b_custom2) = match ev { + TranscriptEvent::FailedExchangeWithKeyMismatch(osk_a, osk_b) => { + (osk_a.clone(), osk_b.clone()) + } + _ => continue, + }; + + // The OSKs have been produced through the call to the function CryptoServer::osk(…) + assert_eq!( + sim.srv_a.srv.osk(PeerPtr(0))?.secret(), + osk_a_custom1.secret() + ); + assert_eq!( + sim.srv_b.srv.osk(PeerPtr(0))?.secret(), + osk_b_custom2.secret() + ); + + // They are not matching (obviously) + assert_ne!(osk_a_custom1.secret(), osk_b_custom2.secret()); + + // We can manually generate OSKs with matching labels + let osk_a_custom2 = sim + .srv_a + .srv + .osk_with_domain_separator(PeerPtr(0), &ds_custom2)?; + let osk_b_custom1 = sim + .srv_b + .srv + .osk_with_domain_separator(PeerPtr(0), &ds_custom1)?; + let osk_a_wg = sim + .srv_a + .srv + .osk_with_domain_separator(PeerPtr(0), &ds_wg)?; + let osk_b_wg = sim + .srv_b + .srv + .osk_with_domain_separator(PeerPtr(0), &ds_wg)?; + + // The key exchange may have failed for some other reason, in this case we expect a + // successful-but-label-mismatch exchange later in the protocol + if osk_a_custom1.secret() != osk_b_custom1.secret() { + continue; + } + + // But if one of the labeled keys match, all should match + assert_eq!(osk_a_custom2.secret(), osk_b_custom2.secret()); + assert_eq!(osk_a_wg.secret(), osk_b_wg.secret()); + + // But the three keys do not match each other + assert_ne!(osk_a_custom1.secret(), osk_a_custom2.secret()); + assert_ne!(osk_a_custom1.secret(), osk_a_wg.secret()); + assert_ne!(osk_a_custom2.secret(), osk_a_wg.secret()); + + // The test succeeded + return Ok(()); + } + + panic!("Test did not succeed even after allowing for a large number of communication rounds"); +} + type MessageType = u8; /// Lets record the events that are produced by Rosenpass @@ -193,6 +301,7 @@ enum TranscriptEvent { event: ServerEvent, }, CompletedExchange(SymKey), + FailedExchangeWithKeyMismatch(SymKey, SymKey), } #[derive(Debug)] @@ -292,7 +401,10 @@ struct SimulatorServer { impl RosenpassSimulator { /// Set up the simulator - fn new(protocol_version: ProtocolVersion) -> anyhow::Result { + fn new( + protocol_version: ProtocolVersion, + osk_domain_separator: OskDomainSeparator, + ) -> 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())?; @@ -305,8 +417,18 @@ 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, protocol_version.clone())?; - let peer_b = srv_b.add_peer(Some(psk), peer_a_pk, protocol_version.clone())?; + let peer_a = srv_a.add_peer( + Some(psk.clone()), + peer_b_pk, + protocol_version.clone(), + osk_domain_separator.clone(), + )?; + let peer_b = srv_b.add_peer( + Some(psk), + peer_a_pk, + protocol_version.clone(), + osk_domain_separator.clone(), + )?; // Set up the individual server data structures let srv_a = SimulatorServer::new(srv_a, peer_b); @@ -566,10 +688,18 @@ impl ServerPtr { None => return Ok(()), }; + // Make sure the OSK of server A always comes first + let (osk_a, osk_b) = match self == ServerPtr::A { + true => (osk, other_osk), + false => (other_osk, osk), + }; + // Issue the successful exchange event if the OSKs are equal; // be careful to use constant time comparison for things like this! - if rosenpass_constant_time::memcmp(osk.secret(), other_osk.secret()) { - self.enqueue_upcoming_poll_event(sim, TE::CompletedExchange(osk)); + if rosenpass_constant_time::memcmp(osk_a.secret(), osk_b.secret()) { + self.enqueue_upcoming_poll_event(sim, TE::CompletedExchange(osk_a)); + } else { + self.enqueue_upcoming_poll_event(sim, TE::FailedExchangeWithKeyMismatch(osk_a, osk_b)); } Ok(()) diff --git a/rp/src/exchange.rs b/rp/src/exchange.rs index e4df77d..d3f729c 100644 --- a/rp/src/exchange.rs +++ b/rp/src/exchange.rs @@ -208,7 +208,10 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> { use rosenpass::{ app_server::{AppServer, BrokerPeer}, config::Verbosity, - protocol::basic_types::{SPk, SSk, SymKey}, + protocol::{ + basic_types::{SPk, SSk, SymKey}, + osk_domain_separator::OskDomainSeparator, + }, }; use rosenpass_secret_memory::Secret; use rosenpass_util::file::{LoadValue as _, LoadValueB64}; @@ -362,6 +365,7 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> { broker_peer, peer.endpoint.map(|x| x.to_string()), peer.protocol_version, + OskDomainSeparator::for_wireguard_psk(), )?; // Configure routes, equivalent to `ip route replace dev ` and set up diff --git a/util/src/fd.rs b/util/src/fd.rs index 658246f..351d011 100644 --- a/util/src/fd.rs +++ b/util/src/fd.rs @@ -561,7 +561,7 @@ mod tests { let mut file = FdIo(open_nullfd()?); let mut buf = [0; 10]; assert!(matches!(file.read(&mut buf), Ok(0) | Err(_))); - assert!(matches!(file.write(&buf), Err(_))); + assert!(file.write(&buf).is_err()); Ok(()) } diff --git a/util/src/length_prefix_encoding/encoder.rs b/util/src/length_prefix_encoding/encoder.rs index 1d3071e..1e0c6d1 100644 --- a/util/src/length_prefix_encoding/encoder.rs +++ b/util/src/length_prefix_encoding/encoder.rs @@ -618,7 +618,7 @@ mod tests { #[test] fn test_lpe_error_conversion_downcast_invalid() { let pos_error = PositionOutOfBufferBounds; - let sanity_error = SanityError::PositionOutOfBufferBounds(pos_error.into()); + let sanity_error = SanityError::PositionOutOfBufferBounds(pos_error); match MessageLenSanityError::try_from(sanity_error) { Ok(_) => panic!("Conversion should always fail (incompatible enum variant)"), Err(err) => assert!(matches!(err, PositionOutOfBufferBounds)), diff --git a/util/src/mem.rs b/util/src/mem.rs index f3f45f5..5d43f07 100644 --- a/util/src/mem.rs +++ b/util/src/mem.rs @@ -302,6 +302,6 @@ mod test_forgetting { drop_was_called.store(false, SeqCst); let forgetting = Forgetting::new(SetFlagOnDrop(drop_was_called.clone())); drop(forgetting); - assert_eq!(drop_was_called.load(SeqCst), false); + assert!(!drop_was_called.load(SeqCst)); } }