Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  mod.rs   Sprache: unbekannt

 
Spracherkennung für: .rs vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

//! Implementation of the end-to-end encryption layer of the _Chat Server Protocol_.
use core::{cell::RefCell, fmt};
use std::{collections::HashMap, rc::Rc};

use educe::Educe;
use libthreema_macros::Name;
use tracing::debug;

use crate::{
    common::{
        ClientInfo, D2xDeviceId, ThreemaId,
        config::{Config, Flavor},
        keys::{ClientKey, DeviceGroupKey},
    },
    csp::payload::MessageWithMetadataBox,
    csp_e2e::{contacts::lookup::ContactLookupCache, message::task::incoming::IncomingMessageTask},
    https::endpoint::HttpsEndpointError,
    model::provider::{
        ContactProvider, ConversationProvider, NonceStorage, ProviderError, SettingsProvider,
        ShortcutProvider,
    },
    utils::{
        debug::Name as _,
        sequence_numbers::{SequenceNumberOverflow, SequenceNumberU32, SequenceNumberValue},
        serde::string,
    },
};

pub mod contacts;
pub mod identity;
pub mod message;
pub mod reflect;
pub mod transaction;

/// Cause of an internal error.
#[derive(Clone, Debug, thiserror::Error)]
pub enum InternalErrorCause {
    /// Exhausted the available sequence numbers (e.g. for `ReflectId`s). Should never happen.
    #[error("Sequence number overflow happened")]
    SequenceNumberOverflow(#[from] SequenceNumberOverflow),

    /// Unable to encrypt a message or a struct.
    #[error("Encrypting '{name}' failed")]
    EncryptionFailed {
        /// Name of the message or struct
        name: &'static str,
    },

    /// Another kind of error occurred
    #[error("{0}")]
    Other(String),
}
impl<T: Into<String>> From<T> for InternalErrorCause {
    fn from(message: T) -> Self {
        Self::Other(message.into())
    }
}

/// An error occurred while running the end-to-end encryption layer of the _Chat Server Protocol_.
///
/// TODO(LIB-16): Clarify recoverability and what to do when encountering an error. so far, the idea is that
/// an error means unrecoverable and that the CSP or CSP/D2M connection needs to be restarted. a new instance
/// of the protocol can then be created with linear/exponential backoff.
#[derive(Clone, Debug, thiserror::Error)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Error), uniffi(flat_error))]
#[cfg_attr(
    feature = "wasm",
    derive(tsify::Tsify, serde::Serialize),
    serde(
        tag = "type",
        content = "details",
        rename_all = "kebab-case",
        rename_all_fields = "camelCase"
    ),
    tsify(into_wasm_abi)
)]
pub enum CspE2eProtocolError {
    /// A foreign function considered infallible returned an error.
    #[cfg(any(test, feature = "uniffi", feature = "cli"))]
    #[error("Infallible function failed in foreign code: {0}")]
    Foreign(String),

    /// Invalid state for the requested operation.
    #[error("Invalid state: {0}")]
    InvalidState(&'static str),

    /// An internal error happened.
    #[cfg_attr(feature = "wasm", serde(serialize_with = "string::to_string::serialize"))]
    #[error("Internal error: {0}")]
    InternalError(#[from] InternalErrorCause),

    /// A desync occurred.
    ///
    /// This may be the result of...
    ///
    /// - a protocol violation from the client using this state machine (e.g. adding a contact to storage
    ///   without using the appropriate task), or
    /// - misbehaviour of another device in the device group (e.g. updating a contact without an appropriate
    ///   transaction), or
    /// - the mediator server misbehaving (e.g. forwarding reflected messages from another device while a
    ///   transaction is ongoing), or
    /// - an implementation error of this protocol state machine.
    ///
    /// If multi-device is active, device group integrity may have been compromised and relinking against
    /// another device is advisable.
    #[error("Desync error: {0}")]
    DesyncError(String),

    /// A network error occurred while communicating with a server.
    #[error("Network error: {0}")]
    NetworkError(String),

    /// A server misbehaved in an operation considered infallible.
    #[error("Server error: {0}")]
    ServerError(String),

    /// Invalid credentials (should only be relevant for Work and OnPrem) reported by a server caused an
    /// operation to fail.
    ///
    /// When processing this variant, notify the user that the Work credentials are invalid and request new
    /// ones. A new CSP connection may be retried after the credentials have been validated.
    #[error("Invalid credentials")]
    InvalidCredentials,

    /// A rate limit of a server has been exceeded.
    ///
    /// When processing this variant, ensure that a new CSP connection attempt is delayed by at least 10s.
    #[error("Rate limit exceeded")]
    RateLimitExceeded,
}
impl From<SequenceNumberOverflow> for CspE2eProtocolError {
    fn from(error: SequenceNumberOverflow) -> Self {
        Self::InternalError(InternalErrorCause::from(error))
    }
}
impl From<HttpsEndpointError> for CspE2eProtocolError {
    fn from(error: HttpsEndpointError) -> Self {
        match error {
            HttpsEndpointError::NetworkError(_) | HttpsEndpointError::ChallengeExpired => {
                CspE2eProtocolError::NetworkError(error.to_string())
            },
            HttpsEndpointError::RateLimitExceeded => CspE2eProtocolError::RateLimitExceeded,
            HttpsEndpointError::InvalidCredentials => CspE2eProtocolError::InvalidCredentials,
            HttpsEndpointError::Forbidden
            | HttpsEndpointError::NotFound
            | HttpsEndpointError::InvalidChallengeResponse
            | HttpsEndpointError::UnexpectedStatus(_)
            | HttpsEndpointError::CustomPossiblyLocalizedError(_)
            | HttpsEndpointError::DecodingFailed(_) => CspE2eProtocolError::ServerError(error.to_string()),
        }
    }
}
impl From<ProviderError> for CspE2eProtocolError {
    fn from(error: ProviderError) -> Self {
        match error {
            ProviderError::InvalidParameter(message) => Self::InternalError(message.into()),
            ProviderError::InvalidState(message) => Self::DesyncError(message),
            #[cfg(any(test, feature = "uniffi", feature = "cli"))]
            ProviderError::Foreign(message) => Self::Foreign(message),
        }
    }
}

/// A D2M reflect ID.
#[derive(Clone, Copy, Eq, Hash, PartialEq, Name)]
pub struct ReflectId(pub u32);
impl fmt::Debug for ReflectId {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(formatter, "{}({:016x})", Self::NAME, self.0.to_be())
    }
}
impl From<SequenceNumberValue<u32>> for ReflectId {
    fn from(value: SequenceNumberValue<u32>) -> Self {
        ReflectId(value.0)
    }
}

/// Initializer for a [`CspE2eContext`].
pub struct CspE2eContextInit {
    /// The user's identity.
    pub user_identity: ThreemaId,

    /// Client key.
    pub client_key: ClientKey,

    /// Application flavour.
    pub flavor: Flavor,

    /// CSP nonce storage.
    pub nonce_storage: Box<RefCell<dyn NonceStorage>>,
}

/// Initializer for a [`D2xContext`].
pub struct D2xContextInit {
    /// The device's (D2X) ID.
    pub device_id: D2xDeviceId,

    /// The device group key.
    pub device_group_key: DeviceGroupKey,

    /// D2D nonce storage.
    pub nonce_storage: Box<RefCell<dyn NonceStorage>>,
}

/// Initializer for a [`CspE2eProtocolContext`].
pub struct CspE2eProtocolContextInit {
    /// Client info.
    pub client_info: ClientInfo,

    /// Configuration used by the protocol.
    pub config: Rc<Config>,

    /// See [`CspE2eContext`].
    pub csp_e2e: CspE2eContextInit,

    /// Optional D2X context, only available if multi-device is enabled, see [`D2xContext`].
    pub d2x: Option<D2xContextInit>,

    /// See [`ShortcutProvider`].
    pub shortcut: Box<dyn ShortcutProvider>,

    /// See [`SettingsProvider`].
    pub settings: Box<RefCell<dyn SettingsProvider>>,

    /// See [`ContactProvider`].
    pub contacts: Box<RefCell<dyn ContactProvider>>,

    /// See [`ConversationProvider`].
    pub conversations: Box<RefCell<dyn ConversationProvider>>,
}

/// The current D2M role.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum D2mRole {
    /// Follower role (i.e. not yet _leader_).
    Follower,

    /// Leader role.
    Leader,
}

/// Sequence number used for outgoing reflections.
struct ReflectSequenceNumber(SequenceNumberU32);

/// CSP-specific context information.
pub struct CspE2eContext {
    /// The user's identity.
    user_identity: ThreemaId,

    /// Client key.
    client_key: ClientKey,

    /// Application flavour.
    flavor: Flavor,

    /// CSP nonce storage.
    nonce_storage: Rc<RefCell<dyn NonceStorage>>,
}
impl From<CspE2eContextInit> for CspE2eContext {
    fn from(init: CspE2eContextInit) -> Self {
        Self {
            user_identity: init.user_identity,
            client_key: init.client_key,
            flavor: init.flavor,
            nonce_storage: init.nonce_storage.into(),
        }
    }
}

/// D2M/D2D-specific context information
pub struct D2xContext {
    /// The device's (D2X) ID.
    device_id: D2xDeviceId,

    /// The device group key.
    device_group_key: DeviceGroupKey,

    /// D2D nonce storage.
    nonce_storage: Box<RefCell<dyn NonceStorage>>,

    /// Sequence number used for outgoing reflections.
    reflect_id: ReflectSequenceNumber,

    /// The current D2M role.
    role: D2mRole,
}
impl From<D2xContextInit> for D2xContext {
    fn from(init: D2xContextInit) -> Self {
        Self {
            device_id: init.device_id,
            device_group_key: init.device_group_key,
            nonce_storage: init.nonce_storage,
            reflect_id: ReflectSequenceNumber(SequenceNumberU32::new(0)),
            role: D2mRole::Follower,
        }
    }
}

/// CSP E2EE protocol context
pub struct CspE2eProtocolContext {
    /// Client info.
    client_info: ClientInfo,

    /// Configuration used by the protocol.
    config: Rc<Config>,

    /// See [`CspE2eContext`].
    csp_e2e: CspE2eContext,

    /// Optional D2X context, only available if multi-device is enabled, see [`D2xContext`].
    d2x: Option<D2xContext>,

    /// See [`ShortcutProvider`].
    shortcut: Box<dyn ShortcutProvider>,

    /// See [`SettingsProvider`].
    settings: Rc<RefCell<dyn SettingsProvider>>,

    /// See [`ContactProvider`].
    contacts: Rc<RefCell<dyn ContactProvider>>,

    /// See [`ConversationProvider`].
    conversations: Rc<RefCell<dyn ConversationProvider>>,

    /// See [`ContactLookupCache`].
    contact_lookup_cache: ContactLookupCache,
}
impl From<CspE2eProtocolContextInit> for CspE2eProtocolContext {
    fn from(init: CspE2eProtocolContextInit) -> Self {
        Self {
            client_info: init.client_info,
            config: init.config,
            csp_e2e: CspE2eContext::from(init.csp_e2e),
            d2x: init.d2x.map(From::from),
            shortcut: init.shortcut,
            settings: init.settings.into(),
            contacts: init.contacts.into(),
            conversations: init.conversations.into(),
            contact_lookup_cache: ContactLookupCache::new(HashMap::new()),
        }
    }
}

/// The Chat Server E2EE Protocol state machine.
///
/// TODO(LIB-16):
/// - How to use.
/// - Protocol must be recreated whenever a reconnection happens.
/// - Add linear/exponential backoff when retrying a connection.
/// - ...
#[derive(Educe)]
#[educe(Debug)]
pub struct CspE2eProtocol {
    #[educe(Debug(ignore))]
    context: CspE2eProtocolContext,
}
impl CspE2eProtocol {
    /// Initiate a new CSP E2EE protocol.
    #[must_use]
    #[tracing::instrument(skip_all)]
    pub fn new(context: CspE2eProtocolContextInit) -> Self {
        debug!("Creating CSP E2EE protocol");

        // Create initial state
        Self {
            context: CspE2eProtocolContext::from(context),
        }
    }

    /// Borrow current [`CspE2eProtocolContext`] state.
    #[inline]
    pub fn context(&mut self) -> &mut CspE2eProtocolContext {
        &mut self.context
    }

    /// TODO(LIB-16): How to use
    ///
    /// # Errors
    ///
    /// TODO(LIB-16): Describe errors
    #[tracing::instrument(skip_all, fields(?d2m_state))]
    pub fn update_d2m_state(&mut self, d2m_state: D2mRole) -> Result<(), CspE2eProtocolError> {
        let Some(d2m_context) = &mut self.context.d2x else {
            return Err(CspE2eProtocolError::InvalidState("MD context not initialized"));
        };
        if matches!(d2m_context.role, D2mRole::Leader) && matches!(d2m_state, D2mRole::Follower) {
            return Err(CspE2eProtocolError::InvalidState(
                "Downgrading D2M state is not allowed",
            ));
        }
        debug!("Updating D2M state");
        d2m_context.role = d2m_state;
        Ok(())
    }

    /// TODO(LIB-16): How to use
    ///
    /// # Errors
    ///
    /// TODO(LIB-16): Describe errors
    #[expect(clippy::unused_self, reason = "TODO(LIB-16)")]
    #[must_use]
    #[tracing::instrument(skip_all, fields(?payload))]
    pub fn handle_incoming_message(&self, payload: MessageWithMetadataBox) -> IncomingMessageTask {
        // TODO(LIB-16): Check for leading state!
        debug!("Creating incoming message task");
        IncomingMessageTask::new(payload)
    }
}

[Dauer der Verarbeitung: 0.26 Sekunden, vorverarbeitet 2026-04-27]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge