Start splitting protocol.rs into multiple files (#655)
Some checks failed
Nix / Build i686-linux.rosenpass (push) Has been cancelled
Nix / Run Nix checks on i686-linux (push) Has been cancelled
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
Build Docker Images / build-and-test-rp (amd64) (push) Has been cancelled
Build Docker Images / build-and-test-rp (arm64) (push) Has been cancelled
Nix on Mac / Build aarch64-darwin.rosenpass (push) Has been cancelled
Nix on Mac / Build aarch64-darwin.rp (push) Has been cancelled
Nix on Mac / Run Nix checks on aarch64-darwin (push) Has been cancelled
Nix / Build x86_64-linux.proverif-patched (push) Has been cancelled
Nix / Build x86_64-linux.rosenpass (push) Has been cancelled
Nix / Build aarch64-linux.rosenpass (push) Has been cancelled
Nix / Build aarch64-linux.rp (push) Has been cancelled
Nix / Build x86_64-linux.rosenpass-static (push) Has been cancelled
Nix / Build x86_64-linux.rp-static (push) Has been cancelled
Nix / Build x86_64-linux.whitepaper (push) Has been cancelled
Nix / Run Nix checks on x86_64-linux (push) Has been cancelled
Nix / Upload whitepaper x86_64-linux (push) Has been cancelled
QC Mac / cargo-test-mac (push) Has been cancelled
QC / prettier (push) Has been cancelled
QC / Shellcheck (push) Has been cancelled
QC / Rust code formatting (push) Has been cancelled
QC / cargo-bench (push) Has been cancelled
QC / mandoc (push) Has been cancelled
QC / cargo-audit (push) Has been cancelled
QC / cargo-clippy (push) Has been cancelled
QC / cargo-doc (push) Has been cancelled
QC / cargo-test (push) Has been cancelled
QC / cargo-test-nix-devshell-x86_64-linux (push) Has been cancelled
QC / cargo-fuzz (push) Has been cancelled
QC / codecov (push) Has been cancelled
Regressions / multi-peer (push) Has been cancelled
Regressions / boot-race (push) Has been cancelled
Supply-Chain / Deny dependencies with vulnerabilities or incompatible licenses (push) Has been cancelled
Supply-Chain / Supply Chain Report (push) Has been cancelled
Supply-Chain / Vet Dependencies (push) Has been cancelled
Nix / Build i686-linux.default (push) Has been cancelled
Nix / Build i686-linux.rosenpass-oci-image (push) Has been cancelled
Nix / Build x86_64-linux.default (push) Has been cancelled
Nix / Build x86_64-linux.proof-proverif (push) Has been cancelled
Build Docker Images / docker-image-rosenpass (arm64) (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
Build Docker Images / docker-image-rp (amd64) (push) Has been cancelled
Build Docker Images / docker-image-rp (arm64) (push) Has been cancelled
Build Docker Images / docker-image-rosenpass (amd64) (push) Has been cancelled
Build Docker Images / merge-digests (rosenpass) (push) Has been cancelled
Build Docker Images / merge-digests (rp) (push) Has been cancelled
Nix on Mac / Build aarch64-darwin.default (push) Has been cancelled
Nix on Mac / Build aarch64-darwin.release-package (push) Has been cancelled
Nix on Mac / Build aarch64-darwin.rosenpass-oci-image (push) Has been cancelled
Nix / Build x86_64-linux.release-package (push) Has been cancelled
Nix / Build x86_64-linux.rosenpass-oci-image (push) Has been cancelled
Nix / Build aarch64-linux.rosenpass-oci-image (push) Has been cancelled
Nix / Build x86_64-linux.rosenpass-static-oci-image (push) Has been cancelled
Dependent Issues / check (push) Has been cancelled

This commit is contained in:
Karolin Varner
2025-06-24 14:50:52 +02:00
committed by GitHub
23 changed files with 1186 additions and 1068 deletions

View File

@@ -1,7 +1,6 @@
use anyhow::Result;
use rosenpass::protocol::{
CryptoServer, HandleMsgResult, MsgBuf, PeerPtr, ProtocolVersion, SPk, SSk, SymKey,
};
use rosenpass::protocol::basic_types::{MsgBuf, SPk, SSk, SymKey};
use rosenpass::protocol::{CryptoServer, HandleMsgResult, PeerPtr, ProtocolVersion};
use std::ops::DerefMut;
use rosenpass_cipher_traits::primitives::Kem;

View File

@@ -14,9 +14,8 @@ use rosenpass_ciphers::StaticKem;
use rosenpass_secret_memory::secret_policy_try_use_memfd_secrets;
use rosenpass_util::trace_bench::RpEventType;
use rosenpass::protocol::{
CryptoServer, HandleMsgResult, MsgBuf, PeerPtr, ProtocolVersion, SPk, SSk, SymKey,
};
use rosenpass::protocol::basic_types::{MsgBuf, SPk, SSk, SymKey};
use rosenpass::protocol::{CryptoServer, HandleMsgResult, PeerPtr, ProtocolVersion};
const ITERATIONS: usize = 100;

View File

@@ -158,10 +158,10 @@ where
);
// Actually read the secrets
let mut sk = crate::protocol::SSk::zero();
let mut sk = crate::protocol::basic_types::SSk::zero();
sk_io.read_exact_til_end(sk.secret_mut()).einvalid_req()?;
let mut pk = crate::protocol::SPk::zero();
let mut pk = crate::protocol::basic_types::SPk::zero();
pk_io.read_exact_til_end(pk.borrow_mut()).einvalid_req()?;
// Retrieve the construction site

View File

@@ -47,7 +47,8 @@ use crate::protocol::BuildCryptoServer;
use crate::protocol::HostIdentification;
use crate::{
config::Verbosity,
protocol::{CryptoServer, MsgBuf, PeerPtr, SPk, SSk, SymKey, Timing},
protocol::basic_types::{MsgBuf, SPk, SSk, SymKey},
protocol::{timing::Timing, CryptoServer, PeerPtr},
};
use rosenpass_util::attempt;
use rosenpass_util::b64::B64Display;
@@ -1337,7 +1338,7 @@ impl AppServer {
break A::SendRetransmission(AppPeerPtr(no))
}
Some(C::Sleep(timeout)) => timeout, // No event from crypto-server, do IO
None => crate::protocol::UNENDING, // Crypto server is uninitialized, do IO
None => crate::protocol::timing::UNENDING, // Crypto server is uninitialized, do IO
};
// Perform IO (look for a message)

View File

@@ -17,7 +17,7 @@ use std::path::PathBuf;
use crate::app_server::AppServerTest;
use crate::app_server::{AppServer, BrokerPeer};
use crate::protocol::{SPk, SSk, SymKey};
use crate::protocol::basic_types::{SPk, SSk, SymKey};
use super::config;
@@ -607,8 +607,8 @@ impl CliArgs {
/// generate secret and public keys, store in files according to the paths passed as arguments
pub fn generate_and_save_keypair(secret_key: PathBuf, public_key: PathBuf) -> anyhow::Result<()> {
let mut ssk = crate::protocol::SSk::random();
let mut spk = crate::protocol::SPk::random();
let mut ssk = crate::protocol::basic_types::SSk::random();
let mut spk = crate::protocol::basic_types::SPk::random();
StaticKem.keygen(ssk.secret_mut(), spk.deref_mut())?;
ssk.store_secret(secret_key)?;
spk.store(public_key)

View File

@@ -7,7 +7,7 @@
//! - TODO: support `~` in <https://github.com/rosenpass/rosenpass/issues/237>
//! - TODO: provide tooling to create config file from shell <https://github.com/rosenpass/rosenpass/issues/247>
use crate::protocol::{SPk, SSk};
use crate::protocol::basic_types::{SPk, SSk};
use rosenpass_util::file::LoadValue;
use std::{
collections::HashSet,

View File

@@ -0,0 +1,36 @@
//! Key types and other fundamental types used in the Rosenpass protocol
use rosenpass_cipher_traits::primitives::{Aead, Kem};
use rosenpass_ciphers::{EphemeralKem, StaticKem, XAead, KEY_LEN};
use rosenpass_secret_memory::{Public, PublicBox, Secret};
use crate::msgs::{BISCUIT_ID_LEN, MAX_MESSAGE_LEN, SESSION_ID_LEN};
/// Static public key
///
/// Using [PublicBox] instead of [Public] because Classic McEliece keys are very large.
pub type SPk = PublicBox<{ StaticKem::PK_LEN }>;
/// Static secret key
pub type SSk = Secret<{ StaticKem::SK_LEN }>;
/// Ephemeral public key
pub type EPk = Public<{ EphemeralKem::PK_LEN }>;
pub type ESk = Secret<{ EphemeralKem::SK_LEN }>;
/// Symmetric key
pub type SymKey = Secret<KEY_LEN>;
/// 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>;
/// Session ID
pub type SessionId = Public<SESSION_ID_LEN>;
/// Biscuit ID
pub type BiscuitId = Public<BISCUIT_ID_LEN>;
/// Nonce for use with random-nonce AEAD
pub type XAEADNonce = Public<{ XAead::NONCE_LEN }>;
/// Buffer capably of holding any Rosenpass protocol message
pub type MsgBuf = Public<MAX_MESSAGE_LEN>;
/// Server-local peer number; this is just the index in [super::CryptoServer::peers]
pub type PeerNo = usize;

View File

@@ -1,4 +1,5 @@
use super::{CryptoServer, PeerPtr, SPk, SSk, SymKey};
use super::basic_types::{SPk, SSk, SymKey};
use super::{CryptoServer, PeerPtr};
use crate::config::ProtocolVersion;
use rosenpass_util::{
build::Build,
@@ -47,7 +48,8 @@ impl Keypair {
/// # Example
///
/// ```rust
/// use rosenpass::protocol::{Keypair, SSk, SPk};
/// use rosenpass::protocol::basic_types::{SSk, SPk};
/// use rosenpass::protocol::Keypair;
///
/// // We have to define the security policy before using Secrets.
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
@@ -66,12 +68,13 @@ impl Keypair {
/// Creates a new "empty" key pair. All bytes are initialized to zero.
///
/// See [SSk:zero()][crate::protocol::SSk::zero] and [SPk:zero()][crate::protocol::SPk::zero], respectively.
/// See [SSk:zero()][SSk::zero] and [SPk:zero()][SPk::zero], respectively.
///
/// # Example
///
/// ```rust
/// use rosenpass::protocol::{Keypair, SSk, SPk};
/// use rosenpass::protocol::basic_types::{SSk, SPk};
/// use rosenpass::protocol::Keypair;
///
/// // We have to define the security policy before using Secrets.
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
@@ -90,7 +93,7 @@ impl Keypair {
/// Creates a new (securely-)random key pair. The mechanism is described in [rosenpass_secret_memory::Secret].
///
/// See [SSk:random()][crate::protocol::SSk::random] and [SPk:random()][crate::protocol::SPk::random], respectively.
/// See [SSk:random()][SSk::random] and [SPk:random()][SPk::random], respectively.
pub fn random() -> Self {
Self::new(SSk::random(), SPk::random())
}
@@ -127,7 +130,7 @@ pub struct MissingKeypair;
///
/// There are multiple ways of creating a crypto server:
///
/// 1. Provide the key pair at initialization time (using [CryptoServer::new][crate::protocol::CryptoServer::new])
/// 1. Provide the key pair at initialization time (using [CryptoServer::new][CryptoServer::new])
/// 2. Provide the key pair at a later time (using [BuildCryptoServer::empty])
///
/// With BuildCryptoServer, you can gradually configure parameters as they become available.
@@ -145,7 +148,8 @@ pub struct MissingKeypair;
///
/// ```rust
/// use rosenpass_util::build::Build;
/// use rosenpass::protocol::{BuildCryptoServer, Keypair, PeerParams, SPk, SymKey};
/// use rosenpass::protocol::basic_types::{SPk, SymKey};
/// use rosenpass::protocol::{BuildCryptoServer, Keypair, PeerParams};
/// use rosenpass::config::ProtocolVersion;
///
/// // We have to define the security policy before using Secrets.
@@ -205,13 +209,13 @@ impl Build<CryptoServer> for BuildCryptoServer {
}
#[derive(Debug)]
/// Cryptographic key(s) identifying the connected [peer][crate::protocol::Peer] ("client")
/// Cryptographic key(s) identifying the connected [peer][super::Peer] ("client")
/// for a given session that is being managed by the crypto server.
///
/// Each peer must be identified by a [public key (SPk)][crate::protocol::SPk].
/// Optionally, a [symmetric key (SymKey)][crate::protocol::SymKey]
/// Each peer must be identified by a [public key (SPk)][SPk].
/// Optionally, a [symmetric key (SymKey)][SymKey]
/// can be provided when setting up the connection.
/// For more information on the intended usage and security considerations, see [Peer::psk][crate::protocol::Peer::psk] and [Peer::spkt][crate::protocol::Peer::spkt].
/// For more information on the intended usage and security considerations, see [Peer::psk][super::Peer::psk] and [Peer::spkt][super::Peer::spkt].
pub struct PeerParams {
/// Pre-shared (symmetric) encryption keys that should be used with this peer.
pub psk: Option<SymKey>,
@@ -322,7 +326,8 @@ impl BuildCryptoServer {
/// secret_policy_use_only_malloc_secrets();
///
/// use rosenpass_util::build::Build;
/// use rosenpass::protocol::{BuildCryptoServer, Keypair, SymKey, SPk};
/// use rosenpass::protocol::basic_types::{SymKey, SPk};
/// use rosenpass::protocol::{BuildCryptoServer, Keypair};
///
/// // Deferred initialization: Create builder first, add some peers later
/// let keypair_option = Some(Keypair::random());
@@ -388,7 +393,8 @@ impl BuildCryptoServer {
/// secret_policy_use_only_malloc_secrets();
///
/// use rosenpass_util::build::Build;
/// use rosenpass::protocol::{BuildCryptoServer, Keypair, SymKey, SPk};
/// use rosenpass::protocol::basic_types::{SymKey, SPk};
/// use rosenpass::protocol::{BuildCryptoServer, Keypair};
///
/// let keypair = Keypair::random();
/// let peer_pk = SPk::random();

View File

@@ -0,0 +1,64 @@
//! Constants and configuration values used in the rosenpass core protocol
use crate::msgs::MAC_SIZE;
use super::timing::Timing;
/// Time after which the responder attempts to rekey the session
///
/// From the wireguard paper: rekey every two minutes,
/// discard the key if no rekey is achieved within three
pub const REKEY_AFTER_TIME_RESPONDER: Timing = 120.0;
/// Time after which the initiator attempts to rekey the session.
///
/// This happens ten seconds after [REKEY_AFTER_TIME_RESPONDER], so
/// parties would usually switch roles after every handshake.
///
/// From the wireguard paper: rekey every two minutes,
/// discard the key if no rekey is achieved within three
pub const REKEY_AFTER_TIME_INITIATOR: Timing = 130.0;
/// Time after which either party rejects the current key.
///
/// At this point a new key should have been negotiated.
///
/// Rejection happens 50-60 seconds after key renegotiation
/// to allow for a graceful handover.
/// From the wireguard paper: rekey every two minutes,
/// discard the key if no rekey is achieved within three
pub const REJECT_AFTER_TIME: Timing = 180.0;
/// The length of the `cookie_secret` in the [whitepaper](https://rosenpass.eu/whitepaper.pdf)
pub const COOKIE_SECRET_LEN: usize = MAC_SIZE;
/// The life time of the `cookie_secret` in the [whitepaper](https://rosenpass.eu/whitepaper.pdf)
pub const COOKIE_SECRET_EPOCH: Timing = 120.0;
/// Length of a cookie value (see info about the cookie mechanism in the [whitepaper](https://rosenpass.eu/whitepaper.pdf))
pub const COOKIE_VALUE_LEN: usize = MAC_SIZE;
/// Time after which to delete a cookie, as the initiator, for a certain peer (see info about the cookie mechanism in the [whitepaper](https://rosenpass.eu/whitepaper.pdf))
pub const PEER_COOKIE_VALUE_EPOCH: Timing = 120.0;
/// Seconds until the biscuit key is changed; we issue biscuits
/// using one biscuit key for one epoch and store the biscuit for
/// decryption for a second epoch
///
/// The biscuit mechanism is used to make sure the responder is stateless in our protocol.
pub const BISCUIT_EPOCH: Timing = 300.0;
/// The initiator opportunistically retransmits their messages; it applies an increasing delay
/// between each retreansmission. This is the factor by which the delay grows after each
/// retransmission.
pub const RETRANSMIT_DELAY_GROWTH: Timing = 2.0;
/// The initiator opportunistically retransmits their messages; it applies an increasing delay
/// between each retreansmission. This is the initial delay between retransmissions.
pub const RETRANSMIT_DELAY_BEGIN: Timing = 0.5;
/// The initiator opportunistically retransmits their messages; it applies an increasing delay
/// between each retreansmission. This is the maximum delay between retransmissions.
pub const RETRANSMIT_DELAY_END: Timing = 10.0;
/// The initiator opportunistically retransmits their messages; it applies an increasing delay
/// between each retreansmission. This is the jitter (randomness) applied to the retransmission
/// delay.
pub const RETRANSMIT_DELAY_JITTER: Timing = 0.5;
/// This is the maximum delay that can separate two events for us to consider the events to have
/// happened at the same time.
pub const EVENT_GRACE: Timing = 0.0025;

View File

@@ -0,0 +1,98 @@
//! Cryptographic key management for cookies and biscuits used in the protocol
//!
//! Cookies in general are conceptually similar to browser cookies;
//! i.e. mechanisms to store information in the party connected to via network.
//!
//! In our case specifically we refer to any mechanisms in the Rosenpass protocol
//! where a peer stores some information in the other party that is cryptographically
//! protected using a temporary, randomly generated key. This file contains the mechanisms
//! used to store the secret keys.
//!
//! We have two cookie-mechanisms in particular:
//!
//! - Rosenpass "biscuits" — the mechanism used to make sure the Rosenpass protocol is stateless
//! with respect to the responder
//! - WireGuard's cookie mechanism to enable proof of IP ownership; Rosenpass has experimental
//! support for this mechanism
//!
//! The CookieStore type is also used to store cookie secrets sent from the responder to the
//! initiator. This is a bad design and we should separate out this functionality.
//!
//! TODO: CookieStore should not be used for cookie secrets sent from responder to initiator.
//! TODO: Move cookie lifetime management functionality into here
use rosenpass_ciphers::KEY_LEN;
use rosenpass_secret_memory::Secret;
use super::{constants::COOKIE_SECRET_LEN, timing::Timing};
/// Container for storing cookie secrets like [BiscuitKey] or [CookieSecret].
///
/// This is really just a secret key and a time stamp of creation. Concrete
/// usages (such as for the biscuit key) impose a time limit about how long
/// a key can be used and the time of creation is used to impose that time limit.
///
/// # Examples
///
/// ```
/// use rosenpass_util::time::Timebase;
/// use rosenpass::protocol::{timing::BCE, basic_types::SymKey, cookies::CookieStore};
///
/// rosenpass_secret_memory::secret_policy_try_use_memfd_secrets();
///
/// let fixed_secret = SymKey::random();
/// let timebase = Timebase::default();
///
/// let mut store = CookieStore::<32>::new();
/// assert_ne!(store.value.secret(), SymKey::zero().secret());
/// assert_eq!(store.created_at, BCE);
///
/// let time_before_call = timebase.now();
/// store.update(&timebase, fixed_secret.secret());
/// assert_eq!(store.value.secret(), fixed_secret.secret());
/// assert!(store.created_at < timebase.now());
/// assert!(store.created_at > time_before_call);
///
/// // Same as new()
/// store.erase();
/// assert_ne!(store.value.secret(), SymKey::zero().secret());
/// assert_eq!(store.created_at, BCE);
///
/// let secret_before_call = store.value.clone();
/// let time_before_call = timebase.now();
/// store.randomize(&timebase);
/// assert_ne!(store.value.secret(), secret_before_call.secret());
/// assert!(store.created_at < timebase.now());
/// assert!(store.created_at > time_before_call);
/// ```
#[derive(Debug)]
pub struct CookieStore<const N: usize> {
/// Time of creation of the secret key
pub created_at: Timing,
/// The secret key
pub value: Secret<N>,
}
/// Stores cookie secret, which is used to create a rotating the cookie value
///
/// Concrete value is in [super::CryptoServer::cookie_secrets].
///
/// The pointer type is [super::ServerCookieSecretPtr].
pub type CookieSecret = CookieStore<COOKIE_SECRET_LEN>;
/// Storage for our biscuit keys.
///
/// The biscuit keys encrypt what we call "biscuits".
/// These biscuits contain the responder state for a particular handshake. By moving
/// state into these biscuits, we make sure the responder is stateless.
///
/// A Biscuit is like a fancy cookie. To avoid state disruption attacks,
/// the responder doesn't store state. Instead the state is stored in a
/// Biscuit, that is encrypted using the [BiscuitKey] which is only known to
/// the Responder. Thus secrecy of the Responder state is not violated, still
/// the responder can avoid storing this state.
///
/// Concrete value is in [super::CryptoServer::biscuit_keys].
///
/// The pointer type is [super::BiscuitKeyPtr].
pub type BiscuitKey = CookieStore<KEY_LEN>;

View File

@@ -0,0 +1,45 @@
//! Quick lookup of values in [super::CryptoServer]
use std::collections::HashMap;
use super::basic_types::{PeerId, PeerNo, SessionId};
use super::KnownResponseHash;
/// Maps various keys to peer (numbers).
///
/// See:
/// - [super::CryptoServer::index]
/// - [super::CryptoServer::peers]
/// - [PeerNo]
/// - [super::PeerPtr]
/// - [super::Peer]
pub type PeerIndex = HashMap<PeerIndexKey, PeerNo>;
/// We maintain various indices in [super::CryptoServer::index], mapping some key to a particular
/// [PeerNo], i.e. to an index in [super::CryptoServer::peers]. These are the possible index key.
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub enum PeerIndexKey {
/// Lookup of a particular peer given the [PeerId], i.e. a value derived from the peers public
/// key as created by [super::CryptoServer::pidm] or [super::Peer::pidt].
///
/// The peer id is used by the initiator to tell the responder about its identity in
/// [crate::msgs::InitHello].
///
/// See also the pointer types [super::PeerPtr].
Peer(PeerId),
/// Lookup of a particular session id.
///
/// This is used to look up both established sessions (see
/// [super::CryptoServer::lookup_session]) and ongoing handshakes (see [super::CryptoServer::lookup_handshake]).
///
/// Lookup of a peer to get an established session or a handshake is sufficient, because a peer
/// contains a limited number of sessions and handshakes ([super::Peer::session] and [super::Peer::handshake] respectively).
///
/// See also the pointer types [super::IniHsPtr] and [super::SessionPtr].
Sid(SessionId),
/// Lookup of a cached response ([crate::msgs::Envelope]<[crate::msgs::EmptyData]>) to an [crate::msgs::InitConf] (i.e.
/// [crate::msgs::Envelope]<[crate::msgs::InitConf]>) message.
///
/// See [super::KnownInitConfResponsePtr] on how this value is maintained.
KnownInitConfResponse(KnownResponseHash),
}

View File

@@ -27,9 +27,8 @@
//! use rosenpass_secret_memory::policy::*;
//! use rosenpass_cipher_traits::primitives::Kem;
//! use rosenpass_ciphers::StaticKem;
//! use rosenpass::{
//! protocol::{SSk, SPk, MsgBuf, PeerPtr, CryptoServer, SymKey},
//! };
//! use rosenpass::protocol::basic_types::{SSk, SPk, MsgBuf, SymKey};
//! use rosenpass::protocol::{PeerPtr, CryptoServer};
//! # fn main() -> anyhow::Result<()> {
//! // Set security policy for storing secrets
//!
@@ -76,8 +75,19 @@
//! ```
mod build_crypto_server;
pub use build_crypto_server::*;
pub mod basic_types;
pub mod constants;
pub mod cookies;
pub mod index;
pub mod testutils;
pub mod timing;
pub mod zerocopy;
#[allow(clippy::module_inception)]
mod protocol;
pub use build_crypto_server::*;
pub use protocol::*;
#[cfg(test)]
mod test;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,682 @@
use std::{borrow::BorrowMut, fmt::Display, net::SocketAddrV4, ops::DerefMut};
use anyhow::{Context, Result};
use serial_test::serial;
use zerocopy::{AsBytes, FromBytes, FromZeroes};
use rosenpass_cipher_traits::primitives::Kem;
use rosenpass_ciphers::StaticKem;
use rosenpass_secret_memory::Public;
use rosenpass_util::mem::DiscardResultExt;
use crate::msgs::{EmptyData, Envelope, InitConf, InitHello, MsgType, RespHello, MAX_MESSAGE_LEN};
use super::{
basic_types::{MsgBuf, SPk, SSk, SymKey},
constants::REKEY_AFTER_TIME_RESPONDER,
zerocopy::{truncating_cast_into, truncating_cast_into_nomut},
CryptoServer, HandleMsgResult, HostIdentification, KnownInitConfResponsePtr, PeerPtr,
PollResult, ProtocolVersion,
};
struct VecHostIdentifier(Vec<u8>);
impl HostIdentification for VecHostIdentifier {
fn encode(&self) -> &[u8] {
&self.0
}
}
impl Display for VecHostIdentifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.0)
}
}
impl From<Vec<u8>> for VecHostIdentifier {
fn from(v: Vec<u8>) -> Self {
VecHostIdentifier(v)
}
}
fn setup_logging() {
use std::io::Write;
let mut log_builder = env_logger::Builder::from_default_env(); // sets log level filter from environment (or defaults)
log_builder.filter_level(log::LevelFilter::Info);
log_builder.format_timestamp_nanos();
log_builder.format(|buf, record| {
let ts_format = buf.timestamp_nanos().to_string();
writeln!(buf, "{}: {}", &ts_format[14..], record.args())
});
let _ = log_builder.try_init();
}
#[test]
#[serial]
fn handles_incorrect_size_messages_v02() {
handles_incorrect_size_messages(ProtocolVersion::V02)
}
#[test]
#[serial]
fn handles_incorrect_size_messages_v03() {
handles_incorrect_size_messages(ProtocolVersion::V03)
}
/// Ensure that the protocol implementation can deal with truncated
/// messages and with overlong messages.
///
/// This test performs a complete handshake between two randomly generated
/// servers; instead of delivering the message correctly at first messages
/// of length zero through about 1.2 times the correct message size are delivered.
///
/// Producing an error is expected on each of these messages.
///
/// Finally the correct message is delivered and the same process
/// starts again in the other direction.
///
/// Through all this, the handshake should still successfully terminate;
/// i.e. an exchanged key must be produced in both servers.
fn handles_incorrect_size_messages(protocol_version: ProtocolVersion) {
setup_logging();
rosenpass_secret_memory::secret_policy_try_use_memfd_secrets();
stacker::grow(8 * 1024 * 1024, || {
const OVERSIZED_MESSAGE: usize = ((MAX_MESSAGE_LEN as f32) * 1.2) as usize;
type MsgBufPlus = Public<OVERSIZED_MESSAGE>;
const PEER0: PeerPtr = PeerPtr(0);
let (mut me, mut they) = make_server_pair(protocol_version).unwrap();
let (mut msgbuf, mut resbuf) = (MsgBufPlus::zero(), MsgBufPlus::zero());
// Process the entire handshake
let mut msglen = Some(me.initiate_handshake(PEER0, &mut *resbuf).unwrap());
while let Some(l) = msglen {
std::mem::swap(&mut me, &mut they);
std::mem::swap(&mut msgbuf, &mut resbuf);
msglen = test_incorrect_sizes_for_msg(&mut me, &*msgbuf, l, &mut *resbuf);
}
assert_eq!(
me.osk(PEER0).unwrap().secret(),
they.osk(PEER0).unwrap().secret()
);
});
}
/// Used in handles_incorrect_size_messages() to first deliver many truncated
/// and overlong messages, finally the correct message is delivered and the response
/// returned.
fn test_incorrect_sizes_for_msg(
srv: &mut CryptoServer,
msgbuf: &[u8],
msglen: usize,
resbuf: &mut [u8],
) -> Option<usize> {
resbuf.fill(0);
for l in 0..(((msglen as f32) * 1.2) as usize) {
if l == msglen {
continue;
}
let res = srv.handle_msg(&msgbuf[..l], resbuf);
assert!(res.is_err()); // handle_msg should raise an error
assert!(!resbuf.iter().any(|x| *x != 0)); // resbuf should not have been changed
}
// Apply the proper handle_msg operation
srv.handle_msg(&msgbuf[..msglen], resbuf).unwrap().resp
}
fn keygen() -> Result<(SSk, SPk)> {
// TODO: Copied from the benchmark; deduplicate
let (mut sk, mut pk) = (SSk::zero(), SPk::zero());
StaticKem.keygen(sk.secret_mut(), pk.deref_mut())?;
Ok((sk, pk))
}
fn make_server_pair(protocol_version: ProtocolVersion) -> Result<(CryptoServer, CryptoServer)> {
// TODO: Copied from the benchmark; deduplicate
let psk = SymKey::random();
let ((ska, pka), (skb, pkb)) = (keygen()?, keygen()?);
let (mut a, mut b) = (
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)?;
Ok((a, b))
}
#[test]
#[serial]
fn test_regular_exchange_v02() {
test_regular_exchange(ProtocolVersion::V02)
}
#[test]
#[serial]
fn test_regular_exchange_v03() {
test_regular_exchange(ProtocolVersion::V03)
}
fn test_regular_exchange(protocol_version: ProtocolVersion) {
setup_logging();
rosenpass_secret_memory::secret_policy_try_use_memfd_secrets();
stacker::grow(8 * 1024 * 1024, || {
type MsgBufPlus = Public<MAX_MESSAGE_LEN>;
let (mut a, mut b) = make_server_pair(protocol_version).unwrap();
let mut a_to_b_buf = MsgBufPlus::zero();
let mut b_to_a_buf = MsgBufPlus::zero();
let ip_a: SocketAddrV4 = "127.0.0.1:8080".parse().unwrap();
let mut ip_addr_port_a = ip_a.ip().octets().to_vec();
ip_addr_port_a.extend_from_slice(&ip_a.port().to_be_bytes());
let _ip_b: SocketAddrV4 = "127.0.0.1:8081".parse().unwrap();
let init_hello_len = a.initiate_handshake(PeerPtr(0), &mut *a_to_b_buf).unwrap();
let init_msg_type: MsgType = a_to_b_buf.value[0].try_into().unwrap();
assert_eq!(init_msg_type, MsgType::InitHello);
//B handles InitHello, sends RespHello
let HandleMsgResult { resp, .. } = b
.handle_msg(&a_to_b_buf.as_slice()[..init_hello_len], &mut *b_to_a_buf)
.unwrap();
let resp_hello_len = resp.unwrap();
let resp_msg_type: MsgType = b_to_a_buf.value[0].try_into().unwrap();
assert_eq!(resp_msg_type, MsgType::RespHello);
let HandleMsgResult {
resp,
exchanged_with,
} = a
.handle_msg(&b_to_a_buf[..resp_hello_len], &mut *a_to_b_buf)
.unwrap();
let init_conf_len = resp.unwrap();
let init_conf_msg_type: MsgType = a_to_b_buf.value[0].try_into().unwrap();
assert_eq!(exchanged_with, Some(PeerPtr(0)));
assert_eq!(init_conf_msg_type, MsgType::InitConf);
//B handles InitConf, sends EmptyData
let HandleMsgResult {
resp: _,
exchanged_with,
} = b
.handle_msg(&a_to_b_buf.as_slice()[..init_conf_len], &mut *b_to_a_buf)
.unwrap();
let empty_data_msg_type: MsgType = b_to_a_buf.value[0].try_into().unwrap();
assert_eq!(exchanged_with, Some(PeerPtr(0)));
assert_eq!(empty_data_msg_type, MsgType::EmptyData);
});
}
#[test]
#[serial]
fn test_regular_init_conf_retransmit_v02() {
test_regular_init_conf_retransmit(ProtocolVersion::V02)
}
#[test]
#[serial]
fn test_regular_init_conf_retransmit_v03() {
test_regular_init_conf_retransmit(ProtocolVersion::V03)
}
fn test_regular_init_conf_retransmit(protocol_version: ProtocolVersion) {
setup_logging();
rosenpass_secret_memory::secret_policy_try_use_memfd_secrets();
stacker::grow(8 * 1024 * 1024, || {
type MsgBufPlus = Public<MAX_MESSAGE_LEN>;
let (mut a, mut b) = make_server_pair(protocol_version).unwrap();
let mut a_to_b_buf = MsgBufPlus::zero();
let mut b_to_a_buf = MsgBufPlus::zero();
let ip_a: SocketAddrV4 = "127.0.0.1:8080".parse().unwrap();
let mut ip_addr_port_a = ip_a.ip().octets().to_vec();
ip_addr_port_a.extend_from_slice(&ip_a.port().to_be_bytes());
let _ip_b: SocketAddrV4 = "127.0.0.1:8081".parse().unwrap();
let init_hello_len = a.initiate_handshake(PeerPtr(0), &mut *a_to_b_buf).unwrap();
let init_msg_type: MsgType = a_to_b_buf.value[0].try_into().unwrap();
assert_eq!(init_msg_type, MsgType::InitHello);
//B handles InitHello, sends RespHello
let HandleMsgResult { resp, .. } = b
.handle_msg(&a_to_b_buf.as_slice()[..init_hello_len], &mut *b_to_a_buf)
.unwrap();
let resp_hello_len = resp.unwrap();
let resp_msg_type: MsgType = b_to_a_buf.value[0].try_into().unwrap();
assert_eq!(resp_msg_type, MsgType::RespHello);
//A handles RespHello, sends InitConf, exchanges keys
let HandleMsgResult {
resp,
exchanged_with,
} = a
.handle_msg(&b_to_a_buf[..resp_hello_len], &mut *a_to_b_buf)
.unwrap();
let init_conf_len = resp.unwrap();
let init_conf_msg_type: MsgType = a_to_b_buf.value[0].try_into().unwrap();
assert_eq!(exchanged_with, Some(PeerPtr(0)));
assert_eq!(init_conf_msg_type, MsgType::InitConf);
//B handles InitConf, sends EmptyData
let HandleMsgResult {
resp: _,
exchanged_with,
} = b
.handle_msg(&a_to_b_buf.as_slice()[..init_conf_len], &mut *b_to_a_buf)
.unwrap();
let empty_data_msg_type: MsgType = b_to_a_buf.value[0].try_into().unwrap();
assert_eq!(exchanged_with, Some(PeerPtr(0)));
assert_eq!(empty_data_msg_type, MsgType::EmptyData);
//B handles InitConf again, sends EmptyData
let HandleMsgResult {
resp: _,
exchanged_with,
} = b
.handle_msg(&a_to_b_buf.as_slice()[..init_conf_len], &mut *b_to_a_buf)
.unwrap();
let empty_data_msg_type: MsgType = b_to_a_buf.value[0].try_into().unwrap();
assert!(exchanged_with.is_none());
assert_eq!(empty_data_msg_type, MsgType::EmptyData);
});
}
#[test]
#[serial]
#[cfg(feature = "experiment_cookie_dos_mitigation")]
fn cookie_reply_mechanism_responder_under_load_v02() {
cookie_reply_mechanism_initiator_bails_on_message_under_load(ProtocolVersion::V02)
}
#[test]
#[serial]
#[cfg(feature = "experiment_cookie_dos_mitigation")]
fn cookie_reply_mechanism_responder_under_load_v03() {
cookie_reply_mechanism_initiator_bails_on_message_under_load(ProtocolVersion::V03)
}
#[cfg(feature = "experiment_cookie_dos_mitigation")]
fn cookie_reply_mechanism_responder_under_load(protocol_version: ProtocolVersion) {
use std::{thread::sleep, time::Duration};
use super::{Lifecycle, MortalExt};
setup_logging();
rosenpass_secret_memory::secret_policy_try_use_memfd_secrets();
stacker::grow(8 * 1024 * 1024, || {
type MsgBufPlus = Public<MAX_MESSAGE_LEN>;
let (mut a, mut b) = make_server_pair(protocol_version.clone()).unwrap();
let mut a_to_b_buf = MsgBufPlus::zero();
let mut b_to_a_buf = MsgBufPlus::zero();
let ip_a: SocketAddrV4 = "127.0.0.1:8080".parse().unwrap();
let mut ip_addr_port_a = ip_a.ip().octets().to_vec();
ip_addr_port_a.extend_from_slice(&ip_a.port().to_be_bytes());
let _ip_b: SocketAddrV4 = "127.0.0.1:8081".parse().unwrap();
let init_hello_len = a.initiate_handshake(PeerPtr(0), &mut *a_to_b_buf).unwrap();
let socket_addr_a = std::net::SocketAddr::V4(ip_a);
let mut ip_addr_port_a = match socket_addr_a.ip() {
std::net::IpAddr::V4(ipv4) => ipv4.octets().to_vec(),
std::net::IpAddr::V6(ipv6) => ipv6.octets().to_vec(),
};
ip_addr_port_a.extend_from_slice(&socket_addr_a.port().to_be_bytes());
let ip_addr_port_a: VecHostIdentifier = ip_addr_port_a.into();
//B handles handshake under load, should send cookie reply message with invalid cookie
let HandleMsgResult { resp, .. } = b
.handle_msg_under_load(
&a_to_b_buf.as_slice()[..init_hello_len],
&mut *b_to_a_buf,
&ip_addr_port_a,
)
.unwrap();
let cookie_reply_len = resp.unwrap();
//A handles cookie reply message
a.handle_msg(&b_to_a_buf[..cookie_reply_len], &mut *a_to_b_buf)
.unwrap();
assert_eq!(PeerPtr(0).cv().lifecycle(&a), Lifecycle::Young);
let expected_cookie_value =
crate::hash_domains::cookie_value(protocol_version.keyed_hash())
.unwrap()
.mix(
b.active_or_retired_cookie_secrets()[0]
.unwrap()
.get(&b)
.value
.secret(),
)
.unwrap()
.mix(ip_addr_port_a.encode())
.unwrap()
.into_value()[..16]
.to_vec();
assert_eq!(
PeerPtr(0).cv().get(&a).map(|x| &x.value.secret()[..]),
Some(&expected_cookie_value[..])
);
let retx_init_hello_len = loop {
match a.poll().unwrap() {
PollResult::SendRetransmission(peer) => {
break a.retransmit_handshake(peer, &mut *a_to_b_buf).unwrap();
}
PollResult::Sleep(time) => {
sleep(Duration::from_secs_f64(time));
}
_ => {}
}
};
let retx_msg_type: MsgType = a_to_b_buf.value[0].try_into().unwrap();
assert_eq!(retx_msg_type, MsgType::InitHello);
//B handles retransmitted message
let HandleMsgResult { resp, .. } = b
.handle_msg_under_load(
&a_to_b_buf.as_slice()[..retx_init_hello_len],
&mut *b_to_a_buf,
&ip_addr_port_a,
)
.unwrap();
let _resp_hello_len = resp.unwrap();
let resp_msg_type: MsgType = b_to_a_buf.value[0].try_into().unwrap();
assert_eq!(resp_msg_type, MsgType::RespHello);
});
}
#[test]
#[serial]
#[cfg(feature = "experiment_cookie_dos_mitigation")]
fn cookie_reply_mechanism_initiator_bails_on_message_under_load_v02() {
cookie_reply_mechanism_initiator_bails_on_message_under_load(ProtocolVersion::V02)
}
#[test]
#[serial]
#[cfg(feature = "experiment_cookie_dos_mitigation")]
fn cookie_reply_mechanism_initiator_bails_on_message_under_load_v03() {
cookie_reply_mechanism_initiator_bails_on_message_under_load(ProtocolVersion::V03)
}
#[cfg(feature = "experiment_cookie_dos_mitigation")]
fn cookie_reply_mechanism_initiator_bails_on_message_under_load(protocol_version: ProtocolVersion) {
setup_logging();
rosenpass_secret_memory::secret_policy_try_use_memfd_secrets();
stacker::grow(8 * 1024 * 1024, || {
type MsgBufPlus = Public<MAX_MESSAGE_LEN>;
let (mut a, mut b) = make_server_pair(protocol_version).unwrap();
let mut a_to_b_buf = MsgBufPlus::zero();
let mut b_to_a_buf = MsgBufPlus::zero();
let ip_a: SocketAddrV4 = "127.0.0.1:8080".parse().unwrap();
let mut ip_addr_port_a = ip_a.ip().octets().to_vec();
ip_addr_port_a.extend_from_slice(&ip_a.port().to_be_bytes());
let ip_b: SocketAddrV4 = "127.0.0.1:8081".parse().unwrap();
//A initiates handshake
let init_hello_len = a.initiate_handshake(PeerPtr(0), &mut *a_to_b_buf).unwrap();
//B handles InitHello message, should respond with RespHello
let HandleMsgResult { resp, .. } = b
.handle_msg(&a_to_b_buf.as_slice()[..init_hello_len], &mut *b_to_a_buf)
.unwrap();
let resp_hello_len = resp.unwrap();
let resp_msg_type: MsgType = b_to_a_buf.value[0].try_into().unwrap();
assert_eq!(resp_msg_type, MsgType::RespHello);
let socket_addr_b = std::net::SocketAddr::V4(ip_b);
let mut ip_addr_port_b = [0u8; 18];
let mut ip_addr_port_b_len = 0;
match socket_addr_b.ip() {
std::net::IpAddr::V4(ipv4) => {
ip_addr_port_b[0..4].copy_from_slice(&ipv4.octets());
ip_addr_port_b_len += 4;
}
std::net::IpAddr::V6(ipv6) => {
ip_addr_port_b[0..16].copy_from_slice(&ipv6.octets());
ip_addr_port_b_len += 16;
}
};
ip_addr_port_b[ip_addr_port_b_len..ip_addr_port_b_len + 2]
.copy_from_slice(&socket_addr_b.port().to_be_bytes());
ip_addr_port_b_len += 2;
let ip_addr_port_b: VecHostIdentifier =
ip_addr_port_b[..ip_addr_port_b_len].to_vec().into();
//A handles RespHello message under load, should not send cookie reply
assert!(a
.handle_msg_under_load(
&b_to_a_buf[..resp_hello_len],
&mut *a_to_b_buf,
&ip_addr_port_b
)
.is_err());
});
}
#[test]
fn init_conf_retransmission_v02() -> Result<()> {
init_conf_retransmission(ProtocolVersion::V02)
}
#[test]
fn init_conf_retransmission_v03() -> Result<()> {
init_conf_retransmission(ProtocolVersion::V03)
}
fn init_conf_retransmission(protocol_version: ProtocolVersion) -> anyhow::Result<()> {
rosenpass_secret_memory::secret_policy_try_use_memfd_secrets();
fn keypair() -> Result<(SSk, SPk)> {
let (mut sk, mut pk) = (SSk::zero(), SPk::zero());
StaticKem.keygen(sk.secret_mut(), pk.deref_mut())?;
Ok((sk, pk))
}
fn proc_initiation(srv: &mut CryptoServer, peer: PeerPtr) -> Result<Envelope<InitHello>> {
let mut buf = MsgBuf::zero();
srv.initiate_handshake(peer, buf.as_mut_slice())?
.discard_result();
let msg = truncating_cast_into::<Envelope<InitHello>>(buf.borrow_mut())?;
Ok(msg.read())
}
fn proc_msg<Rx: AsBytes + FromBytes, Tx: AsBytes + FromBytes>(
srv: &mut CryptoServer,
rx: &Envelope<Rx>,
) -> anyhow::Result<Envelope<Tx>> {
let mut buf = MsgBuf::zero();
srv.handle_msg(rx.as_bytes(), buf.as_mut_slice())?
.resp
.context("Failed to produce RespHello message")?
.discard_result();
let msg = truncating_cast_into::<Envelope<Tx>>(buf.borrow_mut())?;
Ok(msg.read())
}
fn proc_init_hello(
srv: &mut CryptoServer,
ih: &Envelope<InitHello>,
) -> anyhow::Result<Envelope<RespHello>> {
proc_msg::<InitHello, RespHello>(srv, ih)
}
fn proc_resp_hello(
srv: &mut CryptoServer,
rh: &Envelope<RespHello>,
) -> anyhow::Result<Envelope<InitConf>> {
proc_msg::<RespHello, InitConf>(srv, rh)
}
fn proc_init_conf(
srv: &mut CryptoServer,
rh: &Envelope<InitConf>,
) -> anyhow::Result<Envelope<EmptyData>> {
proc_msg::<InitConf, EmptyData>(srv, rh)
}
fn poll(srv: &mut CryptoServer) -> anyhow::Result<()> {
// Discard all events; just apply the side effects
while !matches!(srv.poll()?, PollResult::Sleep(_)) {}
Ok(())
}
// TODO: Implement Clone on our message types
fn clone_msg<Msg: AsBytes + FromBytes>(msg: &Msg) -> anyhow::Result<Msg> {
Ok(truncating_cast_into_nomut::<Msg>(msg.as_bytes())?.read())
}
fn break_payload<Msg: AsBytes + FromBytes>(
srv: &mut CryptoServer,
peer: PeerPtr,
msg: &Envelope<Msg>,
) -> anyhow::Result<Envelope<Msg>> {
let mut msg = clone_msg(msg)?;
msg.as_bytes_mut()[memoffset::offset_of!(Envelope<Msg>, payload)] ^= 0x01;
msg.seal(peer, srv)?; // Recalculate seal; we do not want to focus on "seal broken" errs
Ok(msg)
}
fn check_faulty_proc_init_conf(srv: &mut CryptoServer, ic_broken: &Envelope<InitConf>) {
let mut buf = MsgBuf::zero();
let res = srv.handle_msg(ic_broken.as_bytes(), buf.as_mut_slice());
assert!(res.is_err());
}
// we this as a closure in orer to use the protocol_version variable in it.
let check_retransmission = |srv: &mut CryptoServer,
ic: &Envelope<InitConf>,
ic_broken: &Envelope<InitConf>,
rc: &Envelope<EmptyData>|
-> Result<()> {
// Processing the same RespHello package again leads to retransmission (i.e. exactly the
// same output)
let rc_dup = proc_init_conf(srv, ic)?;
assert_eq!(rc.as_bytes(), rc_dup.as_bytes());
// Though if we directly call handle_resp_hello() we get an error since
// retransmission is not being handled by the cryptographic code
let mut discard_resp_conf = EmptyData::new_zeroed();
let res = srv.handle_init_conf(
&ic.payload,
&mut discard_resp_conf,
protocol_version.clone().keyed_hash(),
);
assert!(res.is_err());
// Obviously, a broken InitConf message should still be rejected
check_faulty_proc_init_conf(srv, ic_broken);
Ok(())
};
let (ska, pka) = keypair()?;
let (skb, pkb) = keypair()?;
// initialize server and a pre-shared key
let mut a = CryptoServer::new(ska, pka.clone());
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())?;
// Execute protocol up till the responder confirmation (EmptyData)
let ih1 = proc_initiation(&mut a, b_peer)?;
let rh1 = proc_init_hello(&mut b, &ih1)?;
let ic1 = proc_resp_hello(&mut a, &rh1)?;
let rc1 = proc_init_conf(&mut b, &ic1)?;
// Modified version of ic1 and rc1, for tests that require it
let ic1_broken = break_payload(&mut a, b_peer, &ic1)?;
assert_ne!(ic1.as_bytes(), ic1_broken.as_bytes());
// Modified version of rc1, for tests that require it
let rc1_broken = break_payload(&mut b, a_peer, &rc1)?;
assert_ne!(rc1.as_bytes(), rc1_broken.as_bytes());
// Retransmission works as designed
check_retransmission(&mut b, &ic1, &ic1_broken, &rc1)?;
// Even with a couple of poll operations in between (which clears the cache
// after a time out of two minutes…we should never hit this time out in this
// cache)
for _ in 0..4 {
poll(&mut b)?;
check_retransmission(&mut b, &ic1, &ic1_broken, &rc1)?;
}
// We can even validate that the data is coming out of the cache by changing the cache
// to use our broken messages. It does not matter that these messages are cryptographically
// broken since we insert them manually into the cache
// a_peer.known_init_conf_response()
KnownInitConfResponsePtr::insert_for_request_msg(
&mut b,
a_peer,
&ic1_broken,
rc1_broken.clone(),
);
check_retransmission(&mut b, &ic1_broken, &ic1, &rc1_broken)?;
// Lets reset to the correct message though
KnownInitConfResponsePtr::insert_for_request_msg(&mut b, a_peer, &ic1, rc1.clone());
// Again, nothing changes after calling poll
poll(&mut b)?;
check_retransmission(&mut b, &ic1, &ic1_broken, &rc1)?;
// Except if we jump forward into the future past the point where the responder
// starts to initiate rekeying; in this case, the automatic time out is triggered and the cache is cleared
super::testutils::time_travel_forward(&mut b, REKEY_AFTER_TIME_RESPONDER);
// As long as we do not call poll, everything is fine
check_retransmission(&mut b, &ic1, &ic1_broken, &rc1)?;
// But after we do, the response is gone and can not be recreated
// since the biscuit is stale
poll(&mut b)?;
check_faulty_proc_init_conf(&mut b, &ic1); // ic1 is now effectively broken
assert!(b.peers[0].known_init_conf_response.is_none()); // The cache is gone
Ok(())
}

View File

@@ -0,0 +1,48 @@
//! Helpers used in tests
use std::ops::DerefMut;
use rosenpass_cipher_traits::primitives::Kem;
use rosenpass_ciphers::StaticKem;
use super::{
basic_types::{SPk, SSk},
CryptoServer, PeerPtr, ProtocolVersion,
};
/// Helper for tests and examples
pub struct ServerForTesting {
pub peer: PeerPtr,
pub peer_keys: (SSk, SPk),
pub srv: CryptoServer,
}
/// TODO: Document that the protocol version is only used for creating the peer for testing
impl ServerForTesting {
pub fn new(protocol_version: ProtocolVersion) -> anyhow::Result<Self> {
let (mut sskm, mut spkm) = (SSk::zero(), SPk::zero());
StaticKem.keygen(sskm.secret_mut(), spkm.deref_mut())?;
let mut srv = CryptoServer::new(sskm, spkm);
let (mut sskt, mut spkt) = (SSk::zero(), SPk::zero());
StaticKem.keygen(sskt.secret_mut(), spkt.deref_mut())?;
let peer = srv.add_peer(None, spkt.clone(), protocol_version)?;
let peer_keys = (sskt, spkt);
Ok(ServerForTesting {
peer,
peer_keys,
srv,
})
}
pub fn tuple(self) -> (PeerPtr, (SSk, SPk), CryptoServer) {
(self.peer, self.peer_keys, self.srv)
}
}
/// Time travel forward in time
pub fn time_travel_forward(srv: &mut CryptoServer, secs: f64) {
let dur = std::time::Duration::from_secs_f64(secs);
srv.timebase.0 = srv.timebase.0.checked_sub(dur).unwrap();
}

View File

@@ -0,0 +1,46 @@
//! Time-keeping related utilities for the Rosenpass protocol
use super::constants::EVENT_GRACE;
/// A type for time, e.g. for backoff before re-tries
pub type Timing = f64;
/// Magic time stamp to indicate some object is ancient; "Before Common Era"
///
/// This is for instance used as a magic time stamp indicating age when some
/// cryptographic object certainly needs to be refreshed.
///
/// Using this instead of Timing::MIN or Timing::INFINITY to avoid floating
/// point math weirdness.
pub const BCE: Timing = -3600.0 * 24.0 * 356.0 * 10_000.0;
/// Magic time stamp to indicate that some process is not time-limited
///
/// Actually it's eight hours; This is intentional to avoid weirdness
/// regarding unexpectedly large numbers in system APIs as this is < i16::MAX
pub const UNENDING: Timing = 3600.0 * 8.0;
/// An even `ev` has happened relative to a point in time `now`
/// if the `ev` does not lie in the future relative to now.
///
/// An event lies in the future relative to `now` if
/// does not lie in the past or present.
///
/// An event `ev` lies in the past if `ev < now`. It lies in the
/// present if the absolute difference between `ev` and `now` is
/// smaller than [EVENT_GRACE].
///
/// Think of this as `ev <= now` for with [EVENT_GRACE] applied.
///
/// # Examples
///
/// ```
/// use rosenpass::protocol::{timing::has_happened, constants::EVENT_GRACE};
/// assert!(has_happened(EVENT_GRACE * -1.0, 0.0));
/// assert!(has_happened(0.0, 0.0));
/// assert!(has_happened(EVENT_GRACE * 0.999, 0.0));
/// assert!(!has_happened(EVENT_GRACE * 1.001, 0.0));
/// ```
pub fn has_happened(ev: Timing, now: Timing) -> bool {
(ev - now) < EVENT_GRACE
}

View File

@@ -0,0 +1,21 @@
//! Helpers for working with the zerocopy crate
use std::mem::size_of;
use zerocopy::{FromBytes, Ref};
use crate::RosenpassError;
/// Used to parse a network message using [zerocopy]
pub fn truncating_cast_into<T: FromBytes>(
buf: &mut [u8],
) -> Result<Ref<&mut [u8], T>, RosenpassError> {
Ref::new(&mut buf[..size_of::<T>()]).ok_or(RosenpassError::BufferSizeMismatch)
}
/// Used to parse a network message using [zerocopy], mutably
pub fn truncating_cast_into_nomut<T: FromBytes>(
buf: &[u8],
) -> Result<Ref<&[u8], T>, RosenpassError> {
Ref::new(&buf[..size_of::<T>()]).ok_or(RosenpassError::BufferSizeMismatch)
}

View File

@@ -15,7 +15,7 @@ use rosenpass::api::{
supply_keypair_response_status,
};
use rosenpass::config::ProtocolVersion;
use rosenpass::protocol::SymKey;
use rosenpass::protocol::basic_types::SymKey;
use rosenpass_util::{
b64::B64Display,
file::LoadValueB64,

View File

@@ -17,7 +17,7 @@ use tempfile::TempDir;
use zerocopy::AsBytes;
use rosenpass::config::ProtocolVersion;
use rosenpass::protocol::SymKey;
use rosenpass::protocol::basic_types::SymKey;
struct KillChild(std::process::Child);

View File

@@ -10,7 +10,7 @@ use std::{
use rosenpass::config::ProtocolVersion;
use rosenpass::{
app_server::{AppServer, AppServerTest, MAX_B64_KEY_SIZE},
protocol::{SPk, SSk, SymKey},
protocol::basic_types::{SPk, SSk, SymKey},
};
use rosenpass_cipher_traits::primitives::Kem;
use rosenpass_ciphers::StaticKem;

View File

@@ -10,8 +10,10 @@ use rosenpass_ciphers::StaticKem;
use rosenpass_util::result::OkExt;
use rosenpass::protocol::{
testutils::time_travel_forward, CryptoServer, HostIdentification, MsgBuf, PeerPtr, PollResult,
ProtocolVersion, SPk, SSk, SymKey, Timing, UNENDING,
basic_types::{MsgBuf, SPk, SSk, SymKey},
testutils::time_travel_forward,
timing::{Timing, UNENDING},
CryptoServer, HostIdentification, PeerPtr, PollResult, ProtocolVersion,
};
// TODO: Most of the utility functions in here should probably be moved to

View File

@@ -206,7 +206,7 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> {
use rosenpass::{
app_server::{AppServer, BrokerPeer},
config::Verbosity,
protocol::{SPk, SSk, SymKey},
protocol::basic_types::{SPk, SSk, SymKey},
};
use rosenpass_secret_memory::Secret;
use rosenpass_util::file::{LoadValue as _, LoadValueB64};

View File

@@ -9,7 +9,7 @@ use anyhow::{anyhow, Result};
use rosenpass_util::file::{LoadValueB64, StoreValue, StoreValueB64};
use zeroize::Zeroize;
use rosenpass::protocol::{SPk, SSk};
use rosenpass::protocol::basic_types::{SPk, SSk};
use rosenpass_cipher_traits::primitives::Kem;
use rosenpass_ciphers::StaticKem;
use rosenpass_secret_memory::{file::StoreSecret as _, Public, Secret};
@@ -118,7 +118,7 @@ pub fn pubkey(private_keys_dir: &Path, public_keys_dir: &Path) -> Result<()> {
mod tests {
use std::fs;
use rosenpass::protocol::{SPk, SSk};
use rosenpass::protocol::basic_types::{SPk, SSk};
use rosenpass_secret_memory::secret_policy_try_use_memfd_secrets;
use rosenpass_secret_memory::Secret;
use rosenpass_util::file::LoadValue;