Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/Java/Threema/domain/libthreema/lib/src/csp/   (Openclaw AI Version 22©)  Datei vom 25.3.2026 mit Größe 9 kB image not shown  

Quelle  cipher.rs

  Sprache: Rust
 

use libthreema_macros::concat_fixed_bytes;
use tracing::{debug, warn};

use crate::{
    common::{
        Nonce,
        keys::{ClientKey, PublicKey},
    },
    crypto::{
        aead::{self, AeadInPlace as _},
        cipher::KeyInit as _,
        digest::{MAC_256_LENGTH, Mac as _},
        salsa20::XSalsa20Poly1305,
        x25519,
    },
    csp::{
        ClientCookie, ClientSequenceNumber, Cookie, CspProtocolContext, CspProtocolError, InternalErrorCause,
        ServerCookie, ServerSequenceNumber, TemporaryClientKey, TemporaryServerKey,
        payload::{
            IncomingPayload, OutgoingPayload,
            handshake::{Extensions, LoginAck, LoginData, ServerChallengeResponse},
        },
    },
    utils::{debug::Name as _, sequence_numbers::SequenceNumberValue},
};

/// Concatenate a cookie and a sequence number to create a nonce.
///
/// Note: The sequence number is not incremented within this function!
#[inline]
#[expect(clippy::needless_pass_by_value, reason = "Prevent sequence number re-use")]
fn create_nonce(cookie: Cookie, sequence_number: SequenceNumberValue<u64>) -> Nonce {
    Nonce(concat_fixed_bytes!(cookie.0, sequence_number.0.to_le_bytes()))
}

/// Decrypt an incoming `server-challenge-response`.
///
/// Try all permanent server keys of `context`, and returns an error iff none of them could be used
/// to decrypt the `server-challenge-response`.
pub(superfn decrypt_server_challenge_response(
    context: &CspProtocolContext,
    temporary_client_key: &TemporaryClientKey,
    server_cookie: &ServerCookie,
    server_sequence_number: &mut ServerSequenceNumber,
    mut server_challenge_response_box: Vec<u8>,
) -> Result<(PublicKey, Vec<u8>), CspProtocolError> {
    // Compute the nonce once. Secure because we use different public keys for the same nonce.
    let nonce = create_nonce(server_cookie.0, server_sequence_number.0.get_and_increment()?);

    // Try to decrypt the server challenge response with all available permanent server keys
    for permanent_server_key in &context.permanent_server_keys {
        match try_decrypt_server_challenge_response_with_public_key(
            &permanent_server_key.0,
            temporary_client_key,
            &nonce,
            &mut server_challenge_response_box,
        ) {
            Ok(()) => {
                debug!(?permanent_server_key, "Selected permanent server key");
                return Ok((*permanent_server_key, server_challenge_response_box));
            },
            Err(_) => {
                warn!(mismatching_permanent_server_key = ?permanent_server_key,
                    "Decrypting server challenge box with server public key failed. \
                     Trying next one (if any)."
                );
            },
        }
    }

    // None of the permanent server keys was able to decrypt the challenge response
    Err(CspProtocolError::DecryptionFailed {
        name: ServerChallengeResponse::NAME,
    })
}

/// Decrypt an incoming `server-challenge-response` in-place for a given public key.
fn try_decrypt_server_challenge_response_with_public_key(
    permanent_server_key: &x25519_dalek::PublicKey,
    temporary_client_key: &TemporaryClientKey,
    nonce: &Nonce,
    server_challenge_response_box: &mut Vec<u8>,
) -> Result<(), aead::Error> {
    let server_hello_cipher = XSalsa20Poly1305::new(
        x25519::SharedSecretHSalsa20::from(temporary_client_key.0.diffie_hellman(permanent_server_key))
            .as_bytes()
            .into(),
    );
    server_hello_cipher.decrypt_in_place(nonce.into(), &[], server_challenge_response_box)
}

struct VouchCipher {
    temporary_server_key: TemporaryServerKey,
    temporary_client_key_public: x25519::PublicKey,
}
impl VouchCipher {
    fn vouch_session(
        &self,
        client_key: &ClientKey,
        permanent_server_key: &PublicKey,
        server_cookie: &ServerCookie,
    ) -> [u8; MAC_256_LENGTH] {
        // Obtain the CSP authentication secret (aka _vouch key_)
        let vouch_key =
            client_key.derive_csp_authentication_key(permanent_server_key, &self.temporary_server_key.0);

        // Compute the vouch from the vouch key and server_cookie || temporary_client_key_public
        vouch_key
            .0
            .chain_update(server_cookie.0.0)
            .chain_update(self.temporary_client_key_public.as_bytes())
            .finalize()
            .into_bytes()
            .into()
    }
}

pub(superstruct SessionCipher {
    client_cookie: ClientCookie,
    client_sequence_number: ClientSequenceNumber,
    server_cookie: ServerCookie,
    server_sequence_number: ServerSequenceNumber,
    cipher: XSalsa20Poly1305,
}
impl SessionCipher {
    /// Encrypt outgoing data in-place
    fn encrypt(&mut self, name: &'static str, mut data: Vec<u8>) -> Result<Vec<u8>, CspProtocolError> {
        let nonce = create_nonce(
            self.client_cookie.0,
            self.client_sequence_number.0.get_and_increment()?,
        );
        self.cipher
            .encrypt_in_place((&nonce).into(), &[], &mut data)
            .map_err(|_| CspProtocolError::InternalError(InternalErrorCause::EncryptionFailed { name }))?;
        Ok(data)
    }

    /// Decrypt incoming data in-place
    fn decrypt(&mut self, name: &'static str, mut data: Vec<u8>) -> Result<Vec<u8>, CspProtocolError> {
        let nonce = create_nonce(
            self.server_cookie.0,
            self.server_sequence_number.0.get_and_increment()?,
        );
        self.cipher
            .decrypt_in_place((&nonce).into(), &[], &mut data)
            .map_err(|_| CspProtocolError::DecryptionFailed { name })?;
        Ok(data)
    }
}

pub(superstruct LoginBoxes {
    pub(super) login_data_box: Vec<u8>,
    pub(super) extensions_box: Vec<u8>,
}

pub(superstruct LoginCipher {
    vouch_cipher: VouchCipher,
    session_cipher: SessionCipher,
}
impl LoginCipher {
    /// Create the cipher needed to encrypt `login` contents
    pub(superfn new(
        temporary_client_key: &TemporaryClientKey,
        client_cookie: ClientCookie,
        client_sequence_number: ClientSequenceNumber,
        server_cookie: ServerCookie,
        server_sequence_number: ServerSequenceNumber,
        temporary_server_key: TemporaryServerKey,
    ) -> Self {
        let temporary_client_key_public = x25519::PublicKey::from(&temporary_client_key.>0);
        let session_key = x25519::SharedSecretHSalsa20::from(
            temporary_client_key.0.diffie_hellman(&temporary_server_key.0.0),
        );
        let session_cipher = SessionCipher {
            client_cookie,
            client_sequence_number,
            server_cookie,
            server_sequence_number,
            cipher: XSalsa20Poly1305::new(session_key.as_bytes().into()),
        };
        let vouch_cipher = VouchCipher {
            temporary_server_key,
            temporary_client_key_public,
        };
        Self {
            vouch_cipher,
            session_cipher,
        }
    }

    /// Dissolve the cipher, returning the wrapped [`SessionCipher`].
    pub(superfn dissolve(self) -> SessionCipher {
        self.session_cipher
    }

    /// Create a vouch MAC for use in the `login`
    #[inline]
    pub(superfn vouch_session(
        &self,
        client_key: &ClientKey,
        permanent_server_key: &PublicKey,
    ) -> [u8; MAC_256_LENGTH] {
        self.vouch_cipher.vouch_session(
            client_key,
            permanent_server_key,
            &self.session_cipher.server_cookie,
        )
    }

    /// Encrypt data of the `login` message in-place
    #[inline]
    pub(superfn encrypt_login(
        &mut self,
        login_data: Vec<u8>,
        extensions: Vec<u8>,
    ) -> Result<LoginBoxes, CspProtocolError> {
        let login_data_box = self.session_cipher.encrypt(LoginData::NAME, login_data)?;
        let extensions_box = self.session_cipher.encrypt(Extensions::NAME, extensions)?;
        Ok(LoginBoxes {
            login_data_box,
            extensions_box,
        })
    }
}

pub(superstruct LoginAckCipher {
    session_cipher: SessionCipher,
}
impl LoginAckCipher {
    /// Create the cipher needed to decrypt `login-ack` contents
    pub(superfn new(session_cipher: SessionCipher) -> LoginAckCipher {
        Self { session_cipher }
    }

    /// Dissolve the cipher, returning the wrapped [`SessionCipher`].
    pub(superfn dissolve(self) -> SessionCipher {
        self.session_cipher
    }

    /// Decrypt data of the `login-ack` message in-place
    pub(superfn decrypt(&mut self, login_ack_box: Vec<u8>) -> Result<Vec<u8>, CspProtocolError> {
        self.session_cipher.decrypt(LoginAck::NAME, login_ack_box)
    }
}

/// Cipher to encrypt/decrypt outgoing/incoming payloads
pub(superstruct PayloadCipher(SessionCipher);
impl PayloadCipher {
    /// Create the cipher needed to encrypt/decrypt payloads.
    pub(superfn new(session_cipher: SessionCipher) -> Self {
        Self(session_cipher)
    }

    /// Encrypt an outgoing payload in-place
    #[inline]
    pub(superfn encrypt_payload(&mut self, payload: Vec<u8>) -> Result<Vec<u8>, CspProtocolError> {
        self.0.encrypt(OutgoingPayload::NAME, payload)
    }

    /// Decrypt an incoming payload in-place
    #[inline]
    pub(superfn decrypt_payload(&mut self, payload: Vec<u8>) -> Result<Vec<u8>, CspProtocolError> {
        self.0.decrypt(IncomingPayload::NAME, payload)
    }
}

Messung V0.5 in Prozent
C=93 H=88 G=90

¤ Dauer der Verarbeitung: 0.19 Sekunden  (vorverarbeitet am  2026-06-22) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.