dev(rosenpass): add support for the shake256 hash function in the rosenpass crate

This commit is contained in:
David Niehues
2025-02-14 16:29:56 +01:00
parent 30e158f594
commit 2d2d109246
14 changed files with 435 additions and 184 deletions

View File

@@ -1,5 +1,5 @@
use anyhow::Result; use anyhow::Result;
use rosenpass::protocol::{CryptoServer, HandleMsgResult, MsgBuf, PeerPtr, SPk, SSk, SymKey}; use rosenpass::protocol::{CryptoServer, HandleMsgResult, MsgBuf, PeerPtr, ProtocolVersion, SPk, SSk, SymKey};
use std::ops::DerefMut; use std::ops::DerefMut;
use rosenpass_cipher_traits::Kem; use rosenpass_cipher_traits::Kem;
@@ -45,21 +45,30 @@ fn keygen() -> Result<(SSk, SPk)> {
Ok((sk, pk)) Ok((sk, pk))
} }
fn make_server_pair() -> Result<(CryptoServer, CryptoServer)> { fn make_server_pair(protocol_version: ProtocolVersion) -> Result<(CryptoServer, CryptoServer)> {
let psk = SymKey::random(); let psk = SymKey::random();
let ((ska, pka), (skb, pkb)) = (keygen()?, keygen()?); let ((ska, pka), (skb, pkb)) = (keygen()?, keygen()?);
let (mut a, mut b) = ( let (mut a, mut b) = (
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)?; // TODO: Adapt this to both protocol versions
b.add_peer(Some(psk), pka)?; a.add_peer(Some(psk.clone()), pkb, protocol_version.clone())?;
b.add_peer(Some(psk), pka, protocol_version)?;
Ok((a, b)) Ok((a, b))
} }
fn criterion_benchmark(c: &mut Criterion) { fn criterion_benchmark_v02(c: &mut Criterion) {
criterion_benchmark(c, ProtocolVersion::V02)
}
fn criterion_benchmark_v03(c: &mut Criterion) {
criterion_benchmark(c, ProtocolVersion::V03)
}
fn criterion_benchmark(c: &mut Criterion, protocol_version: ProtocolVersion) {
secret_policy_try_use_memfd_secrets(); secret_policy_try_use_memfd_secrets();
let (mut a, mut b) = make_server_pair().unwrap(); let (mut a, mut b) = make_server_pair(protocol_version).unwrap();
c.bench_function("cca_secret_alloc", |bench| { c.bench_function("cca_secret_alloc", |bench| {
bench.iter(|| { bench.iter(|| {
SSk::zero(); SSk::zero();
@@ -82,5 +91,6 @@ fn criterion_benchmark(c: &mut Criterion) {
}); });
} }
criterion_group!(benches, criterion_benchmark); criterion_group!(benches_v02, criterion_benchmark_v02);
criterion_main!(benches); criterion_group!(benches_v03, criterion_benchmark_v03);
criterion_main!(benches_v02, benches_v03);

View File

@@ -50,6 +50,7 @@ use crate::{
}; };
use rosenpass_util::attempt; use rosenpass_util::attempt;
use rosenpass_util::b64::B64Display; use rosenpass_util::b64::B64Display;
use crate::config::ProtocolVersion;
/// The maximum size of a base64 encoded symmetric key (estimate) /// The maximum size of a base64 encoded symmetric key (estimate)
pub const MAX_B64_KEY_SIZE: usize = 32 * 5 / 3; pub const MAX_B64_KEY_SIZE: usize = 32 * 5 / 3;
@@ -1042,11 +1043,12 @@ impl AppServer {
outfile: Option<PathBuf>, outfile: Option<PathBuf>,
broker_peer: Option<BrokerPeer>, broker_peer: Option<BrokerPeer>,
hostname: Option<String>, hostname: Option<String>,
protocol_version: ProtocolVersion
) -> 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), ConstructionSite::Builder(builder) => builder.add_peer(psk, pk, protocol_version),
ConstructionSite::Product(srv) => srv.add_peer(psk, pk)?, ConstructionSite::Product(srv) => srv.add_peer(psk, pk, protocol_version.into())?,
}; };
assert!(pn == self.peers.len()); assert!(pn == self.peers.len());

View File

@@ -2,6 +2,9 @@ use anyhow::{Context, Result};
use heck::ToShoutySnakeCase; use heck::ToShoutySnakeCase;
use rosenpass_ciphers::{hash_domain::HashDomain, KEY_LEN}; use rosenpass_ciphers::{hash_domain::HashDomain, KEY_LEN};
use rosenpass_ciphers::subtle::either_hash::EitherShakeOrBlake;
use rosenpass_ciphers::subtle::incorrect_hmac_blake2b::Blake2bCore;
use rosenpass_ciphers::subtle::keyed_shake256::SHAKE256Core;
/// Recursively calculate a concrete hash value for an API message type /// Recursively calculate a concrete hash value for an API message type
fn calculate_hash_value(hd: HashDomain, values: &[&str]) -> Result<[u8; KEY_LEN]> { fn calculate_hash_value(hd: HashDomain, values: &[&str]) -> Result<[u8; KEY_LEN]> {
@@ -12,8 +15,8 @@ fn calculate_hash_value(hd: HashDomain, values: &[&str]) -> Result<[u8; KEY_LEN]
} }
/// Print a hash literal for pasting into the Rosenpass source code /// Print a hash literal for pasting into the Rosenpass source code
fn print_literal(path: &[&str]) -> Result<()> { fn print_literal(path: &[&str], shake_or_blake: EitherShakeOrBlake) -> Result<()> {
let val = calculate_hash_value(HashDomain::zero(), path)?; let val = calculate_hash_value(HashDomain::zero(shake_or_blake), path)?;
let (last, prefix) = path.split_last().context("developer error!")?; let (last, prefix) = path.split_last().context("developer error!")?;
let var_name = last.to_shouty_snake_case(); let var_name = last.to_shouty_snake_case();
@@ -51,31 +54,33 @@ impl Tree {
} }
} }
fn gen_code_inner(&self, prefix: &[&str]) -> Result<()> { fn gen_code_inner(&self, prefix: &[&str], shake_or_blake: EitherShakeOrBlake) -> Result<()> {
let mut path = prefix.to_owned(); let mut path = prefix.to_owned();
path.push(self.name()); path.push(self.name());
match self { match self {
Self::Branch(_, ref children) => { Self::Branch(_, ref children) => {
for c in children.iter() { for c in children.iter() {
c.gen_code_inner(&path)? c.gen_code_inner(&path, shake_or_blake.clone())?
} }
} }
Self::Leaf(_) => print_literal(&path)?, Self::Leaf(_) => print_literal(&path, shake_or_blake)?,
}; };
Ok(()) Ok(())
} }
fn gen_code(&self) -> Result<()> { fn gen_code(&self, shake_or_blake: EitherShakeOrBlake) -> Result<()> {
self.gen_code_inner(&[]) self.gen_code_inner(&[], shake_or_blake)
} }
} }
/// Helper for generating hash-based message IDs for the IPC API /// Helper for generating hash-based message IDs for the IPC API
fn main() -> Result<()> { fn main() -> Result<()> {
fn print_IPC_API_info(shake_or_blake: EitherShakeOrBlake, name: String) -> Result<()> {
let tree = Tree::Branch( let tree = Tree::Branch(
"Rosenpass IPC API".to_owned(), format!("Rosenpass IPC API {}", name).to_owned(),
vec![Tree::Branch( vec![Tree::Branch(
"Rosenpass Protocol Server".to_owned(), "Rosenpass Protocol Server".to_owned(),
vec![ vec![
@@ -93,5 +98,9 @@ fn main() -> Result<()> {
println!("type RawMsgType = u128;"); println!("type RawMsgType = u128;");
println!(); println!();
tree.gen_code() tree.gen_code(shake_or_blake)
}
print_IPC_API_info(EitherShakeOrBlake::Left(SHAKE256Core), " (SHAKE256)".to_owned())?;
print_IPC_API_info(EitherShakeOrBlake::Right(Blake2bCore), " (Blake2b)".to_owned())
} }

View File

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

View File

@@ -109,6 +109,14 @@ pub enum Verbosity {
Verbose, Verbose,
} }
/// TODO: Documentation
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Copy, Clone, Default)]
pub enum ProtocolVersion {
#[default]
V02,
V03,
}
/// Configuration data for a single Rosenpass peer /// Configuration data for a single Rosenpass peer
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct RosenpassPeer { pub struct RosenpassPeer {
@@ -138,6 +146,10 @@ pub struct RosenpassPeer {
/// Information for supplying exchanged keys directly to WireGuard /// Information for supplying exchanged keys directly to WireGuard
#[serde(flatten)] #[serde(flatten)]
pub wg: Option<WireGuard>, pub wg: Option<WireGuard>,
#[serde(default)]
/// The protocol version to use for the exchange
pub protocol_version: ProtocolVersion,
} }
/// Information for supplying exchanged keys directly to WireGuard /// Information for supplying exchanged keys directly to WireGuard

View File

@@ -13,6 +13,8 @@
//! use rosenpass::{hash_domain, hash_domain_ns}; //! use rosenpass::{hash_domain, hash_domain_ns};
//! use rosenpass::hash_domains::protocol; //! use rosenpass::hash_domains::protocol;
//! //!
//! use rosenpass_ciphers::subtle::either_hash::EitherShakeOrBlake;
//!
//! // Declaring a custom hash domain //! // Declaring a custom hash domain
//! hash_domain_ns!(protocol, custom_domain, "my custom hash domain label"); //! hash_domain_ns!(protocol, custom_domain, "my custom hash domain label");
//! //!
@@ -26,15 +28,18 @@
//! hash_domain!(domain_separators, sep1, "1"); //! hash_domain!(domain_separators, sep1, "1");
//! hash_domain!(domain_separators, sep2, "2"); //! hash_domain!(domain_separators, sep2, "2");
//! //!
//! // We use the SHAKE256 hash function for this example
//! let hash_choice = EitherShakeOrBlake::Left(rosenpass_ciphers::subtle::keyed_shake256::SHAKE256Core);
//!
//! // Generating values under hasher1 with both domain separators //! // Generating values under hasher1 with both domain separators
//! let h1 = hasher1()?.mix(b"some data")?.dup(); //! let h1 = hasher1(hash_choice.clone())?.mix(b"some data")?.dup();
//! let h1v1 = h1.mix(&sep1()?)?.mix(b"More data")?.into_value(); //! let h1v1 = h1.mix(&sep1(hash_choice.clone())?)?.mix(b"More data")?.into_value();
//! let h1v2 = h1.mix(&sep2()?)?.mix(b"More data")?.into_value(); //! let h1v2 = h1.mix(&sep2(hash_choice.clone())?)?.mix(b"More data")?.into_value();
//! //!
//! // Generating values under hasher2 with both domain separators //! // Generating values under hasher2 with both domain separators
//! let h2 = hasher2()?.mix(b"some data")?.dup(); //! let h2 = hasher2(hash_choice.clone())?.mix(b"some data")?.dup();
//! let h2v1 = h2.mix(&sep1()?)?.mix(b"More data")?.into_value(); //! let h2v1 = h2.mix(&sep1(hash_choice.clone())?)?.mix(b"More data")?.into_value();
//! let h2v2 = h2.mix(&sep2()?)?.mix(b"More data")?.into_value(); //! let h2v2 = h2.mix(&sep2(hash_choice.clone())?)?.mix(b"More data")?.into_value();
//! //!
//! // All of the domain separators are now different, random strings //! // All of the domain separators are now different, random strings
//! let values = [h1v1, h1v2, h2v1, h2v2]; //! let values = [h1v1, h1v2, h2v1, h2v2];
@@ -49,6 +54,9 @@
use anyhow::Result; use anyhow::Result;
use rosenpass_ciphers::hash_domain::HashDomain; use rosenpass_ciphers::hash_domain::HashDomain;
use rosenpass_ciphers::subtle::either_hash::EitherShakeOrBlake;
use rosenpass_ciphers::subtle::incorrect_hmac_blake2b::Blake2bCore;
use rosenpass_ciphers::subtle::keyed_shake256::SHAKE256Core;
/// Declare a hash function /// Declare a hash function
/// ///
@@ -62,8 +70,8 @@ use rosenpass_ciphers::hash_domain::HashDomain;
macro_rules! hash_domain_ns { macro_rules! hash_domain_ns {
($(#[$($attrss:tt)*])* $base:ident, $name:ident, $($lbl:expr),+ ) => { ($(#[$($attrss:tt)*])* $base:ident, $name:ident, $($lbl:expr),+ ) => {
$(#[$($attrss)*])* $(#[$($attrss)*])*
pub fn $name() -> ::anyhow::Result<::rosenpass_ciphers::hash_domain::HashDomain> { pub fn $name(hash_choice: EitherShakeOrBlake) -> ::anyhow::Result<::rosenpass_ciphers::hash_domain::HashDomain> {
let t = $base()?; let t = $base(hash_choice)?;
$( let t = t.mix($lbl.as_bytes())?; )* $( let t = t.mix($lbl.as_bytes())?; )*
Ok(t) Ok(t)
} }
@@ -81,8 +89,8 @@ macro_rules! hash_domain_ns {
macro_rules! hash_domain { macro_rules! hash_domain {
($(#[$($attrss:tt)*])* $base:ident, $name:ident, $($lbl:expr),+ ) => { ($(#[$($attrss:tt)*])* $base:ident, $name:ident, $($lbl:expr),+ ) => {
$(#[$($attrss)*])* $(#[$($attrss)*])*
pub fn $name() -> ::anyhow::Result<[u8; ::rosenpass_ciphers::KEY_LEN]> { pub fn $name(hash_choice: EitherShakeOrBlake) -> ::anyhow::Result<[u8; ::rosenpass_ciphers::KEY_LEN]> {
let t = $base()?; let t = $base(hash_choice)?;
$( let t = t.mix($lbl.as_bytes())?; )* $( let t = t.mix($lbl.as_bytes())?; )*
Ok(t.into_value()) Ok(t.into_value())
} }
@@ -96,13 +104,20 @@ macro_rules! hash_domain {
/// ///
/// This is generally used to create further hash-domains for specific purposes. See /// This is generally used to create further hash-domains for specific purposes. See
/// ///
/// TODO: Update documentation
///
/// # Examples /// # Examples
/// ///
/// See the source file for details about how this is used concretely. /// See the source file for details about how this is used concretely.
/// ///
/// See the [module](self) documentation on how to use the hash domains in general /// See the [module](self) documentation on how to use the hash domains in general
pub fn protocol() -> Result<HashDomain> { pub fn protocol(hash_choice: EitherShakeOrBlake) -> Result<HashDomain> {
HashDomain::zero().mix("Rosenpass v1 mceliece460896 Kyber512 ChaChaPoly1305 BLAKE2s".as_bytes()) // TODO: Update this string that is mixed in?
match hash_choice {
EitherShakeOrBlake::Left(SHAKE256Core) => HashDomain::zero(hash_choice).mix("Rosenpass v1 mceliece460896 Kyber512 ChaChaPoly1305 SHAKE256".as_bytes()),
EitherShakeOrBlake::Right(Blake2bCore) => HashDomain::zero(hash_choice).mix("Rosenpass v1 mceliece460896 Kyber512 ChaChaPoly1305 Blake2b".as_bytes()),
}
} }
hash_domain_ns!( hash_domain_ns!(

View File

@@ -4,7 +4,7 @@ use rosenpass_util::{
result::ensure_or, result::ensure_or,
}; };
use thiserror::Error; use thiserror::Error;
use crate::config::ProtocolVersion;
use super::{CryptoServer, PeerPtr, SPk, SSk, SymKey}; use super::{CryptoServer, PeerPtr, SPk, SSk, SymKey};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -146,17 +146,18 @@ pub struct MissingKeypair;
/// ```rust /// ```rust
/// use rosenpass_util::build::Build; /// use rosenpass_util::build::Build;
/// use rosenpass::protocol::{BuildCryptoServer, Keypair, PeerParams, SPk, SymKey}; /// use rosenpass::protocol::{BuildCryptoServer, Keypair, PeerParams, SPk, SymKey};
/// use rosenpass::config::ProtocolVersion;
/// ///
/// // 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; /// 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() }; /// let peer1 = PeerParams { psk: Some(SymKey::random()), pk: SPk::random(), protocol_version: ProtocolVersion::V02 };
/// let peer2 = PeerParams { psk: None, pk: SPk::random() }; /// let peer2 = PeerParams { psk: None, pk: SPk::random(), protocol_version: ProtocolVersion::V02 };
/// ///
/// 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); /// builder.add_peer(peer2.psk.clone(), peer2.pk, ProtocolVersion::V02);
/// ///
/// 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);
@@ -186,8 +187,8 @@ impl Build<CryptoServer> for BuildCryptoServer {
let mut srv = CryptoServer::new(sk, pk); let mut srv = CryptoServer::new(sk, pk);
for (idx, PeerParams { psk, pk }) in self.peers.into_iter().enumerate() { for (idx, PeerParams { psk, pk , protocol_version}) in self.peers.into_iter().enumerate() {
let PeerPtr(idx2) = srv.add_peer(psk, pk)?; let PeerPtr(idx2) = srv.add_peer(psk, pk, protocol_version.into())?;
assert!(idx == idx2, "Peer id changed during CryptoServer construction from {idx} to {idx2}. This is a developer error.") assert!(idx == idx2, "Peer id changed during CryptoServer construction from {idx} to {idx2}. This is a developer error.")
} }
@@ -208,6 +209,8 @@ pub struct PeerParams {
pub psk: Option<SymKey>, pub psk: Option<SymKey>,
/// Public key identifying the peer. /// Public key identifying the peer.
pub pk: SPk, pub pk: SPk,
/// The used protocol version.
pub protocol_version: ProtocolVersion,
} }
impl BuildCryptoServer { impl BuildCryptoServer {
@@ -305,6 +308,7 @@ impl BuildCryptoServer {
/// Adding peers to an existing builder: /// Adding peers to an existing builder:
/// ///
/// ```rust /// ```rust
/// use rosenpass::config::ProtocolVersion;
/// // 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; /// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
/// secret_policy_use_only_malloc_secrets(); /// secret_policy_use_only_malloc_secrets();
@@ -323,7 +327,7 @@ impl BuildCryptoServer {
/// // Now we've found a peer that should be added to the configuration /// // 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()); /// builder.with_added_peer(Some(pre_shared_key.clone()), public_key.clone(), ProtocolVersion::V02);
/// ///
/// // New server instances will then start with the peer being registered already /// // 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");
@@ -333,16 +337,16 @@ impl BuildCryptoServer {
/// assert_eq!(peer.spkt, public_key); /// assert_eq!(peer.spkt, public_key);
/// assert_eq!(peer_psk.secret(), pre_shared_key.secret()); /// assert_eq!(peer_psk.secret(), pre_shared_key.secret());
/// ``` /// ```
pub fn with_added_peer(&mut self, psk: Option<SymKey>, pk: SPk) -> &mut Self { pub fn with_added_peer(&mut self, psk: Option<SymKey>, pk: SPk, protocol_version: ProtocolVersion) -> &mut Self {
// TODO: Check here already whether peer was already added // TODO: Check here already whether peer was already added
self.peers.push(PeerParams { psk, pk }); self.peers.push(PeerParams { psk, pk, protocol_version });
self self
} }
/// Add a new entry to the list of registered peers, with or without a pre-shared key. /// Add a new entry to the list of registered peers, with or without a pre-shared key.
pub fn add_peer(&mut self, psk: Option<SymKey>, pk: SPk) -> PeerPtr { pub fn add_peer(&mut self, psk: Option<SymKey>, pk: SPk, protocol_version: ProtocolVersion) -> PeerPtr {
let id = PeerPtr(self.peers.len()); let id = PeerPtr(self.peers.len());
self.with_added_peer(psk, pk); self.with_added_peer(psk, pk, protocol_version);
id id
} }
@@ -356,6 +360,8 @@ impl BuildCryptoServer {
/// ///
/// ```rust /// ```rust
/// // We have to define the security policy before using Secrets. /// // We have to define the security policy before using Secrets.
/// use rosenpass::config::ProtocolVersion;
/// use rosenpass::hash_domains::protocol;
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets; /// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
/// secret_policy_use_only_malloc_secrets(); /// secret_policy_use_only_malloc_secrets();
/// ///
@@ -365,7 +371,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); /// builder.add_peer(None, peer_pk, ProtocolVersion::V02);
/// ///
/// // 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

@@ -33,6 +33,7 @@
//! # fn main() -> anyhow::Result<()> { //! # fn main() -> anyhow::Result<()> {
//! // Set security policy for storing secrets //! // Set security policy for storing secrets
//! //!
//! use rosenpass::protocol::ProtocolVersion;
//! secret_policy_try_use_memfd_secrets(); //! secret_policy_try_use_memfd_secrets();
//! //!
//! // initialize secret and public key for peer a ... //! // initialize secret and public key for peer a ...
@@ -49,8 +50,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)?; //! a.add_peer(Some(psk.clone()), peer_b_pk, ProtocolVersion::V03)?;
//! b.add_peer(Some(psk), peer_a_pk)?; //! b.add_peer(Some(psk), peer_a_pk, ProtocolVersion::V03)?;
//! //!
//! // 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());

View File

@@ -33,7 +33,9 @@ use rosenpass_util::functional::ApplyExt;
use rosenpass_util::mem::DiscardResultExt; use rosenpass_util::mem::DiscardResultExt;
use rosenpass_util::{cat, mem::cpy_min, time::Timebase}; use rosenpass_util::{cat, mem::cpy_min, time::Timebase};
use zerocopy::{AsBytes, FromBytes, Ref}; use zerocopy::{AsBytes, FromBytes, Ref};
use rosenpass_ciphers::subtle::either_hash::{EitherShakeOrBlake};
use rosenpass_ciphers::subtle::incorrect_hmac_blake2b::Blake2bCore;
use rosenpass_ciphers::subtle::keyed_shake256::SHAKE256Core;
use crate::{hash_domains, msgs::*, RosenpassError}; use crate::{hash_domains, msgs::*, RosenpassError};
// CONSTANTS & SETTINGS ////////////////////////// // CONSTANTS & SETTINGS //////////////////////////
@@ -364,6 +366,30 @@ pub enum IndexKey {
KnownInitConfResponse(KnownResponseHash), KnownInitConfResponse(KnownResponseHash),
} }
#[derive(Debug, Clone)]
pub enum ProtocolVersion {
V02,
V03
}
impl ProtocolVersion {
pub fn shake_or_blake(&self) -> EitherShakeOrBlake {
match self {
ProtocolVersion::V02 => EitherShakeOrBlake::Right(Blake2bCore),
ProtocolVersion::V03 => EitherShakeOrBlake::Left(SHAKE256Core),
}
}
}
impl From<crate::config::ProtocolVersion> for ProtocolVersion {
fn from(v: crate::config::ProtocolVersion) -> Self {
match v {
crate::config::ProtocolVersion::V02 => ProtocolVersion::V02,
crate::config::ProtocolVersion::V03 => ProtocolVersion::V03,
}
}
}
/// A peer that the server can execute a key exchange with. /// A peer that the server can execute a key exchange with.
/// ///
/// Peers generally live in [CryptoServer::peers]. [PeerNo] captures an array /// Peers generally live in [CryptoServer::peers]. [PeerNo] captures an array
@@ -375,7 +401,7 @@ pub enum IndexKey {
/// ///
/// ``` /// ```
/// use std::ops::DerefMut; /// use std::ops::DerefMut;
/// use rosenpass::protocol::{SSk, SPk, SymKey, Peer}; /// use rosenpass::protocol::{SSk, SPk, SymKey, Peer, ProtocolVersion};
/// use rosenpass_ciphers::kem::StaticKem; /// use rosenpass_ciphers::kem::StaticKem;
/// use rosenpass_cipher_traits::Kem; /// use rosenpass_cipher_traits::Kem;
/// ///
@@ -390,13 +416,13 @@ pub enum IndexKey {
/// let psk = SymKey::random(); /// let psk = SymKey::random();
/// ///
/// // Creation with a PSK /// // Creation with a PSK
/// let peer_psk = Peer::new(psk, spkt.clone()); /// let peer_psk = Peer::new(psk, spkt.clone(), ProtocolVersion::V03);
/// ///
/// // Creation without a PSK /// // Creation without a PSK
/// let peer_nopsk = Peer::new(SymKey::zero(), spkt); /// let peer_nopsk = Peer::new(SymKey::zero(), spkt, ProtocolVersion::V03);
/// ///
/// // Create a second peer /// // Create a second peer
/// let peer_psk_2 = Peer::new(SymKey::zero(), spkt2); /// let peer_psk_2 = Peer::new(SymKey::zero(), spkt2, ProtocolVersion::V03);
/// ///
/// // Peer ID does not depend on PSK, but it does depend on the public key /// // 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()?);
@@ -452,6 +478,9 @@ 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>,
/// TODO: Documentation
pub protocol_version: ProtocolVersion,
} }
impl Peer { impl Peer {
@@ -460,14 +489,14 @@ impl Peer {
/// This is dirty but allows us to perform easy incremental construction of [Self]. /// This is dirty but allows us to perform easy incremental construction of [Self].
/// ///
/// ``` /// ```
/// use rosenpass::protocol::{Peer, SymKey, SPk}; /// use rosenpass::protocol::{Peer, SymKey, SPk, ProtocolVersion};
/// rosenpass_secret_memory::secret_policy_try_use_memfd_secrets(); /// rosenpass_secret_memory::secret_policy_try_use_memfd_secrets();
/// let p = Peer::zero(); /// let p = Peer::zero(ProtocolVersion::V03);
/// assert_eq!(p.psk.secret(), SymKey::zero().secret()); /// assert_eq!(p.psk.secret(), SymKey::zero().secret());
/// assert_eq!(p.spkt, SPk::zero()); /// assert_eq!(p.spkt, SPk::zero());
/// // etc. /// // etc.
/// ``` /// ```
pub fn zero() -> Self { pub fn zero(protocol_version: ProtocolVersion) -> Self {
Self { Self {
psk: SymKey::zero(), psk: SymKey::zero(),
spkt: SPk::zero(), spkt: SPk::zero(),
@@ -476,6 +505,7 @@ impl Peer {
initiation_requested: false, initiation_requested: false,
handshake: None, handshake: None,
known_init_conf_response: None, known_init_conf_response: None,
protocol_version: protocol_version,
} }
} }
} }
@@ -625,7 +655,7 @@ fn known_response_format() {
response: Envelope::new_zeroed(), response: Envelope::new_zeroed(),
}; };
let s = format!("{v:?}"); let s = format!("{v:?}");
assert!(s.contains("response")); // Smoke test only, its a formatter assert!(s.contains("response")); // Smoke test only, it's a formatter
} }
/// Known [EmptyData] response to an [InitConf] message /// Known [EmptyData] response to an [InitConf] message
@@ -804,11 +834,11 @@ pub trait Mortal {
/// ``` /// ```
/// use std::ops::DerefMut; /// use std::ops::DerefMut;
/// use rosenpass_ciphers::kem::StaticKem; /// use rosenpass_ciphers::kem::StaticKem;
/// use rosenpass::protocol::{SSk, SPk, testutils::ServerForTesting}; /// use rosenpass::protocol::{SSk, SPk, testutils::ServerForTesting, ProtocolVersion};
/// ///
/// rosenpass_secret_memory::secret_policy_try_use_memfd_secrets(); /// rosenpass_secret_memory::secret_policy_try_use_memfd_secrets();
/// ///
/// let (peer, (_, spkt), mut srv) = ServerForTesting::new()?.tuple(); /// let (peer, (_, spkt), mut srv) = ServerForTesting::new(ProtocolVersion::V03)?.tuple();
/// ///
/// // Immutable access /// // Immutable access
/// assert_eq!(peer.get(&srv).spkt, spkt); /// assert_eq!(peer.get(&srv).spkt, spkt);
@@ -1344,7 +1374,7 @@ impl CryptoServer {
/// ///
/// ``` /// ```
/// use std::ops::DerefMut; /// use std::ops::DerefMut;
/// use rosenpass::protocol::{SSk, SPk, CryptoServer}; /// use rosenpass::protocol::{SSk, SPk, CryptoServer, ProtocolVersion};
/// use rosenpass_ciphers::kem::StaticKem; /// use rosenpass_ciphers::kem::StaticKem;
/// use rosenpass_cipher_traits::Kem; /// use rosenpass_cipher_traits::Kem;
/// ///
@@ -1388,9 +1418,9 @@ impl CryptoServer {
/// Calculate the peer ID of this CryptoServer /// Calculate the peer ID of this CryptoServer
#[rustfmt::skip] #[rustfmt::skip]
pub fn pidm(&self) -> Result<PeerId> { pub fn pidm(&self, shake_or_blake: EitherShakeOrBlake) -> Result<PeerId> {
Ok(Public::new( Ok(Public::new(
hash_domains::peerid()? hash_domains::peerid(shake_or_blake)?
.mix(self.spkm.deref())? .mix(self.spkm.deref())?
.into_value())) .into_value()))
} }
@@ -1405,9 +1435,11 @@ impl CryptoServer {
/// Add a peer with an optional pre shared key (`psk`) and its public key (`pk`) /// Add a peer with an optional pre shared key (`psk`) and its public key (`pk`)
/// ///
/// TODO: Adapt documentation
///
/// ``` /// ```
/// use std::ops::DerefMut; /// use std::ops::DerefMut;
/// use rosenpass::protocol::{SSk, SPk, SymKey, CryptoServer}; /// use rosenpass::protocol::{SSk, SPk, SymKey, CryptoServer, ProtocolVersion};
/// use rosenpass_ciphers::kem::StaticKem; /// use rosenpass_ciphers::kem::StaticKem;
/// use rosenpass_cipher_traits::Kem; /// use rosenpass_cipher_traits::Kem;
/// ///
@@ -1422,13 +1454,13 @@ impl CryptoServer {
/// ///
/// let psk = SymKey::random(); /// let psk = SymKey::random();
/// ///
/// let peer = srv.add_peer(Some(psk), spkt.clone())?; /// let peer = srv.add_peer(Some(psk), spkt.clone(), ProtocolVersion::V03)?;
/// ///
/// assert_eq!(peer.get(&srv).spkt, spkt); /// assert_eq!(peer.get(&srv).spkt, spkt);
/// ///
/// Ok::<(), anyhow::Error>(()) /// Ok::<(), anyhow::Error>(())
/// ``` /// ```
pub fn add_peer(&mut self, psk: Option<SymKey>, pk: SPk) -> Result<PeerPtr> { pub fn add_peer(&mut self, psk: Option<SymKey>, pk: SPk, protocol_version: ProtocolVersion) -> Result<PeerPtr> {
let peer = Peer { let peer = Peer {
psk: psk.unwrap_or_else(SymKey::zero), psk: psk.unwrap_or_else(SymKey::zero),
spkt: pk, spkt: pk,
@@ -1437,6 +1469,7 @@ impl CryptoServer {
handshake: None, handshake: None,
known_init_conf_response: None, known_init_conf_response: None,
initiation_requested: false, initiation_requested: false,
protocol_version: protocol_version,
}; };
let peerid = peer.pidt()?; let peerid = peer.pidt()?;
let peerno = self.peers.len(); let peerno = self.peers.len();
@@ -1549,7 +1582,7 @@ impl CryptoServer {
/// Retrieve the active biscuit key, cycling biscuit keys if necessary. /// Retrieve the active biscuit key, cycling biscuit keys if necessary.
/// ///
/// Two biscuit keys are maintained inside [Self::biscuit_keys]; they are /// Two biscuit keys are maintained inside [Self::biscuit_keys]; they are
/// considered fresh ([Lifecycle::Young]) for one [BISCUIT_EPOCH] after creation /// considered fresh ([Lifecycle::Young]) for one [BISCUIT_EPOCH] after creation,
/// and they are considered stale ([Lifecycle::Retired]) for another [BISCUIT_EPOCH]. /// and they are considered stale ([Lifecycle::Retired]) for another [BISCUIT_EPOCH].
/// ///
/// While young, they are used for encryption of biscuits and while retired they are /// While young, they are used for encryption of biscuits and while retired they are
@@ -1624,7 +1657,7 @@ impl Peer {
/// # Examples /// # Examples
/// ///
/// See example in [Self]. /// See example in [Self].
pub fn new(psk: SymKey, pk: SPk) -> Peer { pub fn new(psk: SymKey, pk: SPk, protocol_version: ProtocolVersion) -> Peer {
Peer { Peer {
psk, psk,
spkt: pk, spkt: pk,
@@ -1633,11 +1666,12 @@ impl Peer {
handshake: None, handshake: None,
known_init_conf_response: None, known_init_conf_response: None,
initiation_requested: false, initiation_requested: false,
protocol_version: protocol_version,
} }
} }
/// Compute the peer ID of the peer, /// Compute the peer ID of the peer,
/// as specified in the the [whitepaper](https://rosenpass.eu/whitepaper.pdf). /// as specified in the [whitepaper](https://rosenpass.eu/whitepaper.pdf).
/// ///
/// # Examples /// # Examples
/// ///
@@ -1645,7 +1679,7 @@ impl Peer {
#[rustfmt::skip] #[rustfmt::skip]
pub fn pidt(&self) -> Result<PeerId> { pub fn pidt(&self) -> Result<PeerId> {
Ok(Public::new( Ok(Public::new(
hash_domains::peerid()? hash_domains::peerid(self.protocol_version.shake_or_blake())?
.mix(self.spkt.deref())? .mix(self.spkt.deref())?
.into_value())) .into_value()))
} }
@@ -1658,20 +1692,22 @@ impl Session {
/// ///
/// ``` /// ```
/// use rosenpass::protocol::{Session, HandshakeRole}; /// use rosenpass::protocol::{Session, HandshakeRole};
/// use rosenpass_ciphers::subtle::either_hash::EitherShakeOrBlake;
/// use rosenpass_ciphers::subtle::keyed_shake256::SHAKE256Core;
/// ///
/// rosenpass_secret_memory::secret_policy_try_use_memfd_secrets(); /// rosenpass_secret_memory::secret_policy_try_use_memfd_secrets();
/// ///
/// let s = Session::zero(); /// let s = Session::zero(EitherShakeOrBlake::Left(SHAKE256Core));
/// assert_eq!(s.created_at, 0.0); /// assert_eq!(s.created_at, 0.0);
/// assert_eq!(s.handshake_role, HandshakeRole::Initiator); /// assert_eq!(s.handshake_role, HandshakeRole::Initiator);
/// ``` /// ```
pub fn zero() -> Self { pub fn zero(shake_or_blake: EitherShakeOrBlake) -> Self {
Self { Self {
created_at: 0.0, created_at: 0.0,
sidm: SessionId::zero(), sidm: SessionId::zero(),
sidt: SessionId::zero(), sidt: SessionId::zero(),
handshake_role: HandshakeRole::Initiator, handshake_role: HandshakeRole::Initiator,
ck: SecretHashDomain::zero().dup(), ck: SecretHashDomain::zero(shake_or_blake).dup(),
txkm: SymKey::zero(), txkm: SymKey::zero(),
txkt: SymKey::zero(), txkt: SymKey::zero(),
txnm: 0, txnm: 0,
@@ -1886,7 +1922,7 @@ impl Mortal for KnownInitConfResponsePtr {
/// # Examples /// # Examples
/// ///
/// ``` /// ```
/// use rosenpass::protocol::{Timing, Mortal, MortalExt, Lifecycle, CryptoServer}; /// use rosenpass::protocol::{Timing, Mortal, MortalExt, Lifecycle, CryptoServer, ProtocolVersion};
/// use rosenpass::protocol::testutils::{ServerForTesting, time_travel_forward}; /// use rosenpass::protocol::testutils::{ServerForTesting, time_travel_forward};
/// ///
/// rosenpass_secret_memory::secret_policy_try_use_memfd_secrets(); /// rosenpass_secret_memory::secret_policy_try_use_memfd_secrets();
@@ -1896,7 +1932,7 @@ impl Mortal for KnownInitConfResponsePtr {
/// const D : Timing = 24.0 * H; /// const D : Timing = 24.0 * H;
/// const Y : Timing = 356.0 * D; /// const Y : Timing = 356.0 * D;
/// ///
/// let mut ts = ServerForTesting::new()?; /// let mut ts = ServerForTesting::new(ProtocolVersion::V03)?;
/// ///
/// fn eq_up_to_minute(a: Timing, b: Timing) -> bool { /// fn eq_up_to_minute(a: Timing, b: Timing) -> bool {
/// (a - b) < M /// (a - b) < M
@@ -2136,7 +2172,7 @@ impl CryptoServer {
let cookie_secret = cookie_secret.get(self).value.secret(); let cookie_secret = cookie_secret.get(self).value.secret();
let mut cookie_value = [0u8; 16]; let mut cookie_value = [0u8; 16];
cookie_value.copy_from_slice( cookie_value.copy_from_slice(
&hash_domains::cookie_value()? &hash_domains::cookie_value(EitherShakeOrBlake::Left(SHAKE256Core))?
.mix(cookie_secret)? .mix(cookie_secret)?
.mix(host_identification.encode())? .mix(host_identification.encode())?
.into_value()[..16], .into_value()[..16],
@@ -2152,7 +2188,7 @@ impl CryptoServer {
let msg_in = Ref::<&[u8], Envelope<InitHello>>::new(rx_buf) let msg_in = Ref::<&[u8], Envelope<InitHello>>::new(rx_buf)
.ok_or(RosenpassError::BufferSizeMismatch)?; .ok_or(RosenpassError::BufferSizeMismatch)?;
expected.copy_from_slice( expected.copy_from_slice(
&hash_domains::cookie()? &hash_domains::cookie(EitherShakeOrBlake::Left(SHAKE256Core))?
.mix(&cookie_value)? .mix(&cookie_value)?
.mix(&msg_in.as_bytes()[span_of!(Envelope<InitHello>, msg_type..cookie)])? .mix(&msg_in.as_bytes()[span_of!(Envelope<InitHello>, msg_type..cookie)])?
.into_value()[..16], .into_value()[..16],
@@ -2189,7 +2225,7 @@ impl CryptoServer {
); );
let cookie_value = active_cookie_value.unwrap(); let cookie_value = active_cookie_value.unwrap();
let cookie_key = hash_domains::cookie_key()? let cookie_key = hash_domains::cookie_key(EitherShakeOrBlake::Left(SHAKE256Core))?
.mix(self.spkm.deref())? .mix(self.spkm.deref())?
.into_value(); .into_value();
@@ -2269,24 +2305,43 @@ impl CryptoServer {
log::debug!("Rx {:?}, processing", msg_type); log::debug!("Rx {:?}, processing", msg_type);
let mut msg_out = truncating_cast_into::<Envelope<RespHello>>(tx_buf)?;
let peer = match msg_type { let peer = match msg_type {
Ok(MsgType::InitHello) => { Ok(MsgType::InitHello) => {
let msg_in: Ref<&[u8], Envelope<InitHello>> = let msg_in: Ref<&[u8], Envelope<InitHello>> =
Ref::new(rx_buf).ok_or(RosenpassError::BufferSizeMismatch)?; Ref::new(rx_buf).ok_or(RosenpassError::BufferSizeMismatch)?;
ensure!(msg_in.check_seal(self)?, seal_broken);
let mut msg_out = truncating_cast_into::<Envelope<RespHello>>(tx_buf)?; // At this point, we do not know the hash functon used by the peer, thus we try both,
let peer = self.handle_init_hello(&msg_in.payload, &mut msg_out.payload)?; // with a preference for SHAKE256.
let peer_shake256 = self.handle_init_hello(&msg_in.payload, &mut msg_out.payload, EitherShakeOrBlake::Left(SHAKE256Core));
let (peer, peer_hash_choice) = match peer_shake256 {
Ok(peer ) => (peer, EitherShakeOrBlake::Left(SHAKE256Core)),
Err(_) => {
let peer_blake2b = self.handle_init_hello(&msg_in.payload, &mut msg_out.payload, EitherShakeOrBlake::Right(Blake2bCore));
match peer_blake2b {
Ok(peer) => (peer, EitherShakeOrBlake::Right(Blake2bCore)),
Err(_) => bail!("No valid hash function found for InitHello")
}
}
};
// Now, we make sure that the hash function used by the peer is the same as the one
// that is specified in the local configuration.
self.verify_hash_choice_match(peer, peer_hash_choice.clone())?;
ensure!(msg_in.check_seal(self, peer_hash_choice)?, seal_broken);
len = self.seal_and_commit_msg(peer, MsgType::RespHello, &mut msg_out)?; len = self.seal_and_commit_msg(peer, MsgType::RespHello, &mut msg_out)?;
peer peer
} }
Ok(MsgType::RespHello) => { Ok(MsgType::RespHello) => {
let msg_in: Ref<&[u8], Envelope<RespHello>> = let msg_in: Ref<&[u8], Envelope<RespHello>> =
Ref::new(rx_buf).ok_or(RosenpassError::BufferSizeMismatch)?; Ref::new(rx_buf).ok_or(RosenpassError::BufferSizeMismatch)?;
ensure!(msg_in.check_seal(self)?, seal_broken);
let mut msg_out = truncating_cast_into::<Envelope<InitConf>>(tx_buf)?; let mut msg_out = truncating_cast_into::<Envelope<InitConf>>(tx_buf)?;
let peer = self.handle_resp_hello(&msg_in.payload, &mut msg_out.payload)?; let peer = self.handle_resp_hello(&msg_in.payload, &mut msg_out.payload)?;
ensure!(msg_in.check_seal(self, peer.get(self).protocol_version.shake_or_blake())?, seal_broken);
len = self.seal_and_commit_msg(peer, MsgType::InitConf, &mut msg_out)?; len = self.seal_and_commit_msg(peer, MsgType::InitConf, &mut msg_out)?;
peer.hs() peer.hs()
.store_msg_for_retransmission(self, &msg_out.as_bytes()[..len])?; .store_msg_for_retransmission(self, &msg_out.as_bytes()[..len])?;
@@ -2296,7 +2351,7 @@ impl CryptoServer {
Ok(MsgType::InitConf) => { Ok(MsgType::InitConf) => {
let msg_in: Ref<&[u8], Envelope<InitConf>> = let msg_in: Ref<&[u8], Envelope<InitConf>> =
Ref::new(rx_buf).ok_or(RosenpassError::BufferSizeMismatch)?; Ref::new(rx_buf).ok_or(RosenpassError::BufferSizeMismatch)?;
ensure!(msg_in.check_seal(self)?, seal_broken);
let mut msg_out = truncating_cast_into::<Envelope<EmptyData>>(tx_buf)?; let mut msg_out = truncating_cast_into::<Envelope<EmptyData>>(tx_buf)?;
@@ -2305,6 +2360,7 @@ impl CryptoServer {
// Cached response; copy out of cache // Cached response; copy out of cache
Some(cached) => { Some(cached) => {
let peer = cached.peer(); let peer = cached.peer();
ensure!(msg_in.check_seal(self, peer.get(self).protocol_version.shake_or_blake())?, seal_broken);
let cached = cached let cached = cached
.get(self) .get(self)
.map(|v| v.response.borrow()) .map(|v| v.response.borrow())
@@ -2316,7 +2372,23 @@ impl CryptoServer {
// No cached response, actually call cryptographic handler // No cached response, actually call cryptographic handler
None => { None => {
let peer = self.handle_init_conf(&msg_in.payload, &mut msg_out.payload)?; // At this point, we do not know the hash functon used by the peer, thus we try both,
// with a preference for SHAKE256.
let peer_shake256 = self.handle_init_conf(&msg_in.payload, &mut msg_out.payload, EitherShakeOrBlake::Left(SHAKE256Core));
let (peer, peer_hash_choice) = match peer_shake256 {
Ok(peer) => (peer, EitherShakeOrBlake::Left(SHAKE256Core)),
Err(_) => {
let peer_blake2b = self.handle_init_conf(&msg_in.payload, &mut msg_out.payload, EitherShakeOrBlake::Right(Blake2bCore));
match peer_blake2b {
Ok(peer) => (peer, EitherShakeOrBlake::Right(Blake2bCore)),
Err(_) => bail!("No valid hash function found for InitHello")
}
}
};
// Now, we make sure that the hash function used by the peer is the same as the one
// that is specified in the local configuration.
self.verify_hash_choice_match(peer, peer_hash_choice.clone())?;
ensure!(msg_in.check_seal(self, peer_hash_choice)?, seal_broken);
KnownInitConfResponsePtr::insert_for_request_msg( KnownInitConfResponsePtr::insert_for_request_msg(
self, self,
@@ -2336,9 +2408,8 @@ impl CryptoServer {
Ok(MsgType::EmptyData) => { Ok(MsgType::EmptyData) => {
let msg_in: Ref<&[u8], Envelope<EmptyData>> = let msg_in: Ref<&[u8], Envelope<EmptyData>> =
Ref::new(rx_buf).ok_or(RosenpassError::BufferSizeMismatch)?; Ref::new(rx_buf).ok_or(RosenpassError::BufferSizeMismatch)?;
ensure!(msg_in.check_seal(self)?, seal_broken);
self.handle_resp_conf(&msg_in.payload)? self.handle_resp_conf(&msg_in, seal_broken.to_string())?
} }
Ok(MsgType::CookieReply) => { Ok(MsgType::CookieReply) => {
let msg_in: Ref<&[u8], CookieReply> = let msg_in: Ref<&[u8], CookieReply> =
@@ -2358,6 +2429,20 @@ impl CryptoServer {
}) })
} }
/// TODO documentation
fn verify_hash_choice_match(&self, peer: PeerPtr, peer_hash_choice: EitherShakeOrBlake) -> Result<()> {
match peer.get(self).protocol_version.shake_or_blake() {
EitherShakeOrBlake::Left(SHAKE256Core) => match peer_hash_choice {
EitherShakeOrBlake::Left(SHAKE256Core) => Ok(()),
EitherShakeOrBlake::Right(Blake2bCore) => bail!("Hash function mismatch"),
},
EitherShakeOrBlake::Right(Blake2bCore) => match peer_hash_choice {
EitherShakeOrBlake::Left(SHAKE256Core) => bail!("Hash function mismatch"),
EitherShakeOrBlake::Right(Blake2bCore) => Ok(()),
}
}
}
/// This is used to finalize a message in a transmission buffer /// This is used to finalize a message in a transmission buffer
/// while ensuring that the [Envelope::mac] and [Envelope::cookie] /// while ensuring that the [Envelope::mac] and [Envelope::cookie]
/// fields are properly filled. /// fields are properly filled.
@@ -3096,7 +3181,7 @@ where
{ {
/// Internal business logic: Calculate the message authentication code (`mac`) and also append cookie value /// Internal business logic: Calculate the message authentication code (`mac`) and also append cookie value
pub fn seal(&mut self, peer: PeerPtr, srv: &CryptoServer) -> Result<()> { pub fn seal(&mut self, peer: PeerPtr, srv: &CryptoServer) -> Result<()> {
let mac = hash_domains::mac()? let mac = hash_domains::mac(peer.get(srv).protocol_version.shake_or_blake())?
.mix(peer.get(srv).spkt.deref())? .mix(peer.get(srv).spkt.deref())?
.mix(&self.as_bytes()[span_of!(Self, msg_type..mac)])?; .mix(&self.as_bytes()[span_of!(Self, msg_type..mac)])?;
self.mac.copy_from_slice(mac.into_value()[..16].as_ref()); self.mac.copy_from_slice(mac.into_value()[..16].as_ref());
@@ -3109,7 +3194,7 @@ where
/// This is called inside [Self::seal] and does not need to be called again separately. /// This is called inside [Self::seal] and does not need to be called again separately.
pub fn seal_cookie(&mut self, peer: PeerPtr, srv: &CryptoServer) -> Result<()> { pub fn seal_cookie(&mut self, peer: PeerPtr, srv: &CryptoServer) -> Result<()> {
if let Some(cookie_key) = &peer.cv().get(srv) { if let Some(cookie_key) = &peer.cv().get(srv) {
let cookie = hash_domains::cookie()? let cookie = hash_domains::cookie(peer.get(srv).protocol_version.shake_or_blake())?
.mix(cookie_key.value.secret())? .mix(cookie_key.value.secret())?
.mix(&self.as_bytes()[span_of!(Self, msg_type..cookie)])?; .mix(&self.as_bytes()[span_of!(Self, msg_type..cookie)])?;
self.cookie self.cookie
@@ -3124,8 +3209,8 @@ where
M: AsBytes + FromBytes, M: AsBytes + FromBytes,
{ {
/// Internal business logic: Check the message authentication code produced by [Self::seal] /// Internal business logic: Check the message authentication code produced by [Self::seal]
pub fn check_seal(&self, srv: &CryptoServer) -> Result<bool> { pub fn check_seal(&self, srv: &CryptoServer, shake_or_blake: EitherShakeOrBlake) -> Result<bool> {
let expected = hash_domains::mac()? let expected = hash_domains::mac(shake_or_blake)?
.mix(srv.spkm.deref())? .mix(srv.spkm.deref())?
.mix(&self.as_bytes()[span_of!(Self, msg_type..mac)])?; .mix(&self.as_bytes()[span_of!(Self, msg_type..mac)])?;
Ok(constant_time::memcmp( Ok(constant_time::memcmp(
@@ -3137,11 +3222,11 @@ where
impl InitiatorHandshake { impl InitiatorHandshake {
/// Zero initialization of an InitiatorHandshake, with up to date timestamp /// Zero initialization of an InitiatorHandshake, with up to date timestamp
pub fn zero_with_timestamp(srv: &CryptoServer) -> Self { pub fn zero_with_timestamp(srv: &CryptoServer, shake_or_blake: EitherShakeOrBlake) -> Self {
InitiatorHandshake { InitiatorHandshake {
created_at: srv.timebase.now(), created_at: srv.timebase.now(),
next: HandshakeStateMachine::RespHello, next: HandshakeStateMachine::RespHello,
core: HandshakeState::zero(), core: HandshakeState::zero(shake_or_blake),
eski: ESk::zero(), eski: ESk::zero(),
epki: EPk::zero(), epki: EPk::zero(),
tx_at: 0.0, tx_at: 0.0,
@@ -3156,37 +3241,37 @@ impl InitiatorHandshake {
impl HandshakeState { impl HandshakeState {
/// Zero initialization of an HandshakeState /// Zero initialization of an HandshakeState
pub fn zero() -> Self { pub fn zero(shake_or_blake: EitherShakeOrBlake) -> Self {
Self { Self {
sidi: SessionId::zero(), sidi: SessionId::zero(),
sidr: SessionId::zero(), sidr: SessionId::zero(),
ck: SecretHashDomain::zero().dup(), ck: SecretHashDomain::zero(shake_or_blake).dup(),
} }
} }
/// Securely erase the chaining key /// Securely erase the chaining key
pub fn erase(&mut self) { pub fn erase(&mut self) {
self.ck = SecretHashDomain::zero().dup(); self.ck = SecretHashDomain::zero(self.ck.shake_or_blake().clone()).dup();
} }
/// Initialize the handshake state with the responder public key and the protocol domain /// Initialize the handshake state with the responder public key and the protocol domain
/// separator /// separator
pub fn init(&mut self, spkr: &[u8]) -> Result<&mut Self> { pub fn init(&mut self, spkr: &[u8]) -> Result<&mut Self> {
self.ck = hash_domains::ckinit()?.turn_secret().mix(spkr)?.dup(); self.ck = hash_domains::ckinit(self.ck.shake_or_blake().clone())?.turn_secret().mix(spkr)?.dup();
Ok(self) Ok(self)
} }
/// Mix some data into the chaining key. This is used for mixing cryptographic keys and public /// Mix some data into the chaining key. This is used for mixing cryptographic keys and public
/// data alike into the chaining key /// data alike into the chaining key
pub fn mix(&mut self, a: &[u8]) -> Result<&mut Self> { pub fn mix(&mut self, a: &[u8]) -> Result<&mut Self> {
self.ck = self.ck.mix(&hash_domains::mix()?)?.mix(a)?.dup(); self.ck = self.ck.mix(&hash_domains::mix(self.ck.shake_or_blake().clone())?)?.mix(a)?.dup();
Ok(self) Ok(self)
} }
/// Encrypt some data with a value derived from the current chaining key and mix that data /// Encrypt some data with a value derived from the current chaining key and mix that data
/// into the protocol state. /// into the protocol state.
pub fn encrypt_and_mix(&mut self, ct: &mut [u8], pt: &[u8]) -> Result<&mut Self> { pub fn encrypt_and_mix(&mut self, ct: &mut [u8], pt: &[u8]) -> Result<&mut Self> {
let k = self.ck.mix(&hash_domains::hs_enc()?)?.into_secret(); let k = self.ck.mix(&hash_domains::hs_enc(self.ck.shake_or_blake().clone())?)?.into_secret();
aead::encrypt(ct, k.secret(), &[0u8; aead::NONCE_LEN], &[], pt)?; aead::encrypt(ct, k.secret(), &[0u8; aead::NONCE_LEN], &[], pt)?;
self.mix(ct) self.mix(ct)
} }
@@ -3196,7 +3281,7 @@ impl HandshakeState {
/// Makes sure that the same values are mixed into the chaining that where mixed in on the /// Makes sure that the same values are mixed into the chaining that where mixed in on the
/// sender side. /// sender side.
pub fn decrypt_and_mix(&mut self, pt: &mut [u8], ct: &[u8]) -> Result<&mut Self> { pub fn decrypt_and_mix(&mut self, pt: &mut [u8], ct: &[u8]) -> Result<&mut Self> {
let k = self.ck.mix(&hash_domains::hs_enc()?)?.into_secret(); let k = self.ck.mix(&hash_domains::hs_enc(self.ck.shake_or_blake().clone())?)?.into_secret();
aead::decrypt(pt, k.secret(), &[0u8; aead::NONCE_LEN], &[], ct)?; aead::decrypt(pt, k.secret(), &[0u8; aead::NONCE_LEN], &[], ct)?;
self.mix(ct) self.mix(ct)
} }
@@ -3259,7 +3344,7 @@ impl HandshakeState {
.copy_from_slice(self.ck.clone().danger_into_secret().secret()); .copy_from_slice(self.ck.clone().danger_into_secret().secret());
// calculate ad contents // calculate ad contents
let ad = hash_domains::biscuit_ad()? let ad = hash_domains::biscuit_ad(peer.get(srv).protocol_version.shake_or_blake())?
.mix(srv.spkm.deref())? .mix(srv.spkm.deref())?
.mix(self.sidi.as_slice())? .mix(self.sidi.as_slice())?
.mix(self.sidr.as_slice())? .mix(self.sidr.as_slice())?
@@ -3288,12 +3373,13 @@ impl HandshakeState {
biscuit_ct: &[u8], biscuit_ct: &[u8],
sidi: SessionId, sidi: SessionId,
sidr: SessionId, sidr: SessionId,
shake_or_blake: EitherShakeOrBlake
) -> Result<(PeerPtr, BiscuitId, HandshakeState)> { ) -> Result<(PeerPtr, BiscuitId, HandshakeState)> {
// The first bit of the biscuit indicates which biscuit key was used // The first bit of the biscuit indicates which biscuit key was used
let bk = BiscuitKeyPtr(((biscuit_ct[0] & 0b1000_0000) >> 7) as usize); let bk = BiscuitKeyPtr(((biscuit_ct[0] & 0b1000_0000) >> 7) as usize);
// Calculate additional data fields // Calculate additional data fields
let ad = hash_domains::biscuit_ad()? let ad = hash_domains::biscuit_ad(shake_or_blake)?
.mix(srv.spkm.deref())? .mix(srv.spkm.deref())?
.mix(sidi.as_slice())? .mix(sidi.as_slice())?
.mix(sidr.as_slice())? .mix(sidr.as_slice())?
@@ -3312,18 +3398,17 @@ impl HandshakeState {
// Reconstruct the biscuit fields // Reconstruct the biscuit fields
let no = BiscuitId::from_slice(&biscuit.biscuit_no); let no = BiscuitId::from_slice(&biscuit.biscuit_no);
let ck = SecretHashDomain::danger_from_secret(Secret::from_slice(&biscuit.ck)).dup();
let pid = PeerId::from_slice(&biscuit.pidi); let pid = PeerId::from_slice(&biscuit.pidi);
// Reconstruct the handshake state
let mut hs = Self { sidi, sidr, ck };
hs.mix(biscuit_ct)?;
// Look up the associated peer // Look up the associated peer
let peer = srv let peer = srv
.find_peer(pid) // TODO: FindPeer should return a Result<()> .find_peer(pid) // TODO: FindPeer should return a Result<()>
.with_context(|| format!("Could not decode biscuit for peer {pid:?}: No such peer."))?; .with_context(|| format!("Could not decode biscuit for peer {pid:?}: No such peer."))?;
let ck = SecretHashDomain::danger_from_secret(Secret::from_slice(&biscuit.ck), peer.get(srv).protocol_version.shake_or_blake()).dup();
// Reconstruct the handshake state
let mut hs = Self { sidi, sidr, ck };
hs.mix(biscuit_ct)?;
Ok((peer, no, hs)) Ok((peer, no, hs))
} }
@@ -3332,10 +3417,10 @@ impl HandshakeState {
/// This called by either party. /// This called by either party.
/// ///
/// `role` indicates whether the local peer was an initiator or responder in the handshake. /// `role` indicates whether the local peer was an initiator or responder in the handshake.
pub fn enter_live(self, srv: &CryptoServer, role: HandshakeRole) -> Result<Session> { pub fn enter_live(self, srv: &CryptoServer, role: HandshakeRole, either_shake_or_blake: EitherShakeOrBlake) -> Result<Session> {
let HandshakeState { ck, sidi, sidr } = self; let HandshakeState { ck, sidi, sidr } = self;
let tki = ck.mix(&hash_domains::ini_enc()?)?.into_secret(); let tki = ck.mix(&hash_domains::ini_enc(either_shake_or_blake.clone())?)?.into_secret();
let tkr = ck.mix(&hash_domains::res_enc()?)?.into_secret(); let tkr = ck.mix(&hash_domains::res_enc(either_shake_or_blake)?)?.into_secret();
let created_at = srv.timebase.now(); let created_at = srv.timebase.now();
let (ntx, nrx) = (0, 0); let (ntx, nrx) = (0, 0);
let (mysid, peersid, ktx, krx) = match role { let (mysid, peersid, ktx, krx) = match role {
@@ -3373,7 +3458,7 @@ impl CryptoServer {
.get(self) .get(self)
.as_ref() .as_ref()
.with_context(|| format!("No current session for peer {:?}", peer))?; .with_context(|| format!("No current session for peer {:?}", peer))?;
Ok(session.ck.mix(&hash_domains::osk()?)?.into_secret()) Ok(session.ck.mix(&hash_domains::osk(peer.get(self).protocol_version.shake_or_blake())?)?.into_secret())
} }
} }
@@ -3381,7 +3466,7 @@ impl CryptoServer {
/// Core cryptographic protocol implementation: Kicks of the handshake /// Core cryptographic protocol implementation: Kicks of the handshake
/// on the initiator side, producing the InitHello message. /// on the initiator side, producing the InitHello message.
pub fn handle_initiation(&mut self, peer: PeerPtr, ih: &mut InitHello) -> Result<PeerPtr> { pub fn handle_initiation(&mut self, peer: PeerPtr, ih: &mut InitHello) -> Result<PeerPtr> {
let mut hs = InitiatorHandshake::zero_with_timestamp(self); let mut hs = InitiatorHandshake::zero_with_timestamp(self, peer.get(self).protocol_version.shake_or_blake());
// IHI1 // IHI1
hs.core.init(peer.get(self).spkt.deref())?; hs.core.init(peer.get(self).spkt.deref())?;
@@ -3406,7 +3491,7 @@ impl CryptoServer {
// IHI6 // IHI6
hs.core hs.core
.encrypt_and_mix(ih.pidic.as_mut_slice(), self.pidm()?.as_ref())?; .encrypt_and_mix(ih.pidic.as_mut_slice(), self.pidm(peer.get(self).protocol_version.shake_or_blake())?.as_ref())?;
// IHI7 // IHI7
hs.core hs.core
@@ -3424,8 +3509,9 @@ impl CryptoServer {
/// Core cryptographic protocol implementation: Parses an [InitHello] message and produces a /// Core cryptographic protocol implementation: Parses an [InitHello] message and produces a
/// [RespHello] message on the responder side. /// [RespHello] message on the responder side.
pub fn handle_init_hello(&mut self, ih: &InitHello, rh: &mut RespHello) -> Result<PeerPtr> { /// TODO: Document Hash Functon usage
let mut core = HandshakeState::zero(); pub fn handle_init_hello(&mut self, ih: &InitHello, rh: &mut RespHello, shake_or_blake: EitherShakeOrBlake) -> Result<PeerPtr> {
let mut core = HandshakeState::zero(shake_or_blake);
core.sidi = SessionId::from_slice(&ih.sidi); core.sidi = SessionId::from_slice(&ih.sidi);
@@ -3570,7 +3656,7 @@ impl CryptoServer {
// ICI7 // ICI7
peer.session() peer.session()
.insert(self, core.enter_live(self, HandshakeRole::Initiator)?)?; .insert(self, core.enter_live(self, HandshakeRole::Initiator, peer.get(self).protocol_version.shake_or_blake())?)?;
hs_mut!().core.erase(); hs_mut!().core.erase();
hs_mut!().next = HandshakeStateMachine::RespConf; hs_mut!().next = HandshakeStateMachine::RespConf;
@@ -3578,11 +3664,12 @@ impl CryptoServer {
} }
/// Core cryptographic protocol implementation: Parses an [InitConf] message and produces an /// Core cryptographic protocol implementation: Parses an [InitConf] message and produces an
/// [EmptyData] (responder confimation) message on the responder side. /// [EmptyData] (responder confirmation) message on the responder side.
/// ///
/// This concludes the handshake on the cryptographic level; the [EmptyData] message is just /// This concludes the handshake on the cryptographic level; the [EmptyData] message is just
/// an acknowledgement message telling the initiator to stop performing retransmissions. /// an acknowledgement message telling the initiator to stop performing retransmissions.
pub fn handle_init_conf(&mut self, ic: &InitConf, rc: &mut EmptyData) -> Result<PeerPtr> { /// TODO: documentation
pub fn handle_init_conf(&mut self, ic: &InitConf, rc: &mut EmptyData, shake_or_blake: EitherShakeOrBlake) -> Result<PeerPtr> {
// (peer, bn) ← LoadBiscuit(InitConf.biscuit) // (peer, bn) ← LoadBiscuit(InitConf.biscuit)
// ICR1 // ICR1
let (peer, biscuit_no, mut core) = HandshakeState::load_biscuit( let (peer, biscuit_no, mut core) = HandshakeState::load_biscuit(
@@ -3590,6 +3677,7 @@ impl CryptoServer {
&ic.biscuit, &ic.biscuit,
SessionId::from_slice(&ic.sidi), SessionId::from_slice(&ic.sidi),
SessionId::from_slice(&ic.sidr), SessionId::from_slice(&ic.sidr),
shake_or_blake,
)?; )?;
// ICR2 // ICR2
@@ -3615,7 +3703,7 @@ impl CryptoServer {
// ICR7 // ICR7
peer.session() peer.session()
.insert(self, core.enter_live(self, HandshakeRole::Responder)?)?; .insert(self, core.enter_live(self, HandshakeRole::Responder, peer.get(self).protocol_version.shake_or_blake())?)?;
// TODO: This should be part of the protocol specification. // TODO: This should be part of the protocol specification.
// Abort any ongoing handshake from initiator role // Abort any ongoing handshake from initiator role
peer.hs().take(self); peer.hs().take(self);
@@ -3663,11 +3751,13 @@ impl CryptoServer {
/// message then terminates the handshake. /// message then terminates the handshake.
/// ///
/// The EmptyData message is just there to tell the initiator to abort retransmissions. /// The EmptyData message is just there to tell the initiator to abort retransmissions.
pub fn handle_resp_conf(&mut self, rc: &EmptyData) -> Result<PeerPtr> { pub fn handle_resp_conf(&mut self, msg_in: &Ref<&[u8], Envelope<EmptyData>>, seal_broken: String) -> Result<PeerPtr> {
let rc: &EmptyData = &msg_in.payload;
let sid = SessionId::from_slice(&rc.sid); let sid = SessionId::from_slice(&rc.sid);
let hs = self let hs = self
.lookup_handshake(sid) .lookup_handshake(sid)
.with_context(|| format!("Got RespConf packet for non-existent session {sid:?}"))?; .with_context(|| format!("Got RespConf packet for non-existent session {sid:?}"))?;
ensure!(msg_in.check_seal(self, hs.peer().get(self).protocol_version.shake_or_blake())?, seal_broken);
let ses = hs.peer().session(); let ses = hs.peer().session();
let exp = hs.get(self).as_ref().map(|h| h.next); let exp = hs.get(self).as_ref().map(|h| h.next);
@@ -3685,7 +3775,7 @@ impl CryptoServer {
let s = ses.get_mut(self).as_mut().with_context(|| { let s = ses.get_mut(self).as_mut().with_context(|| {
format!("Cannot validate EmptyData message. Missing encryption session for {sid:?}") format!("Cannot validate EmptyData message. Missing encryption session for {sid:?}")
})?; })?;
// the unwrap can not fail, because the slice returned by ctr() is // the unwrapping can not fail, because the slice returned by ctr() is
// guaranteed to have the correct size // guaranteed to have the correct size
let n = u64::from_le_bytes(rc.ctr); let n = u64::from_le_bytes(rc.ctr);
ensure!(n >= s.txnt, "Stale nonce"); ensure!(n >= s.txnt, "Stale nonce");
@@ -3750,7 +3840,7 @@ impl CryptoServer {
}?; }?;
let spkt = peer.get(self).spkt.deref(); let spkt = peer.get(self).spkt.deref();
let cookie_key = hash_domains::cookie_key()?.mix(spkt)?.into_value(); let cookie_key = hash_domains::cookie_key(EitherShakeOrBlake::Left(SHAKE256Core))?.mix(spkt)?.into_value();
let cookie_value = peer.cv().update_mut(self).unwrap(); let cookie_value = peer.cv().update_mut(self).unwrap();
xaead::decrypt(cookie_value, &cookie_key, &mac, &cr.inner.cookie_encrypted)?; xaead::decrypt(cookie_value, &cookie_key, &mac, &cr.inner.cookie_encrypted)?;
@@ -3795,15 +3885,16 @@ pub mod testutils {
pub srv: CryptoServer, pub srv: CryptoServer,
} }
/// TODO: Document that the protocol verson s only used for creating the peer for testing
impl ServerForTesting { impl ServerForTesting {
pub fn new() -> anyhow::Result<Self> { pub fn new(protocol_version: ProtocolVersion) -> anyhow::Result<Self> {
let (mut sskm, mut spkm) = (SSk::zero(), SPk::zero()); let (mut sskm, mut spkm) = (SSk::zero(), SPk::zero());
StaticKem::keygen(sskm.secret_mut(), spkm.deref_mut())?; StaticKem::keygen(sskm.secret_mut(), spkm.deref_mut())?;
let mut srv = CryptoServer::new(sskm, spkm); let mut srv = CryptoServer::new(sskm, spkm);
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())?; let peer = srv.add_peer(None, spkt.clone(), protocol_version)?;
let peer_keys = (sskt, spkt); let peer_keys = (sskt, spkt);
Ok(ServerForTesting { Ok(ServerForTesting {
@@ -3868,6 +3959,17 @@ mod test {
#[test] #[test]
#[serial] #[serial]
fn handles_incorrect_size_messages_v02() {
handles_incorrect_size_messages(ProtocolVersion::V02)
}
#[test]
#[serial]
fn handles_incorrect_size_messages_v03() {
handles_incorrect_size_messages(ProtocolVersion::V03)
}
/// Ensure that the protocol implementation can deal with truncated /// Ensure that the protocol implementation can deal with truncated
/// messages and with overlong messages. /// messages and with overlong messages.
/// ///
@@ -3882,7 +3984,7 @@ mod test {
/// ///
/// Through all this, the handshake should still successfully terminate; /// Through all this, the handshake should still successfully terminate;
/// i.e. an exchanged key must be produced in both servers. /// i.e. an exchanged key must be produced in both servers.
fn handles_incorrect_size_messages() { fn handles_incorrect_size_messages(protocol_version: ProtocolVersion) {
setup_logging(); setup_logging();
rosenpass_secret_memory::secret_policy_try_use_memfd_secrets(); rosenpass_secret_memory::secret_policy_try_use_memfd_secrets();
stacker::grow(8 * 1024 * 1024, || { stacker::grow(8 * 1024 * 1024, || {
@@ -3891,7 +3993,7 @@ mod test {
const PEER0: PeerPtr = PeerPtr(0); const PEER0: PeerPtr = PeerPtr(0);
let (mut me, mut they) = make_server_pair().unwrap(); let (mut me, mut they) = make_server_pair(protocol_version).unwrap();
let (mut msgbuf, mut resbuf) = (MsgBufPlus::zero(), MsgBufPlus::zero()); let (mut msgbuf, mut resbuf) = (MsgBufPlus::zero(), MsgBufPlus::zero());
// Process the entire handshake // Process the entire handshake
@@ -3941,7 +4043,7 @@ mod test {
Ok((sk, pk)) Ok((sk, pk))
} }
fn make_server_pair() -> Result<(CryptoServer, CryptoServer)> { fn make_server_pair(protocol_version: ProtocolVersion) -> Result<(CryptoServer, CryptoServer)> {
// TODO: Copied from the benchmark; deduplicate // TODO: Copied from the benchmark; deduplicate
let psk = SymKey::random(); let psk = SymKey::random();
let ((ska, pka), (skb, pkb)) = (keygen()?, keygen()?); let ((ska, pka), (skb, pkb)) = (keygen()?, keygen()?);
@@ -3949,19 +4051,29 @@ mod test {
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)?; a.add_peer(Some(psk.clone()), pkb, protocol_version.clone())?;
b.add_peer(Some(psk), pka)?; b.add_peer(Some(psk), pka, protocol_version)?;
Ok((a, b)) Ok((a, b))
} }
#[test] #[test]
#[serial] #[serial]
fn test_regular_exchange() { fn test_regular_exchange_v02() {
test_regular_exchange(ProtocolVersion::V02)
}
#[test]
#[serial]
fn test_regular_exchange_v03() {
test_regular_exchange(ProtocolVersion::V03)
}
fn test_regular_exchange(protocol_version: ProtocolVersion) {
setup_logging(); setup_logging();
rosenpass_secret_memory::secret_policy_try_use_memfd_secrets(); rosenpass_secret_memory::secret_policy_try_use_memfd_secrets();
stacker::grow(8 * 1024 * 1024, || { stacker::grow(8 * 1024 * 1024, || {
type MsgBufPlus = Public<MAX_MESSAGE_LEN>; type MsgBufPlus = Public<MAX_MESSAGE_LEN>;
let (mut a, mut b) = make_server_pair().unwrap(); let (mut a, mut b) = make_server_pair(protocol_version).unwrap();
let mut a_to_b_buf = MsgBufPlus::zero(); let mut a_to_b_buf = MsgBufPlus::zero();
let mut b_to_a_buf = MsgBufPlus::zero(); let mut b_to_a_buf = MsgBufPlus::zero();
@@ -4017,12 +4129,22 @@ mod test {
#[test] #[test]
#[serial] #[serial]
fn test_regular_init_conf_retransmit() { fn test_regular_init_conf_retransmit_v02() {
test_regular_init_conf_retransmit(ProtocolVersion::V02)
}
#[test]
#[serial]
fn test_regular_init_conf_retransmit_v03() {
test_regular_init_conf_retransmit(ProtocolVersion::V03)
}
fn test_regular_init_conf_retransmit(protocol_version: ProtocolVersion) {
setup_logging(); setup_logging();
rosenpass_secret_memory::secret_policy_try_use_memfd_secrets(); rosenpass_secret_memory::secret_policy_try_use_memfd_secrets();
stacker::grow(8 * 1024 * 1024, || { stacker::grow(8 * 1024 * 1024, || {
type MsgBufPlus = Public<MAX_MESSAGE_LEN>; type MsgBufPlus = Public<MAX_MESSAGE_LEN>;
let (mut a, mut b) = make_server_pair().unwrap(); let (mut a, mut b) = make_server_pair(protocol_version).unwrap();
let mut a_to_b_buf = MsgBufPlus::zero(); let mut a_to_b_buf = MsgBufPlus::zero();
let mut b_to_a_buf = MsgBufPlus::zero(); let mut b_to_a_buf = MsgBufPlus::zero();
@@ -4092,12 +4214,22 @@ mod test {
#[test] #[test]
#[serial] #[serial]
fn cookie_reply_mechanism_responder_under_load() { fn cookie_reply_mechanism_responder_under_load_v02() {
cookie_reply_mechanism_initiator_bails_on_message_under_load(ProtocolVersion::V02)
}
#[test]
#[serial]
fn cookie_reply_mechanism_responder_under_load_v03() {
cookie_reply_mechanism_initiator_bails_on_message_under_load(ProtocolVersion::V03)
}
fn cookie_reply_mechanism_responder_under_load(protocol_version: ProtocolVersion) {
setup_logging(); setup_logging();
rosenpass_secret_memory::secret_policy_try_use_memfd_secrets(); rosenpass_secret_memory::secret_policy_try_use_memfd_secrets();
stacker::grow(8 * 1024 * 1024, || { stacker::grow(8 * 1024 * 1024, || {
type MsgBufPlus = Public<MAX_MESSAGE_LEN>; type MsgBufPlus = Public<MAX_MESSAGE_LEN>;
let (mut a, mut b) = make_server_pair().unwrap(); let (mut a, mut b) = make_server_pair(protocol_version.clone()).unwrap();
let mut a_to_b_buf = MsgBufPlus::zero(); let mut a_to_b_buf = MsgBufPlus::zero();
let mut b_to_a_buf = MsgBufPlus::zero(); let mut b_to_a_buf = MsgBufPlus::zero();
@@ -4136,7 +4268,7 @@ mod test {
assert_eq!(PeerPtr(0).cv().lifecycle(&a), Lifecycle::Young); assert_eq!(PeerPtr(0).cv().lifecycle(&a), Lifecycle::Young);
let expected_cookie_value = hash_domains::cookie_value() let expected_cookie_value = hash_domains::cookie_value(protocol_version.shake_or_blake())
.unwrap() .unwrap()
.mix( .mix(
b.active_or_retired_cookie_secrets()[0] b.active_or_retired_cookie_secrets()[0]
@@ -4189,12 +4321,22 @@ mod test {
#[test] #[test]
#[serial] #[serial]
fn cookie_reply_mechanism_initiator_bails_on_message_under_load() { fn cookie_reply_mechanism_initiator_bails_on_message_under_load_v02() {
cookie_reply_mechanism_initiator_bails_on_message_under_load(ProtocolVersion::V02)
}
#[test]
#[serial]
fn cookie_reply_mechanism_initiator_bails_on_message_under_load_v03() {
cookie_reply_mechanism_initiator_bails_on_message_under_load(ProtocolVersion::V03)
}
fn cookie_reply_mechanism_initiator_bails_on_message_under_load(protocol_version: ProtocolVersion) {
setup_logging(); setup_logging();
rosenpass_secret_memory::secret_policy_try_use_memfd_secrets(); rosenpass_secret_memory::secret_policy_try_use_memfd_secrets();
stacker::grow(8 * 1024 * 1024, || { stacker::grow(8 * 1024 * 1024, || {
type MsgBufPlus = Public<MAX_MESSAGE_LEN>; type MsgBufPlus = Public<MAX_MESSAGE_LEN>;
let (mut a, mut b) = make_server_pair().unwrap(); let (mut a, mut b) = make_server_pair(protocol_version).unwrap();
let mut a_to_b_buf = MsgBufPlus::zero(); let mut a_to_b_buf = MsgBufPlus::zero();
let mut b_to_a_buf = MsgBufPlus::zero(); let mut b_to_a_buf = MsgBufPlus::zero();
@@ -4249,10 +4391,19 @@ mod test {
} }
#[test] #[test]
fn init_conf_retransmission() -> anyhow::Result<()> { fn init_conf_retransmission_v02() -> Result<()> {
init_conf_retransmission(ProtocolVersion::V02)
}
#[test]
fn init_conf_retransmission_v03() -> Result<()> {
init_conf_retransmission(ProtocolVersion::V03)
}
fn init_conf_retransmission(protocol_version: ProtocolVersion) -> anyhow::Result<()> {
rosenpass_secret_memory::secret_policy_try_use_memfd_secrets(); rosenpass_secret_memory::secret_policy_try_use_memfd_secrets();
fn keypair() -> anyhow::Result<(SSk, SPk)> { fn keypair() -> Result<(SSk, SPk)> {
let (mut sk, mut pk) = (SSk::zero(), SPk::zero()); let (mut sk, mut pk) = (SSk::zero(), SPk::zero());
StaticKem::keygen(sk.secret_mut(), pk.deref_mut())?; StaticKem::keygen(sk.secret_mut(), pk.deref_mut())?;
Ok((sk, pk)) Ok((sk, pk))
@@ -4261,7 +4412,7 @@ mod test {
fn proc_initiation( fn proc_initiation(
srv: &mut CryptoServer, srv: &mut CryptoServer,
peer: PeerPtr, peer: PeerPtr,
) -> anyhow::Result<Envelope<InitHello>> { ) -> Result<Envelope<InitHello>> {
let mut buf = MsgBuf::zero(); let mut buf = MsgBuf::zero();
srv.initiate_handshake(peer, buf.as_mut_slice())? srv.initiate_handshake(peer, buf.as_mut_slice())?
.discard_result(); .discard_result();
@@ -4331,12 +4482,8 @@ mod test {
assert!(res.is_err()); assert!(res.is_err());
} }
fn check_retransmission( // we this as a closure in orer to use the protocol_version variable in it.
srv: &mut CryptoServer, let check_retransmission = |srv: &mut CryptoServer, ic: &Envelope<InitConf>, ic_broken: &Envelope<InitConf>, rc: &Envelope<EmptyData>| -> Result<()> {
ic: &Envelope<InitConf>,
ic_broken: &Envelope<InitConf>,
rc: &Envelope<EmptyData>,
) -> anyhow::Result<()> {
// Processing the same RespHello package again leads to retransmission (i.e. exactly the // Processing the same RespHello package again leads to retransmission (i.e. exactly the
// same output) // same output)
let rc_dup = proc_init_conf(srv, ic)?; let rc_dup = proc_init_conf(srv, ic)?;
@@ -4345,14 +4492,14 @@ mod test {
// Though if we directly call handle_resp_hello() we get an error since // Though if we directly call handle_resp_hello() we get an error since
// retransmission is not being handled by the cryptographic code // retransmission is not being handled by the cryptographic code
let mut discard_resp_conf = EmptyData::new_zeroed(); let mut discard_resp_conf = EmptyData::new_zeroed();
let res = srv.handle_init_conf(&ic.payload, &mut discard_resp_conf); let res = srv.handle_init_conf(&ic.payload, &mut discard_resp_conf, protocol_version.clone().shake_or_blake());
assert!(res.is_err()); assert!(res.is_err());
// Obviously, a broken InitConf message should still be rejected // Obviously, a broken InitConf message should still be rejected
check_faulty_proc_init_conf(srv, ic_broken); check_faulty_proc_init_conf(srv, ic_broken);
Ok(()) Ok(())
} };
let (ska, pka) = keypair()?; let (ska, pka) = keypair()?;
let (skb, pkb) = keypair()?; let (skb, pkb) = keypair()?;
@@ -4362,8 +4509,8 @@ mod test {
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)?; let b_peer = a.add_peer(None, pkb, protocol_version.clone())?;
let a_peer = b.add_peer(None, pka)?; let a_peer = b.add_peer(None, pka, protocol_version.clone())?;
// 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

@@ -26,7 +26,7 @@ use rosenpass_util::{
use std::os::fd::{AsFd, AsRawFd}; use std::os::fd::{AsFd, AsRawFd};
use tempfile::TempDir; use tempfile::TempDir;
use zerocopy::AsBytes; use zerocopy::AsBytes;
use rosenpass::config::ProtocolVersion;
use rosenpass::protocol::SymKey; use rosenpass::protocol::SymKey;
struct KillChild(std::process::Child); struct KillChild(std::process::Child);
@@ -48,7 +48,16 @@ impl Drop for KillChild {
} }
#[test] #[test]
fn api_integration_api_setup() -> anyhow::Result<()> { fn api_integration_api_setup_v02() -> anyhow::Result<()> {
api_integration_api_setup(ProtocolVersion::V02)
}
#[test]
fn api_integration_api_setup_v03() -> anyhow::Result<()> {
api_integration_api_setup(ProtocolVersion::V03)
}
fn api_integration_api_setup(protocol_version: ProtocolVersion) -> anyhow::Result<()> {
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets(); rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
let dir = TempDir::with_prefix("rosenpass-api-integration-test")?; let dir = TempDir::with_prefix("rosenpass-api-integration-test")?;
@@ -96,6 +105,7 @@ fn api_integration_api_setup() -> anyhow::Result<()> {
peer: format!("{}", peer_b_wg_peer_id.fmt_b64::<8129>()), peer: format!("{}", peer_b_wg_peer_id.fmt_b64::<8129>()),
extra_params: vec![], extra_params: vec![],
}), }),
protocol_version: protocol_version.clone(),
}], }],
}; };
@@ -116,6 +126,7 @@ fn api_integration_api_setup() -> anyhow::Result<()> {
endpoint: Some(peer_a_endpoint.to_owned()), endpoint: Some(peer_a_endpoint.to_owned()),
pre_shared_key: None, pre_shared_key: None,
wg: None, wg: None,
protocol_version: protocol_version.clone(),
}], }],
}; };

View File

@@ -17,6 +17,7 @@ use tempfile::TempDir;
use zerocopy::AsBytes; use zerocopy::AsBytes;
use rosenpass::protocol::SymKey; use rosenpass::protocol::SymKey;
use rosenpass::config::ProtocolVersion;
struct KillChild(std::process::Child); struct KillChild(std::process::Child);
@@ -37,7 +38,16 @@ impl Drop for KillChild {
} }
#[test] #[test]
fn api_integration_test() -> anyhow::Result<()> { fn api_integration_test_v02() -> anyhow::Result<()> {
api_integration_test(ProtocolVersion::V02)
}
fn api_integration_test_v03() -> anyhow::Result<()> {
api_integration_test(ProtocolVersion::V03)
}
fn api_integration_test(protocol_version: ProtocolVersion) -> anyhow::Result<()> {
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets(); rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
let dir = TempDir::with_prefix("rosenpass-api-integration-test")?; let dir = TempDir::with_prefix("rosenpass-api-integration-test")?;
@@ -73,6 +83,7 @@ fn api_integration_test() -> anyhow::Result<()> {
endpoint: None, endpoint: None,
pre_shared_key: None, pre_shared_key: None,
wg: None, wg: None,
protocol_version: protocol_version.clone(),
}], }],
}; };
@@ -93,6 +104,7 @@ fn api_integration_test() -> anyhow::Result<()> {
endpoint: Some(peer_a_endpoint.to_owned()), endpoint: Some(peer_a_endpoint.to_owned()),
pre_shared_key: None, pre_shared_key: None,
wg: None, wg: None,
protocol_version: protocol_version.clone(),
}], }],
}; };

View File

@@ -13,13 +13,23 @@ use rosenpass::{
app_server::{ipv4_any_binding, ipv6_any_binding, AppServer, AppServerTest, MAX_B64_KEY_SIZE}, app_server::{ipv4_any_binding, ipv6_any_binding, AppServer, AppServerTest, MAX_B64_KEY_SIZE},
protocol::{SPk, SSk, SymKey}, protocol::{SPk, SSk, SymKey},
}; };
use rosenpass::config::ProtocolVersion;
use rosenpass_cipher_traits::Kem; use rosenpass_cipher_traits::Kem;
use rosenpass_ciphers::kem::StaticKem; use rosenpass_ciphers::kem::StaticKem;
use rosenpass_secret_memory::Secret; use rosenpass_secret_memory::Secret;
use rosenpass_util::{file::LoadValueB64, functional::run, mem::DiscardResultExt, result::OkExt}; use rosenpass_util::{file::LoadValueB64, functional::run, mem::DiscardResultExt, result::OkExt};
#[test] #[test]
fn key_exchange_with_app_server() -> anyhow::Result<()> { fn key_exchange_with_app_server_v02() -> anyhow::Result<()> {
key_exchange_with_app_server(ProtocolVersion::V02)
}
#[test]
fn key_exchange_with_app_server_v03() -> anyhow::Result<()> {
key_exchange_with_app_server(ProtocolVersion::V03)
}
fn key_exchange_with_app_server(protocol_version: ProtocolVersion) -> anyhow::Result<()> {
let tmpdir = tempfile::tempdir()?; let tmpdir = tempfile::tempdir()?;
let outfile_a = tmpdir.path().join("osk_a"); let outfile_a = tmpdir.path().join("osk_a");
let outfile_b = tmpdir.path().join("osk_b"); let outfile_b = tmpdir.path().join("osk_b");
@@ -57,7 +67,7 @@ fn key_exchange_with_app_server() -> anyhow::Result<()> {
let port = otr_port; let port = otr_port;
let hostname = is_client.then(|| format!("[::1]:{port}")); let hostname = is_client.then(|| format!("[::1]:{port}"));
srv.app_srv srv.app_srv
.add_peer(psk, pk, outfile, broker_peer, hostname)?; .add_peer(psk, pk, outfile, broker_peer, hostname, protocol_version.clone())?;
srv.app_srv.event_loop() srv.app_srv.event_loop()
}) })

View File

@@ -251,7 +251,7 @@ fn check_exchange_under_normal() {
fs::remove_dir_all(&tmpdir).unwrap(); fs::remove_dir_all(&tmpdir).unwrap();
} }
// check that we can trigger a DoS condition and we can exchange keys under DoS // check that we can trigger a DoS condition, and we can exchange keys under DoS
// This test creates a responder (server) with the feature flag "integration_test_always_under_load" to always be under load condition for the test. // This test creates a responder (server) with the feature flag "integration_test_always_under_load" to always be under load condition for the test.
#[test] #[test]
#[serial] #[serial]

View File

@@ -9,20 +9,26 @@ use rosenpass_cipher_traits::Kem;
use rosenpass_ciphers::kem::StaticKem; use rosenpass_ciphers::kem::StaticKem;
use rosenpass_util::result::OkExt; use rosenpass_util::result::OkExt;
use rosenpass::protocol::{ use rosenpass::protocol::{testutils::time_travel_forward, CryptoServer, HostIdentification, MsgBuf, PeerPtr, PollResult, ProtocolVersion, SPk, SSk, SymKey, Timing, UNENDING};
testutils::time_travel_forward, CryptoServer, HostIdentification, MsgBuf, PeerPtr, PollResult,
SPk, SSk, SymKey, Timing, UNENDING,
};
// TODO: Most of the utility functions in here should probably be moved to // TODO: Most of the utility functions in here should probably be moved to
// rosenpass::protocol::testutils; // rosenpass::protocol::testutils;
#[test] #[test]
fn test_successful_exchange_with_poll() -> anyhow::Result<()> { fn test_successful_exchange_with_poll_v02() -> anyhow::Result<()> {
test_successful_exchange_with_poll(ProtocolVersion::V02)
}
#[test]
fn test_successful_exchange_with_poll_v03() -> anyhow::Result<()> {
test_successful_exchange_with_poll(ProtocolVersion::V03)
}
fn test_successful_exchange_with_poll(protocol_version: ProtocolVersion) -> anyhow::Result<()> {
// Set security policy for storing secrets; choose the one that is faster for testing // 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()?; let mut sim = RosenpassSimulator::new(protocol_version)?;
sim.poll_loop(150)?; // Poll 75 times sim.poll_loop(150)?; // Poll 75 times
let transcript = sim.transcript; let transcript = sim.transcript;
@@ -79,12 +85,21 @@ fn test_successful_exchange_with_poll() -> anyhow::Result<()> {
} }
#[test] #[test]
fn test_successful_exchange_under_packet_loss() -> anyhow::Result<()> { fn test_successful_exchange_under_packet_loss_v02() -> anyhow::Result<()> {
test_successful_exchange_under_packet_loss(ProtocolVersion::V02)
}
#[test]
fn test_successful_exchange_under_packet_loss_v03() -> anyhow::Result<()> {
test_successful_exchange_under_packet_loss(ProtocolVersion::V03)
}
fn test_successful_exchange_under_packet_loss(protocol_version: ProtocolVersion) -> anyhow::Result<()> {
// Set security policy for storing secrets; choose the one that is faster for testing // 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();
// Create the simulator // Create the simulator
let mut sim = RosenpassSimulator::new()?; let mut sim = RosenpassSimulator::new(protocol_version)?;
// Make sure the servers are set to under load condition // Make sure the servers are set to under load condition
sim.srv_a.under_load = true; sim.srv_a.under_load = true;
@@ -272,7 +287,7 @@ struct SimulatorServer {
impl RosenpassSimulator { impl RosenpassSimulator {
/// Set up the simulator /// Set up the simulator
fn new() -> anyhow::Result<Self> { fn new(protocol_version: ProtocolVersion) -> 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())?;
@@ -285,8 +300,8 @@ 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)?; 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)?; let peer_b = srv_b.add_peer(Some(psk), peer_a_pk, protocol_version.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);
@@ -314,8 +329,8 @@ impl RosenpassSimulator {
Ok(()) Ok(())
} }
/// Every call to poll produces one [TranscriptEvent] and /// Every call to poll produces one [TranscriptEvent]
/// and implicitly adds it to [Self:::transcript] /// and implicitly adds it to [Self::transcript]
fn poll(&mut self) -> anyhow::Result<&TranscriptEvent> { fn poll(&mut self) -> anyhow::Result<&TranscriptEvent> {
let ev = TranscriptEvent::begin_poll() let ev = TranscriptEvent::begin_poll()
.try_fold_with(|| self.poll_focus.poll(self))? .try_fold_with(|| self.poll_focus.poll(self))?