mirror of
https://github.com/rosenpass/rosenpass.git
synced 2025-12-16 20:37:06 +03:00
feat: Support for custom osk (output key) domain separators in Rosenpass app
Some checks failed
rosenpass-ciphers - primitives - benchmark / prim-benchmark (i686-linux) (push) Has been cancelled
rosenpass-ciphers - primitives - benchmark / prim-benchmark (x86_64-linux) (push) Has been cancelled
rosenpass - protocol - benchmark / proto-benchmark (i686-linux) (push) Has been cancelled
rosenpass - protocol - benchmark / proto-benchmark (x86_64-linux) (push) Has been cancelled
rosenpass-ciphers - primitives - benchmark / ciphers-primitives-bench-status (push) Has been cancelled
rosenpass - protocol - benchmark / ciphers-protocol-bench-status (push) Has been cancelled
Some checks failed
rosenpass-ciphers - primitives - benchmark / prim-benchmark (i686-linux) (push) Has been cancelled
rosenpass-ciphers - primitives - benchmark / prim-benchmark (x86_64-linux) (push) Has been cancelled
rosenpass - protocol - benchmark / proto-benchmark (i686-linux) (push) Has been cancelled
rosenpass - protocol - benchmark / proto-benchmark (x86_64-linux) (push) Has been cancelled
rosenpass-ciphers - primitives - benchmark / ciphers-primitives-bench-status (push) Has been cancelled
rosenpass - protocol - benchmark / ciphers-protocol-bench-status (push) Has been cancelled
This allows for custom protocol extensions with custom domain separators to be used without modifying the Rosenpass source code
This commit is contained in:
@@ -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<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`
|
||||
/// 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<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`
|
||||
/// by calling [SecretHashDomain::invoke_primitive] with the key of this
|
||||
/// [HashDomainNamespace] as `k` and `v` as `d`.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
|
||||
@@ -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<SymKey>,
|
||||
@@ -1021,11 +1023,16 @@ impl AppServer {
|
||||
broker_peer: Option<BrokerPeer>,
|
||||
hostname: Option<String>,
|
||||
protocol_version: ProtocolVersion,
|
||||
osk_domain_separator: OskDomainSeparator,
|
||||
) -> anyhow::Result<AppPeerPtr> {
|
||||
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());
|
||||
|
||||
|
||||
@@ -491,6 +491,7 @@ impl CliArgs {
|
||||
broker_peer,
|
||||
cfg_peer.endpoint.clone(),
|
||||
cfg_peer.protocol_version.into(),
|
||||
cfg_peer.osk_domain_separator.try_into()?,
|
||||
)?;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<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
|
||||
@@ -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(())
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -18,6 +18,8 @@ pub type ESk = Secret<{ EphemeralKem::SK_LEN }>;
|
||||
|
||||
/// Symmetric key
|
||||
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))
|
||||
pub type PeerId = Public<KEY_LEN>;
|
||||
|
||||
@@ -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<CryptoServer> 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<SymKey>,
|
||||
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<SymKey>,
|
||||
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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
91
rosenpass/src/protocol/osk_domain_separator.rs
Normal file
91
rosenpass/src/protocol/osk_domain_separator.rs
Normal 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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<crate::config::ProtocolVersion> 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<crate::config::ProtocolVersion> 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<KnownInitConfResponse>,
|
||||
|
||||
/// 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<SymKey>,
|
||||
pk: SPk,
|
||||
protocol_version: ProtocolVersion,
|
||||
osk_domain_separator: OskDomainSeparator,
|
||||
) -> Result<PeerPtr> {
|
||||
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<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
|
||||
///
|
||||
/// 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<SymKey> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(),
|
||||
}],
|
||||
};
|
||||
|
||||
|
||||
@@ -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(),
|
||||
}],
|
||||
};
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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<Self> {
|
||||
fn new(
|
||||
protocol_version: ProtocolVersion,
|
||||
osk_domain_separator: OskDomainSeparator,
|
||||
) -> anyhow::Result<Self> {
|
||||
// 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(())
|
||||
|
||||
@@ -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 <allowed_ips> dev <dev>` and set up
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user