#![allow(missing_docs)]
use crate::misc::CryptError;
use crate::ratchets::Ratchet;
use crate::sync_toggle::{CurrentToggleState, SyncToggle};
use crate::toolset::{Toolset, ToolsetUpdateStatus};
use citadel_io::RwLock;
use citadel_pqcrypto::constructor_opts::ConstructorOpts;
use citadel_types::crypto::CryptoParameters;
use citadel_types::prelude::{ObjectId, SecurityLevel};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
use std::sync::Arc;
use uuid::Uuid;
#[derive(Serialize, Deserialize, Clone)]
pub struct PeerSessionCrypto<R: Ratchet> {
#[serde(bound = "")]
toolset: Arc<RwLock<Toolset<R>>>,
pub update_in_progress: SyncToggle,
local_is_initiator: bool,
cid: u64,
incrementing_group_id_messaging: Arc<AtomicU64>,
pub incrementing_group_id_file_transfer: Arc<AtomicU64>,
pub latest_usable_version: Arc<AtomicU32>,
}
const ORDERING: Ordering = Ordering::Relaxed;
impl<R: Ratchet> PeerSessionCrypto<R> {
pub fn new(toolset: Toolset<R>, local_is_initiator: bool) -> Self {
Self {
cid: toolset.cid,
toolset: Arc::new(RwLock::new(toolset)),
update_in_progress: SyncToggle::new(),
local_is_initiator,
incrementing_group_id_messaging: Arc::new(AtomicU64::new(0)),
incrementing_group_id_file_transfer: Arc::new(AtomicU64::new(0)),
latest_usable_version: Arc::new(AtomicU32::new(0)),
}
}
pub fn get_ratchet(&self, version: Option<u32>) -> Option<R> {
self.toolset
.read()
.get_ratchet(version.unwrap_or_else(|| self.latest_usable_version.load(ORDERING)))
.cloned()
}
#[allow(clippy::type_complexity)]
pub fn commit_next_ratchet_version(
&self,
mut newest_version: R::Constructor,
local_cid: u64,
generate_next: bool,
) -> Result<
(
Option<<R::Constructor as EndpointRatchetConstructor<R>>::BobToAliceWireTransfer>,
ToolsetUpdateStatus,
),
CryptError,
> {
let mut toolset = self.toolset.write();
let cur_vers = toolset.get_most_recent_ratchet_version();
let next_vers = cur_vers.wrapping_add(1);
newest_version.update_version(next_vers).ok_or_else(|| {
CryptError::RekeyUpdateError("Unable to progress past update_version".to_string())
})?;
if !generate_next {
let latest_ratchet = newest_version
.finish_with_custom_cid(local_cid)
.ok_or_else(|| {
CryptError::RekeyUpdateError(
"Unable to progress past finish_with_custom_cid for bob-to-alice trigger"
.to_string(),
)
})?;
let status = toolset.update_from(latest_ratchet).ok_or_else(|| {
CryptError::RekeyUpdateError(
"Unable to progress past update_from for bob-to-alice trigger".to_string(),
)
})?;
return Ok((None, status));
}
let transfer = newest_version.stage0_bob().ok_or_else(|| {
CryptError::RekeyUpdateError("Unable to progress past stage0_bob".to_string())
})?;
let next_ratchet = newest_version
.finish_with_custom_cid(local_cid)
.ok_or_else(|| {
CryptError::RekeyUpdateError(
"Unable to progress past finish_with_custom_cid".to_string(),
)
})?;
let status = toolset.update_from(next_ratchet).ok_or_else(|| {
CryptError::RekeyUpdateError("Unable to progress past update_from".to_string())
})?;
log::trace!(target: "citadel", "[E2E] Client {local_cid} successfully updated Ratchet from v{cur_vers} to v{next_vers}");
Ok((Some(transfer), status))
}
pub fn deregister_oldest_ratchet(&self, version: u32) -> Result<(), CryptError<String>> {
self.toolset.write().deregister_oldest_ratchet(version)
}
pub fn update_sync_safe(
&self,
constructor: R::Constructor,
triggered_by_bob_to_alice_transfer: bool,
) -> Result<KemTransferStatus<R>, CryptError> {
let local_cid = self.cid;
let update_in_progress =
self.update_in_progress.toggle_on_if_untoggled() == CurrentToggleState::AlreadyToggled;
log::trace!(target: "citadel", "[E2E] Calling UPDATE (triggered by bob_to_alice tx: {triggered_by_bob_to_alice_transfer}. Update in progress: {update_in_progress})");
if update_in_progress && !triggered_by_bob_to_alice_transfer {
if !self.local_is_initiator {
return Ok(KemTransferStatus::Contended);
}
}
let result = self.commit_next_ratchet_version(
constructor,
local_cid,
!triggered_by_bob_to_alice_transfer,
);
if let Err(err) = &result {
log::error!(target: "citadel", "[E2E] Error during update: {:?}", err);
self.update_in_progress.toggle_off();
}
let (transfer, status) = result?;
if let Some(transfer) = transfer {
Ok(KemTransferStatus::Some(transfer, status))
} else {
if !triggered_by_bob_to_alice_transfer {
return Err(CryptError::RekeyUpdateError(
"This should only be reached if triggered by a bob-to-alice transfer event, yet, conflicting program state".to_string(),
));
}
Ok(KemTransferStatus::StatusNoTransfer(status))
}
}
pub fn maybe_unlock(&self) -> Option<R> {
if self.update_in_progress.reset_and_get_previous() != CurrentToggleState::AlreadyToggled {
log::error!(target: "citadel", "Client {} expected update_in_progress to be true", self.cid);
return None;
}
log::trace!(target: "citadel", "Unlocking for {}", self.cid);
self.get_ratchet(None)
}
pub fn post_alice_stage1_or_post_stage1_bob(&self) {
log::trace!(target: "citadel", "post_alice_stage1_or_post_stage1_bob for {}: Upgrading from {} to {}", self.cid, self.latest_usable_version(), self.latest_usable_version().wrapping_add(1));
let _ = self.latest_usable_version.fetch_add(1, ORDERING);
}
pub fn get_and_increment_group_id(&self) -> u64 {
self.incrementing_group_id_messaging.fetch_add(1, ORDERING)
}
pub fn get_and_increment_group_file_transfer(&self) -> u64 {
self.incrementing_group_id_file_transfer
.fetch_add(1, ORDERING)
}
pub fn get_next_object_id(&self) -> ObjectId {
Uuid::new_v4().as_u128().into()
}
pub fn get_next_constructor(&self) -> Option<R::Constructor> {
if self.update_in_progress.toggle_on_if_untoggled() == CurrentToggleState::JustToggled {
self.get_ratchet(None)?.next_alice_constructor()
} else {
None
}
}
pub fn refresh_state(&self) {
self.update_in_progress.toggle_off();
self.incrementing_group_id_messaging.store(0, ORDERING);
self.incrementing_group_id_file_transfer.store(0, ORDERING);
}
pub fn get_default_params(&self) -> CryptoParameters {
self.toolset
.read()
.get_static_auxiliary_ratchet()
.get_message_pqc_and_entropy_bank_at_layer(None)
.expect("Expected to get message pqc and entropy bank")
.0
.params
}
pub fn local_is_initiator(&self) -> bool {
self.local_is_initiator
}
pub fn latest_usable_version(&self) -> u32 {
self.latest_usable_version.load(ORDERING)
}
pub fn cid(&self) -> u64 {
self.cid
}
pub fn toolset(&self) -> &Arc<RwLock<Toolset<R>>> {
&self.toolset
}
}
pub trait AssociatedSecurityLevel {
fn security_level(&self) -> SecurityLevel;
}
pub trait AssociatedCryptoParams {
fn crypto_params(&self) -> CryptoParameters;
}
pub trait EndpointRatchetConstructor<R: Ratchet>: Debug + Send + Sync + 'static {
type AliceToBobWireTransfer: Send
+ Sync
+ Serialize
+ DeserializeOwned
+ AssociatedSecurityLevel
+ AssociatedCryptoParams;
type BobToAliceWireTransfer: Send
+ Sync
+ Serialize
+ DeserializeOwned
+ AssociatedSecurityLevel;
fn new_alice(opts: Vec<ConstructorOpts>, cid: u64, new_version: u32) -> Option<Self>
where
Self: Sized;
fn new_bob<T: AsRef<[u8]>>(
cid: u64,
opts: Vec<ConstructorOpts>,
transfer: Self::AliceToBobWireTransfer,
psks: &[T],
) -> Option<Self>
where
Self: Sized;
fn stage0_alice(&self) -> Option<Self::AliceToBobWireTransfer>;
fn stage0_bob(&mut self) -> Option<Self::BobToAliceWireTransfer>;
fn stage1_alice<T: AsRef<[u8]>>(
&mut self,
transfer: Self::BobToAliceWireTransfer,
psks: &[T],
) -> Result<(), CryptError>;
fn update_version(&mut self, version: u32) -> Option<()>;
fn finish_with_custom_cid(self, cid: u64) -> Option<R>;
fn finish(self) -> Option<R>;
}
#[derive(Serialize, Deserialize)]
#[allow(variant_size_differences)]
pub enum KemTransferStatus<R: Ratchet> {
StatusNoTransfer(ToolsetUpdateStatus),
Empty,
Contended,
#[serde(bound = "")]
Some(
<R::Constructor as EndpointRatchetConstructor<R>>::BobToAliceWireTransfer,
ToolsetUpdateStatus,
),
}
impl<R: Ratchet> Debug for KemTransferStatus<R> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
KemTransferStatus::StatusNoTransfer(status) => {
write!(f, "KemTransferStatus::StatusNoTransfer({:?})", status)
}
KemTransferStatus::Empty => write!(f, "KemTransferStatus::Empty"),
KemTransferStatus::Contended => write!(f, "KemTransferStatus::Contended"),
KemTransferStatus::Some(_, status) => {
write!(f, "KemTransferStatus::Some(transfer, {status:?})")
}
}
}
}
impl<R: Ratchet> KemTransferStatus<R> {
pub fn requires_truncation(&self) -> Option<u32> {
match self {
KemTransferStatus::StatusNoTransfer(
ToolsetUpdateStatus::CommittedNeedsSynchronization {
oldest_version: old_version,
..
},
)
| KemTransferStatus::Some(
_,
ToolsetUpdateStatus::CommittedNeedsSynchronization {
oldest_version: old_version,
..
},
) => Some(*old_version),
_ => None,
}
}
pub fn omitted(&self) -> bool {
matches!(self, Self::Contended)
}
pub fn has_some(&self) -> bool {
matches!(self, KemTransferStatus::Some(..))
}
}