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
This commit is contained in:
Karolin Varner
2025-06-25 19:27:19 +02:00
parent 48b7bb2f14
commit b1a7d94295
23 changed files with 579 additions and 72 deletions

View File

@@ -83,6 +83,33 @@ impl HashDomain {
Ok(Self(new_key, self.1)) 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<I, T>(mut self, it: I) -> Result<Self>
where
I: IntoIterator<Item = T>,
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` /// Creates a new [SecretHashDomain] by mixing in a new key `v`
/// by calling [SecretHashDomain::invoke_primitive] with this /// by calling [SecretHashDomain::invoke_primitive] with this
/// [HashDomain]'s key as `k` and `v` as `d`. /// [HashDomain]'s key as `k` and `v` as `d`.
@@ -161,6 +188,46 @@ impl SecretHashDomain {
Self::invoke_primitive(self.0.secret(), v, self.1) 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<I, T>(mut self, it: I) -> Result<Self>
where
I: IntoIterator<Item = T>,
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` /// Creates a new [SecretHashDomain] by mixing in a new key `v`
/// by calling [SecretHashDomain::invoke_primitive] with the key of this /// by calling [SecretHashDomain::invoke_primitive] with the key of this
/// [HashDomainNamespace] as `k` and `v` as `d`. /// [HashDomainNamespace] as `k` and `v` as `d`.

View File

@@ -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}). 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} ## 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. 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.

View File

@@ -8,6 +8,7 @@ use rosenpass_ciphers::StaticKem;
use rosenpass_secret_memory::secret_policy_try_use_memfd_secrets; use rosenpass_secret_memory::secret_policy_try_use_memfd_secrets;
use rosenpass::protocol::basic_types::{MsgBuf, SPk, SSk, SymKey}; use rosenpass::protocol::basic_types::{MsgBuf, SPk, SSk, SymKey};
use rosenpass::protocol::osk_domain_separator::OskDomainSeparator;
use rosenpass::protocol::{CryptoServer, HandleMsgResult, PeerPtr, ProtocolVersion}; use rosenpass::protocol::{CryptoServer, HandleMsgResult, PeerPtr, ProtocolVersion};
fn handle( fn handle(
@@ -54,8 +55,18 @@ fn make_server_pair(protocol_version: ProtocolVersion) -> Result<(CryptoServer,
CryptoServer::new(ska, pka.clone()), CryptoServer::new(ska, pka.clone()),
CryptoServer::new(skb, pkb.clone()), CryptoServer::new(skb, pkb.clone()),
); );
a.add_peer(Some(psk.clone()), pkb, protocol_version.clone())?; a.add_peer(
b.add_peer(Some(psk), pka, protocol_version)?; Some(psk.clone()),
pkb,
protocol_version.clone(),
OskDomainSeparator::default(),
)?;
b.add_peer(
Some(psk),
pka,
protocol_version,
OskDomainSeparator::default(),
)?;
Ok((a, b)) Ok((a, b))
} }

View File

@@ -12,6 +12,7 @@ use rosenpass_secret_memory::secret_policy_try_use_memfd_secrets;
use rosenpass_util::trace_bench::RpEventType; use rosenpass_util::trace_bench::RpEventType;
use rosenpass::protocol::basic_types::{MsgBuf, SPk, SSk, SymKey}; use rosenpass::protocol::basic_types::{MsgBuf, SPk, SSk, SymKey};
use rosenpass::protocol::osk_domain_separator::OskDomainSeparator;
use rosenpass::protocol::{CryptoServer, HandleMsgResult, PeerPtr, ProtocolVersion}; use rosenpass::protocol::{CryptoServer, HandleMsgResult, PeerPtr, ProtocolVersion};
const ITERATIONS: usize = 100; const ITERATIONS: usize = 100;
@@ -73,8 +74,18 @@ fn make_server_pair(protocol_version: ProtocolVersion) -> Result<(CryptoServer,
CryptoServer::new(ska, pka.clone()), CryptoServer::new(ska, pka.clone()),
CryptoServer::new(skb, pkb.clone()), CryptoServer::new(skb, pkb.clone()),
); );
a.add_peer(Some(psk.clone()), pkb, protocol_version.clone())?; a.add_peer(
b.add_peer(Some(psk), pka, protocol_version)?; Some(psk.clone()),
pkb,
protocol_version.clone(),
OskDomainSeparator::default(),
)?;
b.add_peer(
Some(psk),
pka,
protocol_version,
OskDomainSeparator::default(),
)?;
Ok((a, b)) Ok((a, b))
} }

View File

@@ -26,6 +26,7 @@ use rosenpass_wireguard_broker::{WireguardBrokerCfg, WireguardBrokerMio, WG_KEY_
use crate::config::{ProtocolVersion, Verbosity}; use crate::config::{ProtocolVersion, Verbosity};
use crate::protocol::basic_types::{MsgBuf, SPk, SSk, SymKey}; use crate::protocol::basic_types::{MsgBuf, SPk, SSk, SymKey};
use crate::protocol::osk_domain_separator::OskDomainSeparator;
use crate::protocol::timing::Timing; use crate::protocol::timing::Timing;
use crate::protocol::{BuildCryptoServer, CryptoServer, HostIdentification, PeerPtr}; use crate::protocol::{BuildCryptoServer, CryptoServer, HostIdentification, PeerPtr};
@@ -1013,6 +1014,7 @@ impl AppServer {
/// # Examples /// # Examples
/// ///
/// See [Self::new]. /// See [Self::new].
#[allow(clippy::too_many_arguments)]
pub fn add_peer( pub fn add_peer(
&mut self, &mut self,
psk: Option<SymKey>, psk: Option<SymKey>,
@@ -1021,11 +1023,16 @@ impl AppServer {
broker_peer: Option<BrokerPeer>, broker_peer: Option<BrokerPeer>,
hostname: Option<String>, hostname: Option<String>,
protocol_version: ProtocolVersion, protocol_version: ProtocolVersion,
osk_domain_separator: OskDomainSeparator,
) -> anyhow::Result<AppPeerPtr> { ) -> anyhow::Result<AppPeerPtr> {
let PeerPtr(pn) = match &mut self.crypto_site { let PeerPtr(pn) = match &mut self.crypto_site {
ConstructionSite::Void => bail!("Crypto server construction site is void"), ConstructionSite::Void => bail!("Crypto server construction site is void"),
ConstructionSite::Builder(builder) => builder.add_peer(psk, pk, protocol_version), ConstructionSite::Builder(builder) => {
ConstructionSite::Product(srv) => srv.add_peer(psk, pk, protocol_version.into())?, 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()); assert!(pn == self.peers.len());

View File

@@ -491,6 +491,7 @@ impl CliArgs {
broker_peer, broker_peer,
cfg_peer.endpoint.clone(), cfg_peer.endpoint.clone(),
cfg_peer.protocol_version.into(), cfg_peer.protocol_version.into(),
cfg_peer.osk_domain_separator.try_into()?,
)?; )?;
} }

View File

@@ -18,6 +18,7 @@ use serde::{Deserialize, Serialize};
use rosenpass_util::file::{fopen_w, LoadValue, Visibility}; use rosenpass_util::file::{fopen_w, LoadValue, Visibility};
use crate::protocol::basic_types::{SPk, SSk}; use crate::protocol::basic_types::{SPk, SSk};
use crate::protocol::osk_domain_separator::OskDomainSeparator;
use crate::app_server::AppServer; use crate::app_server::AppServer;
@@ -153,6 +154,73 @@ pub struct RosenpassPeer {
#[serde(default)] #[serde(default)]
/// The protocol version to use for the exchange /// The protocol version to use for the exchange
pub protocol_version: ProtocolVersion, 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<String>,
// 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<Vec<String>>,
}
impl RosenpassPeerOskDomainSeparator {
pub fn org_and_label(&self) -> anyhow::Result<Option<(&String, &Vec<String>)>> {
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<RosenpassPeerOskDomainSeparator> for OskDomainSeparator {
type Error = anyhow::Error;
fn try_from(val: RosenpassPeerOskDomainSeparator) -> anyhow::Result<Self> {
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 /// 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(()) Ok(())

View File

@@ -295,25 +295,21 @@ hash_domain_ns!(
/// We do recommend that third parties base their specific domain separators /// We do recommend that third parties base their specific domain separators
/// on a internet domain and/or mix in much more specific information. /// 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]. /// See [_ckextract].
/// ///
/// # Examples /// # Examples
/// ///
/// See the [module](self) documentation on how to use the hash domains in general. /// See the [module](self) documentation on how to use the hash domains in general.
_ckextract, _user, "user"); _ckextract, cke_user, "user");
hash_domain_ns!( hash_domain_ns!(
/// Chaining key domain separator for any rosenpass specific purposes. /// 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]. /// See [_ckextract].
/// ///
/// # Examples /// # Examples
/// ///
/// See the [module](self) documentation on how to use the hash domains in general. /// 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!( hash_domain!(
/// Chaining key domain separator for deriving the key sent to WireGuard. /// Chaining key domain separator for deriving the key sent to WireGuard.
/// ///
@@ -325,4 +321,4 @@ hash_domain!(
/// Check out its source code! /// Check out its source code!
/// ///
/// See the [module](self) documentation on how to use the hash domains in general. /// 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");

View File

@@ -18,6 +18,8 @@ pub type ESk = Secret<{ EphemeralKem::SK_LEN }>;
/// Symmetric key /// Symmetric key
pub type SymKey = Secret<KEY_LEN>; pub type SymKey = Secret<KEY_LEN>;
/// 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)) /// Peer ID (derived from the public key, see the hash derivations in the [whitepaper](https://rosenpass.eu/whitepaper.pdf))
pub type PeerId = Public<KEY_LEN>; pub type PeerId = Public<KEY_LEN>;

View File

@@ -6,6 +6,7 @@ use rosenpass_util::{build::Build, result::ensure_or};
use crate::config::ProtocolVersion; use crate::config::ProtocolVersion;
use super::basic_types::{SPk, SSk, SymKey}; use super::basic_types::{SPk, SSk, SymKey};
use super::osk_domain_separator::OskDomainSeparator;
use super::{CryptoServer, PeerPtr}; use super::{CryptoServer, PeerPtr};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -148,20 +149,23 @@ pub struct MissingKeypair;
/// ///
/// ```rust /// ```rust
/// use rosenpass_util::build::Build; /// use rosenpass_util::build::Build;
/// use rosenpass::protocol::basic_types::{SPk, SymKey}; /// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
/// use rosenpass::protocol::{BuildCryptoServer, Keypair, PeerParams}; ///
/// use rosenpass::config::ProtocolVersion; /// 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. /// // 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(); /// secret_policy_use_only_malloc_secrets();
/// ///
/// let keypair = Keypair::random(); /// let keypair = Keypair::random();
/// let peer1 = PeerParams { psk: Some(SymKey::random()), 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 }; /// 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]); /// 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"); /// let server = builder.build().expect("build failed");
/// assert_eq!(server.peers.len(), 2); /// assert_eq!(server.peers.len(), 2);
@@ -191,16 +195,17 @@ impl Build<CryptoServer> for BuildCryptoServer {
let mut srv = CryptoServer::new(sk, pk); let mut srv = CryptoServer::new(sk, pk);
for ( for (idx, params) in self.peers.into_iter().enumerate() {
idx, let PeerParams {
PeerParams {
psk, psk,
pk, pk,
protocol_version, protocol_version,
}, osk_domain_separator,
) in self.peers.into_iter().enumerate() } = params;
{
let PeerPtr(idx2) = srv.add_peer(psk, pk, protocol_version.into())?; 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.") 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, pub pk: SPk,
/// The used protocol version. /// The used protocol version.
pub protocol_version: ProtocolVersion, pub protocol_version: ProtocolVersion,
pub osk_domain_separator: OskDomainSeparator,
} }
impl BuildCryptoServer { impl BuildCryptoServer {
@@ -321,13 +327,15 @@ impl BuildCryptoServer {
/// ///
/// ```rust /// ```rust
/// use rosenpass::config::ProtocolVersion; /// 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_util::build::Build;
/// use rosenpass::protocol::basic_types::{SymKey, SPk}; /// use rosenpass::protocol::basic_types::{SymKey, SPk};
/// use rosenpass::protocol::{BuildCryptoServer, Keypair}; /// 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 /// // Deferred initialization: Create builder first, add some peers later
/// let keypair_option = Some(Keypair::random()); /// 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 /// // Now we've found a peer that should be added to the configuration
/// let pre_shared_key = SymKey::random(); /// let pre_shared_key = SymKey::random();
/// let public_key = SPk::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 /// // New server instances will then start with the peer being registered already
/// let server = builder.build().expect("build failed"); /// let server = builder.build().expect("build failed");
@@ -355,12 +363,14 @@ impl BuildCryptoServer {
psk: Option<SymKey>, psk: Option<SymKey>,
pk: SPk, pk: SPk,
protocol_version: ProtocolVersion, protocol_version: ProtocolVersion,
osk_domain_separator: OskDomainSeparator,
) -> &mut Self { ) -> &mut Self {
// TODO: Check here already whether peer was already added // TODO: Check here already whether peer was already added
self.peers.push(PeerParams { self.peers.push(PeerParams {
psk, psk,
pk, pk,
protocol_version, protocol_version,
osk_domain_separator,
}); });
self self
} }
@@ -371,9 +381,10 @@ impl BuildCryptoServer {
psk: Option<SymKey>, psk: Option<SymKey>,
pk: SPk, pk: SPk,
protocol_version: ProtocolVersion, protocol_version: ProtocolVersion,
osk_domain_separator: OskDomainSeparator,
) -> PeerPtr { ) -> PeerPtr {
let id = PeerPtr(self.peers.len()); 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 id
} }
@@ -394,6 +405,7 @@ impl BuildCryptoServer {
/// ///
/// use rosenpass::protocol::basic_types::{SymKey, SPk}; /// use rosenpass::protocol::basic_types::{SymKey, SPk};
/// use rosenpass::protocol::{BuildCryptoServer, Keypair}; /// use rosenpass::protocol::{BuildCryptoServer, Keypair};
/// use rosenpass::protocol::osk_domain_separator::OskDomainSeparator;
/// ///
/// // We have to define the security policy before using Secrets. /// // We have to define the security policy before using Secrets.
/// secret_policy_use_only_malloc_secrets(); /// secret_policy_use_only_malloc_secrets();
@@ -401,7 +413,7 @@ impl BuildCryptoServer {
/// let keypair = Keypair::random(); /// let keypair = Keypair::random();
/// let peer_pk = SPk::random(); /// let peer_pk = SPk::random();
/// let mut builder = BuildCryptoServer::new(Some(keypair.clone()), vec![]); /// 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 /// // Extract configuration parameters from the decomissioned builder
/// let (keypair_option, peers) = builder.take_parts(); /// let (keypair_option, peers) = builder.take_parts();

View File

@@ -31,6 +31,7 @@
//! //!
//! use rosenpass::protocol::basic_types::{SSk, SPk, MsgBuf, SymKey}; //! use rosenpass::protocol::basic_types::{SSk, SPk, MsgBuf, SymKey};
//! use rosenpass::protocol::{PeerPtr, CryptoServer}; //! use rosenpass::protocol::{PeerPtr, CryptoServer};
//! use rosenpass::protocol::osk_domain_separator::OskDomainSeparator;
//! //!
//! # fn main() -> anyhow::Result<()> { //! # fn main() -> anyhow::Result<()> {
//! // Set security policy for storing secrets //! // Set security policy for storing secrets
@@ -52,8 +53,8 @@
//! let mut b = CryptoServer::new(peer_b_sk, peer_b_pk.clone()); //! let mut b = CryptoServer::new(peer_b_sk, peer_b_pk.clone());
//! //!
//! // introduce peers to each other //! // introduce peers to each other
//! a.add_peer(Some(psk.clone()), peer_b_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)?; //! b.add_peer(Some(psk), peer_a_pk, ProtocolVersion::V03, OskDomainSeparator::default())?;
//! //!
//! // declare buffers for message exchange //! // declare buffers for message exchange
//! let (mut a_buf, mut b_buf) = (MsgBuf::zero(), MsgBuf::zero()); //! let (mut a_buf, mut b_buf) = (MsgBuf::zero(), MsgBuf::zero());
@@ -84,6 +85,7 @@ pub mod basic_types;
pub mod constants; pub mod constants;
pub mod cookies; pub mod cookies;
pub mod index; pub mod index;
pub mod osk_domain_separator;
pub mod testutils; pub mod testutils;
pub mod timing; pub mod timing;
pub mod zerocopy; pub mod zerocopy;

View File

@@ -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<u8>,
/// Any custom labels within that namespace. Could be descriptive prose.
labels: Vec<Vec<u8>>,
},
}
impl OskDomainSeparator {
/// Construct [OskDomainSeparator::ExtensionWireguardPsk]
pub fn for_wireguard_psk() -> Self {
Self::ExtensionWireguardPsk
}
/// Construct [OskDomainSeparator::Custom] from strings
pub fn custom_utf8<I, T>(namespace: &str, label: I) -> Self
where
I: IntoIterator<Item = T>,
T: AsRef<str>,
{
let namespace = namespace.as_bytes().to_owned();
let labels = label
.into_iter()
.map(|e| e.as_ref().as_bytes().to_owned())
.collect::<Vec<_>>();
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<PublicSymKey> {
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(),
}
}
}

View File

@@ -36,7 +36,8 @@ use rosenpass_util::{
use crate::{hash_domains, msgs::*, RosenpassError}; use crate::{hash_domains, msgs::*, RosenpassError};
use super::basic_types::{ 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::{ use super::constants::{
BISCUIT_EPOCH, COOKIE_SECRET_EPOCH, COOKIE_SECRET_LEN, COOKIE_VALUE_LEN, 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::cookies::{BiscuitKey, CookieSecret, CookieStore};
use super::index::{PeerIndex, PeerIndexKey}; use super::index::{PeerIndex, PeerIndexKey};
use super::osk_domain_separator::OskDomainSeparator;
use super::timing::{has_happened, Timing, BCE, UNENDING}; use super::timing::{has_happened, Timing, BCE, UNENDING};
use super::zerocopy::{truncating_cast_into, truncating_cast_into_nomut}; use super::zerocopy::{truncating_cast_into, truncating_cast_into_nomut};
@@ -179,6 +181,7 @@ impl From<crate::config::ProtocolVersion> for ProtocolVersion {
/// ///
/// use rosenpass::protocol::basic_types::{SSk, SPk, SymKey}; /// use rosenpass::protocol::basic_types::{SSk, SPk, SymKey};
/// use rosenpass::protocol::{Peer, ProtocolVersion}; /// use rosenpass::protocol::{Peer, ProtocolVersion};
/// use rosenpass::protocol::osk_domain_separator::OskDomainSeparator;
/// ///
/// rosenpass_secret_memory::secret_policy_try_use_memfd_secrets(); /// rosenpass_secret_memory::secret_policy_try_use_memfd_secrets();
/// ///
@@ -191,13 +194,13 @@ impl From<crate::config::ProtocolVersion> for ProtocolVersion {
/// let psk = SymKey::random(); /// let psk = SymKey::random();
/// ///
/// // Creation with a PSK /// // 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 /// // 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 /// // 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 /// // Peer ID does not depend on PSK, but it does depend on the public key
/// assert_eq!(peer_psk.pidt()?, peer_nopsk.pidt()?); /// 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 /// 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. /// on the network without having to account for it in the cryptographic code itself.
pub known_init_conf_response: Option<KnownInitConfResponse>, pub known_init_conf_response: Option<KnownInitConfResponse>,
/// The protocol version used by with this peer. /// The protocol version used by with this peer.
pub protocol_version: ProtocolVersion, pub protocol_version: ProtocolVersion,
/// Domain separator for generated OSKs
pub osk_domain_separator: OskDomainSeparator,
} }
impl Peer { impl Peer {
@@ -282,6 +286,7 @@ impl Peer {
handshake: None, handshake: None,
known_init_conf_response: None, known_init_conf_response: None,
protocol_version, protocol_version,
osk_domain_separator: OskDomainSeparator::default(),
} }
} }
} }
@@ -1222,6 +1227,7 @@ impl CryptoServer {
/// ``` /// ```
/// use std::ops::DerefMut; /// use std::ops::DerefMut;
/// use rosenpass::protocol::basic_types::{SSk, SPk, SymKey}; /// use rosenpass::protocol::basic_types::{SSk, SPk, SymKey};
/// use rosenpass::protocol::osk_domain_separator::OskDomainSeparator;
/// use rosenpass::protocol::{CryptoServer, ProtocolVersion}; /// use rosenpass::protocol::{CryptoServer, ProtocolVersion};
/// use rosenpass_ciphers::StaticKem; /// use rosenpass_ciphers::StaticKem;
/// use rosenpass_cipher_traits::primitives::Kem; /// use rosenpass_cipher_traits::primitives::Kem;
@@ -1237,7 +1243,7 @@ impl CryptoServer {
/// ///
/// let psk = SymKey::random(); /// let psk = SymKey::random();
/// // We use the latest protocol version for the example. /// // 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); /// assert_eq!(peer.get(&srv).spkt, spkt);
/// ///
@@ -1248,6 +1254,7 @@ impl CryptoServer {
psk: Option<SymKey>, psk: Option<SymKey>,
pk: SPk, pk: SPk,
protocol_version: ProtocolVersion, protocol_version: ProtocolVersion,
osk_domain_separator: OskDomainSeparator,
) -> Result<PeerPtr> { ) -> Result<PeerPtr> {
let peer = Peer { let peer = Peer {
psk: psk.unwrap_or_else(SymKey::zero), psk: psk.unwrap_or_else(SymKey::zero),
@@ -1258,6 +1265,7 @@ impl CryptoServer {
known_init_conf_response: None, known_init_conf_response: None,
initiation_requested: false, initiation_requested: false,
protocol_version, protocol_version,
osk_domain_separator,
}; };
let peerid = peer.pidt()?; let peerid = peer.pidt()?;
let peerno = self.peers.len(); let peerno = self.peers.len();
@@ -1447,7 +1455,12 @@ impl Peer {
/// # Examples /// # Examples
/// ///
/// See example in [Self]. /// 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 { Peer {
psk, psk,
spkt: pk, spkt: pk,
@@ -1457,6 +1470,7 @@ impl Peer {
known_init_conf_response: None, known_init_conf_response: None,
initiation_requested: false, initiation_requested: false,
protocol_version, protocol_version,
osk_domain_separator,
} }
} }
@@ -3310,28 +3324,61 @@ impl HandshakeState {
} }
impl CryptoServer { 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<SymKey> {
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<SymKey> {
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 /// Get the shared key that was established with given peer
/// ///
/// Fail if no session is available with the peer /// Fail if no session is available with the peer
/// ///
/// # See also
///
/// - [Self::osk_with_domain_separator]
/// - [Self::osk_with_compressed_domain_separator]
///
/// # Examples /// # Examples
/// ///
/// See the example in [crate::protocol] for an incomplete but working example /// See the example in [crate::protocol] for an incomplete but working example
/// of how to perform a key exchange using Rosenpass. /// of how to perform a key exchange using Rosenpass.
/// ///
/// See the example in [CryptoServer::poll] for a complete example. /// 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<SymKey> { pub fn osk(&self, peer: PeerPtr) -> Result<SymKey> {
let session = peer let hash_choice = peer.get(self).protocol_version.keyed_hash();
.session() let compressed_domain_separator = peer
.get(self) .get(self)
.as_ref() .osk_domain_separator
.with_context(|| format!("No current session for peer {:?}", peer))?; .compress_with(hash_choice)?;
Ok(session self.osk_with_compressed_domain_separator(peer, &compressed_domain_separator)
.ck
.mix(&hash_domains::osk(
peer.get(self).protocol_version.keyed_hash(),
)?)?
.into_secret())
} }
} }

View File

@@ -13,6 +13,7 @@ use crate::msgs::{EmptyData, Envelope, InitConf, InitHello, MsgType, RespHello,
use super::basic_types::{MsgBuf, SPk, SSk, SymKey}; use super::basic_types::{MsgBuf, SPk, SSk, SymKey};
use super::constants::REKEY_AFTER_TIME_RESPONDER; 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::zerocopy::{truncating_cast_into, truncating_cast_into_nomut};
use super::{ use super::{
CryptoServer, HandleMsgResult, HostIdentification, KnownInitConfResponsePtr, PeerPtr, 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(ska, pka.clone()),
CryptoServer::new(skb, pkb.clone()), CryptoServer::new(skb, pkb.clone()),
); );
a.add_peer(Some(psk.clone()), pkb, protocol_version.clone())?; a.add_peer(
b.add_peer(Some(psk), pka, protocol_version)?; Some(psk.clone()),
pkb,
protocol_version.clone(),
OskDomainSeparator::default(),
)?;
b.add_peer(
Some(psk),
pka,
protocol_version,
OskDomainSeparator::default(),
)?;
Ok((a, b)) Ok((a, b))
} }
@@ -619,8 +630,18 @@ fn init_conf_retransmission(protocol_version: ProtocolVersion) -> anyhow::Result
let mut b = CryptoServer::new(skb, pkb.clone()); let mut b = CryptoServer::new(skb, pkb.clone());
// introduce peers to each other // introduce peers to each other
let b_peer = a.add_peer(None, pkb, protocol_version.clone())?; let b_peer = a.add_peer(
let a_peer = b.add_peer(None, pka, protocol_version.clone())?; 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) // Execute protocol up till the responder confirmation (EmptyData)
let ih1 = proc_initiation(&mut a, b_peer)?; let ih1 = proc_initiation(&mut a, b_peer)?;

View File

@@ -7,6 +7,7 @@ use rosenpass_ciphers::StaticKem;
use super::{ use super::{
basic_types::{SPk, SSk}, basic_types::{SPk, SSk},
osk_domain_separator::OskDomainSeparator,
CryptoServer, PeerPtr, ProtocolVersion, CryptoServer, PeerPtr, ProtocolVersion,
}; };
@@ -26,7 +27,12 @@ impl ServerForTesting {
let (mut sskt, mut spkt) = (SSk::zero(), SPk::zero()); let (mut sskt, mut spkt) = (SSk::zero(), SPk::zero());
StaticKem.keygen(sskt.secret_mut(), spkt.deref_mut())?; 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); let peer_keys = (sskt, spkt);
Ok(ServerForTesting { Ok(ServerForTesting {

View File

@@ -106,6 +106,7 @@ fn api_integration_api_setup(protocol_version: ProtocolVersion) -> anyhow::Resul
extra_params: vec![], extra_params: vec![],
}), }),
protocol_version: protocol_version.clone(), 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, pre_shared_key: None,
wg: None, wg: None,
protocol_version: protocol_version.clone(), protocol_version: protocol_version.clone(),
osk_domain_separator: Default::default(),
}], }],
}; };

View File

@@ -83,6 +83,7 @@ fn api_integration_test(protocol_version: ProtocolVersion) -> anyhow::Result<()>
pre_shared_key: None, pre_shared_key: None,
wg: None, wg: None,
protocol_version: protocol_version.clone(), 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, pre_shared_key: None,
wg: None, wg: None,
protocol_version: protocol_version.clone(), protocol_version: protocol_version.clone(),
osk_domain_separator: Default::default(),
}], }],
}; };

View File

@@ -6,8 +6,8 @@ use rosenpass_ciphers::StaticKem;
use rosenpass_util::{file::LoadValueB64, functional::run, mem::DiscardResultExt, result::OkExt}; use rosenpass_util::{file::LoadValueB64, functional::run, mem::DiscardResultExt, result::OkExt};
use rosenpass::app_server::{AppServer, AppServerTest, MAX_B64_KEY_SIZE}; use rosenpass::app_server::{AppServer, AppServerTest, MAX_B64_KEY_SIZE};
use rosenpass::config::ProtocolVersion;
use rosenpass::protocol::basic_types::{SPk, SSk, SymKey}; use rosenpass::protocol::basic_types::{SPk, SSk, SymKey};
use rosenpass::{config::ProtocolVersion, protocol::osk_domain_separator::OskDomainSeparator};
#[test] #[test]
fn key_exchange_with_app_server_v02() -> anyhow::Result<()> { 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, outfile,
broker_peer, broker_peer,
hostname, hostname,
protocol_version.clone(), protocol_version,
OskDomainSeparator::default(),
)?; )?;
srv.app_srv.event_loop() srv.app_srv.event_loop()

View File

@@ -10,6 +10,7 @@ use rosenpass_ciphers::StaticKem;
use rosenpass_util::result::OkExt; use rosenpass_util::result::OkExt;
use rosenpass::protocol::basic_types::{MsgBuf, SPk, SSk, SymKey}; 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::testutils::time_travel_forward;
use rosenpass::protocol::timing::{Timing, UNENDING}; use rosenpass::protocol::timing::{Timing, UNENDING};
use rosenpass::protocol::{CryptoServer, HostIdentification, PeerPtr, PollResult, ProtocolVersion}; use rosenpass::protocol::{CryptoServer, HostIdentification, PeerPtr, PollResult, ProtocolVersion};
@@ -19,19 +20,38 @@ use rosenpass::protocol::{CryptoServer, HostIdentification, PeerPtr, PollResult,
#[test] #[test]
fn test_successful_exchange_with_poll_v02() -> anyhow::Result<()> { 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] #[test]
fn test_successful_exchange_with_poll_v03() -> anyhow::Result<()> { 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 // Set security policy for storing secrets; choose the one that is faster for testing
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets(); 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 sim.poll_loop(150)?; // Poll 75 times
let transcript = sim.transcript; 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(); rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
// Create the simulator // 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 // Make sure the servers are set to under load condition
sim.srv_a.under_load = true; sim.srv_a.under_load = true;
@@ -181,6 +201,94 @@ fn test_successful_exchange_under_packet_loss(
Ok(()) 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; type MessageType = u8;
/// Lets record the events that are produced by Rosenpass /// Lets record the events that are produced by Rosenpass
@@ -193,6 +301,7 @@ enum TranscriptEvent {
event: ServerEvent, event: ServerEvent,
}, },
CompletedExchange(SymKey), CompletedExchange(SymKey),
FailedExchangeWithKeyMismatch(SymKey, SymKey),
} }
#[derive(Debug)] #[derive(Debug)]
@@ -292,7 +401,10 @@ struct SimulatorServer {
impl RosenpassSimulator { impl RosenpassSimulator {
/// Set up the simulator /// Set up the simulator
fn new(protocol_version: ProtocolVersion) -> anyhow::Result<Self> { fn new(
protocol_version: ProtocolVersion,
osk_domain_separator: OskDomainSeparator,
) -> anyhow::Result<Self> {
// Set up the first server // Set up the first server
let (mut peer_a_sk, mut peer_a_pk) = (SSk::zero(), SPk::zero()); 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())?; 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. // Generate a PSK and introduce the Peers to each other.
let psk = SymKey::random(); let psk = SymKey::random();
let peer_a = srv_a.add_peer(Some(psk.clone()), peer_b_pk, protocol_version.clone())?; let peer_a = srv_a.add_peer(
let peer_b = srv_b.add_peer(Some(psk), peer_a_pk, protocol_version.clone())?; 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 // Set up the individual server data structures
let srv_a = SimulatorServer::new(srv_a, peer_b); let srv_a = SimulatorServer::new(srv_a, peer_b);
@@ -566,10 +688,18 @@ impl ServerPtr {
None => return Ok(()), 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; // Issue the successful exchange event if the OSKs are equal;
// be careful to use constant time comparison for things like this! // be careful to use constant time comparison for things like this!
if rosenpass_constant_time::memcmp(osk.secret(), other_osk.secret()) { if rosenpass_constant_time::memcmp(osk_a.secret(), osk_b.secret()) {
self.enqueue_upcoming_poll_event(sim, TE::CompletedExchange(osk)); self.enqueue_upcoming_poll_event(sim, TE::CompletedExchange(osk_a));
} else {
self.enqueue_upcoming_poll_event(sim, TE::FailedExchangeWithKeyMismatch(osk_a, osk_b));
} }
Ok(()) Ok(())

View File

@@ -208,7 +208,10 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> {
use rosenpass::{ use rosenpass::{
app_server::{AppServer, BrokerPeer}, app_server::{AppServer, BrokerPeer},
config::Verbosity, 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_secret_memory::Secret;
use rosenpass_util::file::{LoadValue as _, LoadValueB64}; use rosenpass_util::file::{LoadValue as _, LoadValueB64};
@@ -362,6 +365,7 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> {
broker_peer, broker_peer,
peer.endpoint.map(|x| x.to_string()), peer.endpoint.map(|x| x.to_string()),
peer.protocol_version, peer.protocol_version,
OskDomainSeparator::for_wireguard_psk(),
)?; )?;
// Configure routes, equivalent to `ip route replace <allowed_ips> dev <dev>` and set up // Configure routes, equivalent to `ip route replace <allowed_ips> dev <dev>` and set up

View File

@@ -561,7 +561,7 @@ mod tests {
let mut file = FdIo(open_nullfd()?); let mut file = FdIo(open_nullfd()?);
let mut buf = [0; 10]; let mut buf = [0; 10];
assert!(matches!(file.read(&mut buf), Ok(0) | Err(_))); assert!(matches!(file.read(&mut buf), Ok(0) | Err(_)));
assert!(matches!(file.write(&buf), Err(_))); assert!(file.write(&buf).is_err());
Ok(()) Ok(())
} }

View File

@@ -618,7 +618,7 @@ mod tests {
#[test] #[test]
fn test_lpe_error_conversion_downcast_invalid() { fn test_lpe_error_conversion_downcast_invalid() {
let pos_error = PositionOutOfBufferBounds; 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) { match MessageLenSanityError::try_from(sanity_error) {
Ok(_) => panic!("Conversion should always fail (incompatible enum variant)"), Ok(_) => panic!("Conversion should always fail (incompatible enum variant)"),
Err(err) => assert!(matches!(err, PositionOutOfBufferBounds)), Err(err) => assert!(matches!(err, PositionOutOfBufferBounds)),

View File

@@ -302,6 +302,6 @@ mod test_forgetting {
drop_was_called.store(false, SeqCst); drop_was_called.store(false, SeqCst);
let forgetting = Forgetting::new(SetFlagOnDrop(drop_was_called.clone())); let forgetting = Forgetting::new(SetFlagOnDrop(drop_was_called.clone()));
drop(forgetting); drop(forgetting);
assert_eq!(drop_was_called.load(SeqCst), false); assert!(!drop_was_called.load(SeqCst));
} }
} }