Skip to main content

wacore_libsignal/protocol/
session_cipher.rs

1//
2// Copyright 2020-2022 Signal Messenger, LLC.
3// SPDX-License-Identifier: AGPL-3.0-only
4//
5
6use std::cell::RefCell;
7
8use rand::{CryptoRng, Rng};
9
10use crate::crypto::DecryptionError as DecryptionErrorCrypto;
11use crate::crypto::{
12    aes_256_cbc_decrypt_in_place, aes_256_cbc_decrypt_into, aes_256_cbc_encrypt_into,
13};
14
15// Thread-local buffers for AES operations to reduce allocations and memory fragmentation
16thread_local! {
17    static ENCRYPTION_BUFFER: RefCell<EncryptionBuffer> = RefCell::new(EncryptionBuffer::new());
18    static DECRYPTION_BUFFER: RefCell<EncryptionBuffer> = RefCell::new(EncryptionBuffer::new());
19}
20
21// Wrapper for the encryption buffer with intelligent size management
22struct EncryptionBuffer {
23    buffer: Vec<u8>,
24    usage_count: usize,
25}
26
27impl EncryptionBuffer {
28    const INITIAL_CAPACITY: usize = 1024;
29    const MAX_CAPACITY: usize = 16 * 1024; // 16KB max
30    const SHRINK_THRESHOLD: usize = 100; // Shrink every 100 uses if oversized
31
32    fn new() -> Self {
33        Self {
34            buffer: Vec::with_capacity(Self::INITIAL_CAPACITY),
35            usage_count: 0,
36        }
37    }
38
39    fn get_buffer(&mut self) -> &mut Vec<u8> {
40        self.usage_count += 1;
41
42        // Periodically manage buffer size to prevent unbounded growth
43        if self.usage_count.is_multiple_of(Self::SHRINK_THRESHOLD) {
44            // If buffer has grown beyond max capacity, shrink it back
45            if self.buffer.capacity() > Self::MAX_CAPACITY {
46                self.buffer = Vec::with_capacity(Self::INITIAL_CAPACITY);
47            } else if self.buffer.is_empty() && self.buffer.capacity() > Self::INITIAL_CAPACITY * 2
48            {
49                // If buffer is empty but has grown significantly, shrink it
50                self.buffer.shrink_to(Self::INITIAL_CAPACITY);
51            }
52        }
53
54        &mut self.buffer
55    }
56}
57
58/// Current capacity of this thread's encrypt buffer. Test-only: lets the
59/// oversized-buffer-release regression test observe the thread-local.
60#[cfg(test)]
61fn encryption_buffer_capacity() -> usize {
62    ENCRYPTION_BUFFER.with(|b| b.borrow().buffer.capacity())
63}
64use crate::protocol::consts::MAX_FORWARD_JUMPS;
65
66/// Max chain steps a single decrypt may derive before rejecting a
67/// too-far-ahead counter. Peer sessions match WA Web's `signalFutureMessagesMax`
68/// (2000); a session with our own devices gets the wider self ceiling.
69const fn forward_jump_limit(is_self: bool) -> usize {
70    if is_self {
71        crate::protocol::consts::MAX_FORWARD_JUMPS_SELF
72    } else {
73        MAX_FORWARD_JUMPS
74    }
75}
76use crate::protocol::ratchet::keys::MessageKeyGenerator;
77use crate::protocol::ratchet::{ChainKey, RootKey, UsePQRatchet};
78use crate::protocol::state::{DecryptSnapshot, PreKeyId, ReceiverChainState, SessionState};
79use crate::protocol::{
80    CiphertextMessage, CiphertextMessageType, Direction, IdentityChange, IdentityKeyStore, KeyPair,
81    PreKeySignalMessage, PreKeyStore, ProtocolAddress, PublicKey, Result, SessionCheckout,
82    SessionRecord, SessionStore, SignalMessage, SignalProtocolError, SignedPreKeyStore, session,
83};
84
85/// Plaintext plus whether decrypting this message replaced a previously-stored
86/// identity key for the sender. A [`IdentityChange::ReplacedExisting`] is the
87/// local signal that the peer's identity changed (e.g. reinstall), letting the
88/// caller react without waiting for the server's `<identity/>` push.
89#[derive(Debug)]
90pub struct DecryptionResult {
91    pub plaintext: Vec<u8>,
92    pub identity_change: IdentityChange,
93    /// The one-time pre-key a pkmsg consumed, if any. The decrypt does NOT delete
94    /// it: removing the prekey is the caller's responsibility, and only once the
95    /// promoted session is itself durable. A crash with the prekey already gone
96    /// but the session still volatile makes a redelivered pkmsg undecryptable, so
97    /// the caller buffers this id and deletes it alongside the session flush.
98    /// `None` for a SignalMessage decrypt or a pkmsg that reused an existing session.
99    pub consumed_prekey_id: Option<PreKeyId>,
100}
101
102/// A parsed Signal envelope whose wire storage may be consumed by decryption.
103///
104/// Borrowed APIs remain available for callers that need to retain ciphertext.
105/// This owner lets receive pipelines reuse a uniquely-owned ciphertext
106/// allocation for plaintext after authentication, avoiding a full-size peak
107/// copy for large inline messages. Errors before authenticated decryption leave
108/// the envelope available for identity/session migration retries.
109#[derive(Debug)]
110pub struct OwnedCiphertextMessage {
111    inner: Option<CiphertextMessage>,
112}
113
114impl OwnedCiphertextMessage {
115    pub fn new(message: CiphertextMessage) -> Self {
116        Self {
117            inner: Some(message),
118        }
119    }
120
121    pub fn is_available(&self) -> bool {
122        self.inner.is_some()
123    }
124
125    fn message_type(&self) -> Option<CiphertextMessageType> {
126        self.inner.as_ref().map(CiphertextMessage::message_type)
127    }
128
129    fn pre_key_message(&self) -> Option<&PreKeySignalMessage> {
130        match self.inner.as_ref()? {
131            CiphertextMessage::PreKeySignalMessage(message) => Some(message),
132            _ => None,
133        }
134    }
135
136    fn signal_message(&self) -> Option<&SignalMessage> {
137        match self.inner.as_ref()? {
138            CiphertextMessage::SignalMessage(message) => Some(message),
139            CiphertextMessage::PreKeySignalMessage(message) => Some(message.message()),
140            _ => None,
141        }
142    }
143
144    fn take_signal_message(&mut self) -> Option<SignalMessage> {
145        match self.inner.take()? {
146            CiphertextMessage::SignalMessage(message) => Some(message),
147            CiphertextMessage::PreKeySignalMessage(message) => Some(message.into_message()),
148            other => {
149                self.inner = Some(other);
150                None
151            }
152        }
153    }
154}
155
156impl From<CiphertextMessage> for OwnedCiphertextMessage {
157    fn from(message: CiphertextMessage) -> Self {
158        Self::new(message)
159    }
160}
161
162// Keeping both ownership modes in one concrete type avoids duplicating the
163// decrypt state machine while deferring owned-envelope consumption until MAC
164// authentication succeeds.
165enum SignalDecryptInput<'a> {
166    Borrowed(&'a SignalMessage),
167    Owned(&'a mut OwnedCiphertextMessage),
168}
169
170impl SignalDecryptInput<'_> {
171    #[inline(always)]
172    fn signal_message(&self) -> &SignalMessage {
173        match self {
174            Self::Borrowed(message) => message,
175            Self::Owned(message) => message
176                .signal_message()
177                .expect("owned Signal decrypt input has a Signal envelope"),
178        }
179    }
180
181    #[inline(always)]
182    fn is_available(&self) -> bool {
183        match self {
184            Self::Borrowed(_) => true,
185            Self::Owned(message) => message.is_available(),
186        }
187    }
188
189    fn decrypt(
190        &mut self,
191        key: &[u8],
192        iv: &[u8],
193    ) -> std::result::Result<Vec<u8>, DecryptionErrorCrypto> {
194        match self {
195            Self::Borrowed(message) => decrypt_borrowed_signal(message, key, iv),
196            Self::Owned(message) => {
197                let message = message
198                    .take_signal_message()
199                    .expect("authenticated owned input remains available");
200                match message.try_into_ciphertext_vec() {
201                    Ok(mut ciphertext) => {
202                        aes_256_cbc_decrypt_in_place(&mut ciphertext, key, iv)?;
203                        Ok(ciphertext)
204                    }
205                    Err(shared) => decrypt_borrowed_signal(&shared, key, iv),
206                }
207            }
208        }
209    }
210}
211
212#[inline(always)]
213fn decrypt_borrowed_signal(
214    message: &SignalMessage,
215    key: &[u8],
216    iv: &[u8],
217) -> std::result::Result<Vec<u8>, DecryptionErrorCrypto> {
218    DECRYPTION_BUFFER.with(|buffer| {
219        let mut buf_wrapper = buffer.borrow_mut();
220        let buf = buf_wrapper.get_buffer();
221        let ciphertext = message
222            .body()
223            .map_err(|_| DecryptionErrorCrypto::BadCiphertext("invalid Signal ciphertext body"))?;
224        aes_256_cbc_decrypt_into(ciphertext, key, iv, buf)?;
225        let result = std::mem::take(buf);
226        // Keep ordinary messages allocation-free without pinning an occasional
227        // oversized plaintext in thread-local storage.
228        buf.reserve(EncryptionBuffer::INITIAL_CAPACITY);
229        Ok(result)
230    })
231}
232
233enum PreKeyDecryptInput<'a> {
234    Borrowed(&'a PreKeySignalMessage),
235    Owned(&'a mut OwnedCiphertextMessage),
236}
237
238impl<'a> PreKeyDecryptInput<'a> {
239    #[inline]
240    fn pre_key_message(&self) -> &PreKeySignalMessage {
241        match self {
242            Self::Borrowed(message) => message,
243            Self::Owned(message) => message
244                .pre_key_message()
245                .expect("owned PreKey decrypt input has a PreKey envelope"),
246        }
247    }
248
249    #[inline]
250    fn into_signal_input(self) -> SignalDecryptInput<'a> {
251        match self {
252            Self::Borrowed(message) => SignalDecryptInput::Borrowed(message.message()),
253            Self::Owned(message) => SignalDecryptInput::Owned(message),
254        }
255    }
256}
257
258pub async fn message_encrypt(
259    ptext: &[u8],
260    remote_address: &ProtocolAddress,
261    session_store: &mut dyn SessionStore,
262    identity_store: &mut dyn IdentityKeyStore,
263) -> Result<CiphertextMessage> {
264    let mut session = SessionCheckout::load(session_store, remote_address)
265        .await?
266        .ok_or_else(|| SignalProtocolError::SessionNotFound(remote_address.clone()))?;
267
268    let result =
269        message_encrypt_inner(ptext, remote_address, session.record_mut(), identity_store).await;
270
271    // Always restore — chain key is only advanced inside the inner
272    // function after identity checks pass, so no counters are burned.
273    session.commit().await?;
274
275    result
276}
277
278async fn message_encrypt_inner(
279    ptext: &[u8],
280    remote_address: &ProtocolAddress,
281    session_record: &mut SessionRecord,
282    identity_store: &mut dyn IdentityKeyStore,
283) -> Result<CiphertextMessage> {
284    let session_state = session_record
285        .session_state_mut()
286        .ok_or_else(|| SignalProtocolError::SessionNotFound(remote_address.clone()))?;
287
288    let chain_key = session_state.get_sender_chain_key()?;
289
290    let (message_keys_gen, next_chain_key) = chain_key.step_with_message_keys()?;
291    let message_keys = message_keys_gen.generate_keys();
292
293    let sender_ephemeral = session_state.sender_ratchet_key()?;
294    let previous_counter = session_state.previous_counter();
295    let session_version = session_state
296        .session_version()?
297        .try_into()
298        .map_err(|_| SignalProtocolError::InvalidSessionStructure("version does not fit in u8"))?;
299
300    let local_identity_key = session_state.local_identity_key()?;
301    let their_identity_key = session_state.remote_identity_key()?.ok_or_else(|| {
302        SignalProtocolError::InvalidState(
303            "message_encrypt",
304            format!("no remote identity key for {remote_address}"),
305        )
306    })?;
307
308    // Check trust before doing any crypto work
309    if !crate::protocol::storage::is_trusted_identity(
310        identity_store,
311        remote_address,
312        &their_identity_key,
313        Direction::Sending,
314    )
315    .await?
316    {
317        log::warn!(
318            "Identity key {} is not trusted for remote address {}",
319            hex::encode(their_identity_key.public_key().public_key_bytes()),
320            remote_address,
321        );
322        return Err(SignalProtocolError::UntrustedIdentity(
323            remote_address.clone(),
324        ));
325    }
326
327    // Encrypt into the thread-local buffer and build the message while still
328    // borrowing it: SignalMessage::new copies the ciphertext into its protobuf
329    // body, so no owned intermediate Vec is needed. The buffer is reused across
330    // calls (cleared, capacity kept) instead of being taken out and reallocated.
331    // aes_256_cbc_encrypt appends from buf.len(), so clear it first; this also
332    // drops any ciphertext left by a prior call that errored.
333    let message = ENCRYPTION_BUFFER.with(|buffer| {
334        let mut buf_wrapper = buffer.borrow_mut();
335        let buf = buf_wrapper.get_buffer();
336        buf.clear();
337        aes_256_cbc_encrypt_into(ptext, message_keys.cipher_key(), message_keys.iv(), buf)
338            .map_err(|_| {
339                log::error!("session state corrupt for {remote_address}");
340                SignalProtocolError::InvalidSessionStructure("invalid sender chain message keys")
341            })?;
342        let ctext = buf.as_slice();
343
344        let message = if let Some(items) = session_state.unacknowledged_pre_key_message_items()? {
345            let local_registration_id = session_state.local_registration_id();
346
347            log::debug!(
348                "Building PreKeyWhisperMessage for: {} with preKeyId: {}",
349                remote_address,
350                items
351                    .pre_key_id()
352                    .map_or_else(|| "<none>".to_string(), |id| id.to_string()),
353            );
354
355            let message = SignalMessage::new(
356                session_version,
357                message_keys.mac_key(),
358                sender_ephemeral,
359                chain_key.index(),
360                previous_counter,
361                ctext,
362                &local_identity_key,
363                &their_identity_key,
364            )?;
365
366            CiphertextMessage::PreKeySignalMessage(PreKeySignalMessage::new(
367                session_version,
368                local_registration_id,
369                items.pre_key_id(),
370                items.signed_pre_key_id(),
371                *items.base_key(),
372                local_identity_key,
373                message,
374            )?)
375        } else {
376            CiphertextMessage::SignalMessage(SignalMessage::new(
377                session_version,
378                message_keys.mac_key(),
379                sender_ephemeral,
380                chain_key.index(),
381                previous_counter,
382                ctext,
383                &local_identity_key,
384                &their_identity_key,
385            )?)
386        };
387        // A plaintext whose ciphertext exceeds MAX_CAPACITY leaves an oversized
388        // buffer in thread-local storage; release it now (the old take+realloc
389        // path did this implicitly) so a single large send doesn't pin memory
390        // per worker thread until get_buffer's periodic shrink fires.
391        if buf.capacity() > EncryptionBuffer::MAX_CAPACITY {
392            *buf = Vec::with_capacity(EncryptionBuffer::INITIAL_CAPACITY);
393        }
394        Ok::<CiphertextMessage, SignalProtocolError>(message)
395    })?;
396
397    crate::protocol::storage::save_identity(identity_store, remote_address, &their_identity_key)
398        .await?;
399
400    session_state.set_sender_chain_key(&next_chain_key)?;
401
402    // Counters are leased in batches so the send path only needs a durable
403    // flush when the lease runs out; a reload fast-forwards past the whole
404    // lease, so this counter can never be re-derived after a crash. A consumer
405    // that waived the lease persists before the wire and reserves nothing.
406    session_record.reserve_sender_chain_counters(chain_key.index());
407
408    Ok(message)
409}
410
411#[allow(clippy::too_many_arguments)]
412pub async fn message_decrypt<R: Rng + CryptoRng>(
413    ciphertext: &CiphertextMessage,
414    remote_address: &ProtocolAddress,
415    session_store: &mut dyn SessionStore,
416    identity_store: &mut dyn IdentityKeyStore,
417    pre_key_store: &mut dyn PreKeyStore,
418    signed_pre_key_store: &dyn SignedPreKeyStore,
419    csprng: &mut R,
420    use_pq_ratchet: UsePQRatchet,
421) -> Result<DecryptionResult> {
422    match ciphertext {
423        CiphertextMessage::SignalMessage(m) => {
424            message_decrypt_signal(m, remote_address, session_store, identity_store, csprng).await
425        }
426        CiphertextMessage::PreKeySignalMessage(m) => {
427            message_decrypt_prekey(
428                m,
429                remote_address,
430                session_store,
431                identity_store,
432                pre_key_store,
433                signed_pre_key_store,
434                csprng,
435                use_pq_ratchet,
436            )
437            .await
438        }
439        _ => Err(SignalProtocolError::InvalidArgument(format!(
440            "message_decrypt cannot be used to decrypt {:?} messages",
441            ciphertext.message_type()
442        ))),
443    }
444}
445
446/// Decrypt a caller-owned Signal envelope, reusing its wire allocation when it
447/// is uniquely owned. See [`OwnedCiphertextMessage`] for retry semantics.
448#[allow(clippy::too_many_arguments)]
449pub async fn message_decrypt_owned<R: Rng + CryptoRng>(
450    ciphertext: &mut OwnedCiphertextMessage,
451    remote_address: &ProtocolAddress,
452    session_store: &mut dyn SessionStore,
453    identity_store: &mut dyn IdentityKeyStore,
454    pre_key_store: &mut dyn PreKeyStore,
455    signed_pre_key_store: &dyn SignedPreKeyStore,
456    csprng: &mut R,
457    use_pq_ratchet: UsePQRatchet,
458) -> Result<DecryptionResult> {
459    match ciphertext.message_type() {
460        Some(CiphertextMessageType::Whisper) => {
461            message_decrypt_signal_input(
462                SignalDecryptInput::Owned(ciphertext),
463                remote_address,
464                session_store,
465                identity_store,
466                csprng,
467            )
468            .await
469        }
470        Some(CiphertextMessageType::PreKey) => {
471            message_decrypt_prekey_input(
472                PreKeyDecryptInput::Owned(ciphertext),
473                remote_address,
474                session_store,
475                identity_store,
476                pre_key_store,
477                signed_pre_key_store,
478                csprng,
479                use_pq_ratchet,
480            )
481            .await
482        }
483        Some(kind) => Err(SignalProtocolError::InvalidArgument(format!(
484            "message_decrypt_owned cannot decrypt {kind:?} messages"
485        ))),
486        None => Err(SignalProtocolError::InvalidArgument(
487            "owned ciphertext was already consumed".to_owned(),
488        )),
489    }
490}
491
492#[allow(clippy::too_many_arguments)]
493pub async fn message_decrypt_prekey<R: Rng + CryptoRng>(
494    ciphertext: &PreKeySignalMessage,
495    remote_address: &ProtocolAddress,
496    session_store: &mut dyn SessionStore,
497    identity_store: &mut dyn IdentityKeyStore,
498    pre_key_store: &mut dyn PreKeyStore,
499    signed_pre_key_store: &dyn SignedPreKeyStore,
500    csprng: &mut R,
501    use_pq_ratchet: UsePQRatchet,
502) -> Result<DecryptionResult> {
503    message_decrypt_prekey_input(
504        PreKeyDecryptInput::Borrowed(ciphertext),
505        remote_address,
506        session_store,
507        identity_store,
508        pre_key_store,
509        signed_pre_key_store,
510        csprng,
511        use_pq_ratchet,
512    )
513    .await
514}
515
516#[allow(clippy::too_many_arguments)]
517async fn message_decrypt_prekey_input<R: Rng + CryptoRng>(
518    ciphertext: PreKeyDecryptInput<'_>,
519    remote_address: &ProtocolAddress,
520    session_store: &mut dyn SessionStore,
521    identity_store: &mut dyn IdentityKeyStore,
522    pre_key_store: &mut dyn PreKeyStore,
523    signed_pre_key_store: &dyn SignedPreKeyStore,
524    csprng: &mut R,
525    use_pq_ratchet: UsePQRatchet,
526) -> Result<DecryptionResult> {
527    let mut session = SessionCheckout::load_or_create(session_store, remote_address).await?;
528    session.snapshot_for_rollback();
529
530    let result = message_decrypt_prekey_inner(
531        ciphertext,
532        remote_address,
533        session.record_mut(),
534        identity_store,
535        pre_key_store,
536        signed_pre_key_store,
537        csprng,
538        use_pq_ratchet,
539    )
540    .await;
541
542    if result.is_ok() {
543        session.commit().await?;
544    } else {
545        session.rollback().await?;
546    }
547
548    let (plaintext, pre_key_used, identity_change) = result?;
549
550    // The consumed prekey is reported up, not deleted here: the promoted session
551    // is still volatile in the caller's cache, so the prekey must only be removed
552    // once that session is durable (see DecryptionResult::consumed_prekey_id).
553    Ok(DecryptionResult {
554        plaintext,
555        identity_change,
556        consumed_prekey_id: pre_key_used,
557    })
558}
559
560#[allow(clippy::too_many_arguments)]
561async fn message_decrypt_prekey_inner<R: Rng + CryptoRng>(
562    ciphertext: PreKeyDecryptInput<'_>,
563    remote_address: &ProtocolAddress,
564    session_record: &mut SessionRecord,
565    identity_store: &mut dyn IdentityKeyStore,
566    pre_key_store: &mut dyn PreKeyStore,
567    signed_pre_key_store: &dyn SignedPreKeyStore,
568    csprng: &mut R,
569    use_pq_ratchet: UsePQRatchet,
570) -> Result<(Vec<u8>, Option<PreKeyId>, IdentityChange)> {
571    let process_prekey_result = session::process_prekey(
572        ciphertext.pre_key_message(),
573        remote_address,
574        session_record,
575        identity_store,
576        pre_key_store,
577        signed_pre_key_store,
578        use_pq_ratchet,
579    )
580    .await;
581
582    let (pre_key_used, identity_to_save, reused_existing_session) = match process_prekey_result {
583        Ok(result) => result,
584        Err(e) => {
585            let errs = [e];
586            log::error!(
587                "{}",
588                create_decryption_failure_log(
589                    remote_address,
590                    &errs,
591                    session_record,
592                    ciphertext.pre_key_message().message()
593                )?
594            );
595            let [e] = errs;
596            return Err(e);
597        }
598    };
599    let their_identity_key = *identity_to_save.their_identity_key;
600    let mut ciphertext = ciphertext.into_signal_input();
601
602    let decrypt = decrypt_message_with_record(
603        remote_address,
604        session_record,
605        &mut ciphertext,
606        CiphertextMessageType::PreKey,
607        csprng,
608    )?;
609
610    let saved = identity_store
611        .save_identity(remote_address, &their_identity_key)
612        .await?;
613    let plaintext = decrypt.commit();
614
615    // A duplicate/out-of-order pkmsg that matched an existing session carries the
616    // identity from when that session was built, not a fresh rotation. Reporting
617    // it as a change would fire a spurious local identity-change reaction (mirrors
618    // the previous-session SignalMessage path).
619    let identity_change = if reused_existing_session {
620        IdentityChange::NewOrUnchanged
621    } else {
622        saved
623    };
624
625    Ok((plaintext, pre_key_used.pre_key_id, identity_change))
626}
627
628pub async fn message_decrypt_signal<R: Rng + CryptoRng>(
629    ciphertext: &SignalMessage,
630    remote_address: &ProtocolAddress,
631    session_store: &mut dyn SessionStore,
632    identity_store: &mut dyn IdentityKeyStore,
633    csprng: &mut R,
634) -> Result<DecryptionResult> {
635    message_decrypt_signal_input(
636        SignalDecryptInput::Borrowed(ciphertext),
637        remote_address,
638        session_store,
639        identity_store,
640        csprng,
641    )
642    .await
643}
644
645async fn message_decrypt_signal_input<R: Rng + CryptoRng>(
646    mut ciphertext: SignalDecryptInput<'_>,
647    remote_address: &ProtocolAddress,
648    session_store: &mut dyn SessionStore,
649    identity_store: &mut dyn IdentityKeyStore,
650    csprng: &mut R,
651) -> Result<DecryptionResult> {
652    let mut session = SessionCheckout::load(session_store, remote_address)
653        .await?
654        .ok_or_else(|| SignalProtocolError::SessionNotFound(remote_address.clone()))?;
655
656    let result = message_decrypt_signal_inner(
657        &mut ciphertext,
658        remote_address,
659        session.record_mut(),
660        identity_store,
661        csprng,
662    )
663    .await;
664
665    session.commit().await?;
666
667    let (plaintext, identity_change) = result?;
668    Ok(DecryptionResult {
669        plaintext,
670        identity_change,
671        consumed_prekey_id: None,
672    })
673}
674
675async fn message_decrypt_signal_inner<R: Rng + CryptoRng>(
676    ciphertext: &mut SignalDecryptInput<'_>,
677    remote_address: &ProtocolAddress,
678    session_record: &mut SessionRecord,
679    identity_store: &mut dyn IdentityKeyStore,
680    csprng: &mut R,
681) -> Result<(Vec<u8>, IdentityChange)> {
682    // A record with no current state and no previous states is degenerate — treat
683    // it as missing so the caller gets SessionNotFound and sends a proper retry
684    // receipt (with error code 1) instead of attempting decryption that will always
685    // fail with an unhelpful InvalidMessage error.
686    if session_record.session_state().is_none() && session_record.previous_session_count() == 0 {
687        log::warn!(
688            "Session record for {} exists but has no usable state (no current, 0 previous). \
689             Treating as SessionNotFound.",
690            remote_address
691        );
692        return Err(SignalProtocolError::SessionNotFound(remote_address.clone()));
693    }
694
695    let decrypt = decrypt_message_with_record(
696        remote_address,
697        session_record,
698        ciphertext,
699        CiphertextMessageType::Whisper,
700        csprng,
701    )?;
702
703    let their_identity_key = decrypt
704        .state()
705        .remote_identity_key()
706        .expect("successfully decrypted; must have a remote identity key")
707        .expect("successfully decrypted; must have a remote identity key");
708
709    // Handle identity trust based on whether we used the current or a previous session.
710    //
711    // For current session: Check if the identity is trusted, and save it if so.
712    // For previous session: Skip the trust check and save the identity directly.
713    //
714    // When we successfully decrypt with a previous (archived) session, we already had
715    // a valid session with that identity - it was trusted when the session was established.
716    // The previous session gets promoted to current via `promote_old_session`, so we need
717    // to save its identity to avoid UntrustedIdentity errors on subsequent messages.
718    // This handles out-of-order message delivery after an identity change gracefully.
719    let identity_change = if decrypt.used_previous_session() {
720        log::debug!(
721            "Saving identity for {} from previous session (skipping trust check)",
722            remote_address,
723        );
724        // Re-saving an archived session's (older) identity for out-of-order
725        // delivery is not the peer's current identity changing, so never report
726        // it as a change. Doing so would fire a spurious local identity-change
727        // reaction and clobber the current identity.
728        crate::protocol::storage::save_identity(
729            identity_store,
730            remote_address,
731            &their_identity_key,
732        )
733        .await?;
734        IdentityChange::NewOrUnchanged
735    } else {
736        if !crate::protocol::storage::is_trusted_identity(
737            identity_store,
738            remote_address,
739            &their_identity_key,
740            Direction::Receiving,
741        )
742        .await?
743        {
744            log::warn!(
745                "Identity key {} is not trusted for remote address {}",
746                hex::encode(their_identity_key.public_key().public_key_bytes()),
747                remote_address,
748            );
749            return Err(SignalProtocolError::UntrustedIdentity(
750                remote_address.clone(),
751            ));
752        }
753
754        crate::protocol::storage::save_identity(identity_store, remote_address, &their_identity_key)
755            .await?
756    };
757
758    Ok((decrypt.commit(), identity_change))
759}
760
761/// A refused agreement is a local failure, not a verdict about the message, so
762/// it outranks the MAC-based classifications: the caller has to be able to tell
763/// "this backend is down" from "this message is corrupt". Kept in the pile
764/// rather than returned on the spot, since a sibling session with an open
765/// receiver chain needs no agreement and may still read the message.
766fn take_key_agreement_failure(errs: &mut Vec<SignalProtocolError>) -> Option<SignalProtocolError> {
767    let at = errs
768        .iter()
769        .position(|e| matches!(e, SignalProtocolError::KeyAgreementFailed(_)))?;
770    Some(errs.remove(at))
771}
772
773fn create_decryption_failure_log(
774    remote_address: &ProtocolAddress,
775    mut errs: &[SignalProtocolError],
776    record: &SessionRecord,
777    ciphertext: &SignalMessage,
778) -> Result<String> {
779    fn append_session_summary(
780        lines: &mut Vec<String>,
781        idx: usize,
782        state: std::result::Result<&SessionState, crate::protocol::state::InvalidSessionError>,
783        err: Option<&SignalProtocolError>,
784    ) {
785        let chains = state.map(|state| state.all_receiver_chain_logging_info());
786        match (err, &chains) {
787            (Some(err), Ok(chains)) => {
788                lines.push(format!(
789                    "Candidate session {} failed with '{}', had {} receiver chains",
790                    idx,
791                    err,
792                    chains.len()
793                ));
794            }
795            (Some(err), Err(state_err)) => {
796                lines.push(format!(
797                    "Candidate session {idx} failed with '{err}'; cannot get receiver chain info ({state_err})",
798                ));
799            }
800            (None, Ok(chains)) => {
801                lines.push(format!(
802                    "Candidate session {} had {} receiver chains",
803                    idx,
804                    chains.len()
805                ));
806            }
807            (None, Err(state_err)) => {
808                lines.push(format!(
809                    "Candidate session {idx}: cannot get receiver chain info ({state_err})",
810                ));
811            }
812        }
813
814        if let Ok(chains) = chains {
815            for chain in chains {
816                let chain_idx = match chain.1 {
817                    Some(i) => i.to_string(),
818                    None => "missing in protobuf".to_string(),
819                };
820
821                lines.push(format!(
822                    "Receiver chain with sender ratchet public key {} chain key index {}",
823                    hex::encode(chain.0),
824                    chain_idx
825                ));
826            }
827        }
828    }
829
830    let mut lines = vec![];
831
832    lines.push(format!(
833        "Message from {} failed to decrypt; sender ratchet public key {} message counter {}",
834        remote_address,
835        hex::encode(ciphertext.sender_ratchet_key().public_key_bytes()),
836        ciphertext.counter()
837    ));
838
839    if let Some(current_session) = record.session_state() {
840        let err = errs.first();
841        if err.is_some() {
842            errs = &errs[1..];
843        }
844        append_session_summary(&mut lines, 0, Ok(current_session), err);
845    } else {
846        lines.push("No current session".to_string());
847    }
848
849    for (idx, (state, err)) in record
850        .previous_session_states()
851        .zip(errs.iter().map(Some).chain(std::iter::repeat(None)))
852        .enumerate()
853    {
854        let state = match state {
855            Ok(ref state) => Ok(state),
856            Err(err) => Err(err),
857        };
858        append_session_summary(&mut lines, idx + 1, state, err);
859    }
860
861    Ok(lines.join("\n"))
862}
863
864enum RecordDecryptState {
865    Current {
866        snapshot: DecryptSnapshot,
867        sender_chain_reset: bool,
868    },
869    Previous {
870        state: Box<SessionState>,
871        effect: StateDecryptEffect,
872        index: usize,
873    },
874}
875
876struct DeferredCurrentDecrypt<'a> {
877    record: &'a mut SessionRecord,
878    ratchet_key: PublicKey,
879    chain_key: ChainKey,
880    plaintext: Vec<u8>,
881}
882
883enum RecordDecrypt<'a> {
884    Deferred(DeferredCurrentDecrypt<'a>),
885    Transaction(RecordDecryptTransaction<'a>),
886}
887
888impl RecordDecrypt<'_> {
889    fn state(&self) -> &SessionState {
890        match self {
891            Self::Deferred(decrypt) => decrypt
892                .record
893                .session_state()
894                .expect("a deferred decrypt keeps the current state installed"),
895            Self::Transaction(decrypt) => decrypt.state(),
896        }
897    }
898
899    fn used_previous_session(&self) -> bool {
900        match self {
901            Self::Deferred(_) => false,
902            Self::Transaction(decrypt) => decrypt.used_previous_session(),
903        }
904    }
905
906    fn commit(self) -> Vec<u8> {
907        match self {
908            Self::Deferred(mut decrypt) => {
909                let state = decrypt
910                    .record
911                    .session_state_mut()
912                    .expect("a deferred decrypt keeps the current state installed");
913                state
914                    .set_receiver_chain_key(&decrypt.ratchet_key, &decrypt.chain_key)
915                    .expect("the deferred in-order receiver chain remains installed");
916                state.clear_unacknowledged_pre_key_message();
917                std::mem::take(&mut decrypt.plaintext)
918            }
919            Self::Transaction(decrypt) => decrypt.commit(),
920        }
921    }
922}
923
924struct RecordDecryptTransaction<'a> {
925    record: &'a mut SessionRecord,
926    state: Option<RecordDecryptState>,
927    plaintext: Vec<u8>,
928}
929
930impl RecordDecryptTransaction<'_> {
931    fn state(&self) -> &SessionState {
932        match self
933            .state
934            .as_ref()
935            .expect("a live decrypt transaction owns its rollback")
936        {
937            RecordDecryptState::Current { .. } => self
938                .record
939                .session_state()
940                .expect("a current decrypt transaction keeps the current state installed"),
941            RecordDecryptState::Previous { state, .. } => state,
942        }
943    }
944
945    fn used_previous_session(&self) -> bool {
946        matches!(self.state, Some(RecordDecryptState::Previous { .. }))
947    }
948
949    fn commit(mut self) -> Vec<u8> {
950        match self
951            .state
952            .take()
953            .expect("a live decrypt transaction owns its rollback")
954        {
955            RecordDecryptState::Current {
956                sender_chain_reset, ..
957            } => {
958                let state = self
959                    .record
960                    .session_state_mut()
961                    .expect("a current decrypt transaction keeps the current state installed");
962                state.clear_unacknowledged_pre_key_message();
963                if sender_chain_reset {
964                    self.record.rebase_lease_after_sender_chain_reset();
965                }
966            }
967            RecordDecryptState::Previous {
968                mut state, effect, ..
969            } => {
970                let sender_chain_reset = effect.sender_chain_reset();
971                effect.commit(&mut state);
972                state.clear_unacknowledged_pre_key_message();
973                if sender_chain_reset {
974                    // The ratchet gave this state a chain built from a fresh
975                    // random ephemeral: no counter on it can have been spent,
976                    // so it takes the same path as any other fresh ratchet
977                    // rather than having the outgoing chain's lease burned
978                    // into it (which, past the fast-forward ceiling, would
979                    // drop the chain outright).
980                    self.record.promote_fresh_state(*state);
981                } else {
982                    self.record.promote_state(*state);
983                }
984            }
985        }
986        std::mem::take(&mut self.plaintext)
987    }
988}
989
990impl Drop for RecordDecryptTransaction<'_> {
991    fn drop(&mut self) {
992        let Some(state) = self.state.take() else {
993            return;
994        };
995        match state {
996            RecordDecryptState::Current { snapshot, .. } => self
997                .record
998                .session_state_mut()
999                .expect("a current decrypt transaction keeps the current state installed")
1000                .restore_decrypt_snapshot(snapshot),
1001            RecordDecryptState::Previous {
1002                mut state,
1003                effect,
1004                index,
1005            } => {
1006                effect.rollback(&mut state);
1007                self.record.restore_previous_session(index, *state);
1008            }
1009        }
1010    }
1011}
1012
1013fn decrypt_message_with_record<'a, R: Rng + CryptoRng>(
1014    remote_address: &ProtocolAddress,
1015    record: &'a mut SessionRecord,
1016    ciphertext: &mut SignalDecryptInput<'_>,
1017    original_message_type: CiphertextMessageType,
1018    csprng: &mut R,
1019) -> Result<RecordDecrypt<'a>> {
1020    debug_assert!(matches!(
1021        original_message_type,
1022        CiphertextMessageType::Whisper | CiphertextMessageType::PreKey
1023    ));
1024    let log_decryption_failure =
1025        |message: &SignalMessage, state: &SessionState, error: &SignalProtocolError| {
1026            // A warning rather than an error because we try multiple sessions.
1027            log::warn!(
1028                "Failed to decrypt {:?} message with ratchet key: {} and counter: {}. \
1029             Session loaded for {}. Local session has base key: {} and counter: {}. {}",
1030                original_message_type,
1031                hex::encode(message.sender_ratchet_key().public_key_bytes()),
1032                message.counter(),
1033                remote_address,
1034                state
1035                    .sender_ratchet_key_for_logging()
1036                    .unwrap_or_else(|e| format!("<error: {e}>")),
1037                state.previous_counter(),
1038                error
1039            );
1040        };
1041
1042    let mut errs = vec![];
1043
1044    // Take ownership of current state instead of cloning - avoids allocation
1045    if let Some(mut current_state) = record.take_session_state() {
1046        let result = decrypt_message_with_state(
1047            CurrentOrPrevious::Current,
1048            &mut current_state,
1049            ciphertext,
1050            original_message_type,
1051            remote_address,
1052            csprng,
1053        );
1054
1055        match result {
1056            Ok(result) => {
1057                log::debug!(
1058                    "decrypted {:?} message from {} with current session state (base key {})",
1059                    original_message_type,
1060                    remote_address,
1061                    current_state
1062                        .sender_ratchet_key_for_logging()
1063                        .expect("successful decrypt always has a valid base key"),
1064                );
1065                record.set_session_state(current_state);
1066                return match result.effect {
1067                    StateDecryptEffect::DeferredChainKey {
1068                        ratchet_key,
1069                        chain_key,
1070                    } => Ok(RecordDecrypt::Deferred(DeferredCurrentDecrypt {
1071                        record,
1072                        ratchet_key,
1073                        chain_key,
1074                        plaintext: result.plaintext,
1075                    })),
1076                    StateDecryptEffect::Applied {
1077                        snapshot,
1078                        sender_chain_reset,
1079                    } => Ok(RecordDecrypt::Transaction(RecordDecryptTransaction {
1080                        record,
1081                        state: Some(RecordDecryptState::Current {
1082                            snapshot,
1083                            sender_chain_reset,
1084                        }),
1085                        plaintext: result.plaintext,
1086                    })),
1087                };
1088            }
1089            Err(SignalProtocolError::DuplicatedMessage(chain, counter))
1090                if original_message_type == CiphertextMessageType::Whisper =>
1091            {
1092                // Re-initiations reuse the peer's signed pre-key as a ratchet
1093                // key, so an archived session may still hold the skipped key
1094                // this state has already consumed. Keep searching.
1095                let error = SignalProtocolError::DuplicatedMessage(chain, counter);
1096                log_decryption_failure(ciphertext.signal_message(), &current_state, &error);
1097                errs.push(error);
1098                record.set_session_state(current_state);
1099            }
1100            Err(SignalProtocolError::DuplicatedMessage(chain, counter)) => {
1101                let error = SignalProtocolError::DuplicatedMessage(chain, counter);
1102                log_decryption_failure(ciphertext.signal_message(), &current_state, &error);
1103                record.set_session_state(current_state);
1104                return Err(error);
1105            }
1106            Err(e) if !ciphertext.is_available() => {
1107                // Authentication succeeded, but the provider rejected the
1108                // caller-owned body after it had been consumed for in-place
1109                // decryption. No other session can validly authenticate it.
1110                record.set_session_state(current_state);
1111                return Err(e);
1112            }
1113            Err(e) => {
1114                log_decryption_failure(ciphertext.signal_message(), &current_state, &e);
1115                errs.push(e);
1116                match original_message_type {
1117                    CiphertextMessageType::PreKey => {
1118                        // A PreKey message creates a session and then decrypts a Whisper message
1119                        // using that session. No need to check older sessions.
1120                        // Log at warn level since this error may be recoverable at higher layers
1121                        // (e.g., UntrustedIdentity can be handled by clearing old identity and retrying)
1122                        // Restore state before returning error
1123                        record.set_session_state(current_state);
1124                        log::warn!(
1125                            "{}",
1126                            create_decryption_failure_log(
1127                                remote_address,
1128                                &errs,
1129                                record,
1130                                ciphertext.signal_message()
1131                            )?
1132                        );
1133                        if let Some(refused) = take_key_agreement_failure(&mut errs) {
1134                            return Err(refused);
1135                        }
1136                        // Preserve BadMac so it maps to WA Web error code 7 in retry receipts.
1137                        if errs
1138                            .iter()
1139                            .any(|e| matches!(e, SignalProtocolError::BadMac(_)))
1140                        {
1141                            return Err(SignalProtocolError::BadMac(original_message_type));
1142                        }
1143                        return Err(SignalProtocolError::InvalidMessage(
1144                            original_message_type,
1145                            "decryption failed",
1146                        ));
1147                    }
1148                    CiphertextMessageType::Whisper => {
1149                        // Restore state before trying previous sessions
1150                        record.set_session_state(current_state);
1151                    }
1152                    CiphertextMessageType::SenderKey | CiphertextMessageType::Plaintext => {
1153                        unreachable!("should not be using Double Ratchet for these")
1154                    }
1155                }
1156            }
1157        }
1158    }
1159
1160    // Try some old sessions using take/restore pattern to avoid cloning all sessions.
1161    // We take ownership of one session at a time, try to decrypt, and either:
1162    // - Promote it if successful (session already removed by take)
1163    // - Restore it if failed (put it back at the same index)
1164    let previous_count = record.previous_session_count();
1165    let mut idx = 0;
1166
1167    while idx < previous_count {
1168        // Take ownership of this session (removes from list)
1169        let Some(mut previous) = record.take_previous_session(idx) else {
1170            // Should not happen since we checked count, but handle gracefully
1171            break;
1172        };
1173
1174        let result = decrypt_message_with_state(
1175            CurrentOrPrevious::Previous,
1176            &mut previous,
1177            ciphertext,
1178            original_message_type,
1179            remote_address,
1180            csprng,
1181        );
1182
1183        match result {
1184            Ok(result) => {
1185                log::debug!(
1186                    "decrypted {:?} message from {} with PREVIOUS session state (base key {})",
1187                    original_message_type,
1188                    remote_address,
1189                    previous
1190                        .sender_ratchet_key_for_logging()
1191                        .expect("successful decrypt always has a valid base key"),
1192                );
1193                return Ok(RecordDecrypt::Transaction(RecordDecryptTransaction {
1194                    record,
1195                    state: Some(RecordDecryptState::Previous {
1196                        state: Box::new(previous),
1197                        effect: result.effect,
1198                        index: idx,
1199                    }),
1200                    plaintext: result.plaintext,
1201                }));
1202            }
1203            Err(SignalProtocolError::DuplicatedMessage(chain, counter))
1204                if original_message_type == CiphertextMessageType::Whisper =>
1205            {
1206                let error = SignalProtocolError::DuplicatedMessage(chain, counter);
1207                log_decryption_failure(ciphertext.signal_message(), &previous, &error);
1208                errs.push(error);
1209                record.restore_previous_session(idx, previous);
1210                idx += 1;
1211            }
1212            Err(SignalProtocolError::DuplicatedMessage(chain, counter)) => {
1213                let error = SignalProtocolError::DuplicatedMessage(chain, counter);
1214                log_decryption_failure(ciphertext.signal_message(), &previous, &error);
1215                record.restore_previous_session(idx, previous);
1216                return Err(error);
1217            }
1218            Err(e) if !ciphertext.is_available() => {
1219                record.restore_previous_session(idx, previous);
1220                return Err(e);
1221            }
1222            Err(e) => {
1223                log_decryption_failure(ciphertext.signal_message(), &previous, &e);
1224                errs.push(e);
1225                // Restore the session at the same index and move to next
1226                record.restore_previous_session(idx, previous);
1227                idx += 1;
1228            }
1229        }
1230    }
1231
1232    // No session worked - log error and return failure
1233    let previous_state_count = record.previous_session_count();
1234
1235    if let Some(current_state) = record.session_state() {
1236        log::error!(
1237            "No valid session for recipient: {}, current session base key {}, number of previous states: {}",
1238            remote_address,
1239            current_state
1240                .sender_ratchet_key_for_logging()
1241                .unwrap_or_else(|e| format!("<error: {e}>")),
1242            previous_state_count,
1243        );
1244    } else {
1245        log::error!(
1246            "No valid session for recipient: {}, (no current session state), number of previous states: {}",
1247            remote_address,
1248            previous_state_count,
1249        );
1250    }
1251    log::error!(
1252        "{}",
1253        create_decryption_failure_log(remote_address, &errs, record, ciphertext.signal_message())?
1254    );
1255
1256    // A state that recognized the exact chain and an already-consumed counter is
1257    // definitive evidence of a replay; sibling sessions sharing that ratchet key
1258    // derive different keys and fail their MAC as expected noise. Classifying a
1259    // replay as BadMac would trigger a retry receipt for an already-processed
1260    // message, so the duplicate verdict must win.
1261    if let Some((chain, counter)) = errs.iter().find_map(|error| match error {
1262        SignalProtocolError::DuplicatedMessage(chain, counter) => Some((*chain, *counter)),
1263        _ => None,
1264    }) {
1265        return Err(SignalProtocolError::DuplicatedMessage(chain, counter));
1266    }
1267    if let Some(refused) = take_key_agreement_failure(&mut errs) {
1268        return Err(refused);
1269    }
1270    // Otherwise, if any session state produced a BadMac error, propagate it rather
1271    // than the generic InvalidMessage. BadMac means at least one state derived a
1272    // message key and verified the MAC — it specifically failed, which maps to WA
1273    // Web error code 7 (SignalErrorBadMac) vs 4 (SignalErrorInvalidMessage).
1274    if errs
1275        .iter()
1276        .any(|e| matches!(e, SignalProtocolError::BadMac(_)))
1277    {
1278        return Err(SignalProtocolError::BadMac(original_message_type));
1279    }
1280
1281    Err(SignalProtocolError::InvalidMessage(
1282        original_message_type,
1283        "decryption failed",
1284    ))
1285}
1286
1287#[derive(Clone, Copy)]
1288enum CurrentOrPrevious {
1289    Current,
1290    Previous,
1291}
1292
1293impl std::fmt::Display for CurrentOrPrevious {
1294    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1295        match self {
1296            Self::Current => write!(f, "current"),
1297            Self::Previous => write!(f, "previous"),
1298        }
1299    }
1300}
1301
1302struct StateDecryptResult {
1303    plaintext: Vec<u8>,
1304    effect: StateDecryptEffect,
1305}
1306
1307enum StateDecryptEffect {
1308    DeferredChainKey {
1309        ratchet_key: PublicKey,
1310        chain_key: ChainKey,
1311    },
1312    Applied {
1313        snapshot: DecryptSnapshot,
1314        /// A DH ratchet replaced this state's sender chain, so the record's
1315        /// counter lease no longer describes the chain it is about to gate.
1316        sender_chain_reset: bool,
1317    },
1318}
1319
1320impl StateDecryptEffect {
1321    /// The in-order fast path never ratchets, so only an applied decrypt can
1322    /// have retired a sender chain.
1323    fn sender_chain_reset(&self) -> bool {
1324        match self {
1325            Self::DeferredChainKey { .. } => false,
1326            Self::Applied {
1327                sender_chain_reset, ..
1328            } => *sender_chain_reset,
1329        }
1330    }
1331
1332    fn commit(self, state: &mut SessionState) {
1333        match self {
1334            Self::DeferredChainKey {
1335                ratchet_key,
1336                chain_key,
1337            } => {
1338                state
1339                    .set_receiver_chain_key(&ratchet_key, &chain_key)
1340                    .expect("the deferred in-order receiver chain remains installed");
1341            }
1342            Self::Applied { .. } => {}
1343        }
1344    }
1345
1346    fn rollback(self, state: &mut SessionState) {
1347        match self {
1348            Self::DeferredChainKey { .. } => {}
1349            Self::Applied { snapshot, .. } => state.restore_decrypt_snapshot(snapshot),
1350        }
1351    }
1352}
1353
1354fn decrypt_message_with_state<R: Rng + CryptoRng>(
1355    current_or_previous: CurrentOrPrevious,
1356    state: &mut SessionState,
1357    ciphertext: &mut SignalDecryptInput<'_>,
1358    original_message_type: CiphertextMessageType,
1359    remote_address: &ProtocolAddress,
1360    csprng: &mut R,
1361) -> Result<StateDecryptResult> {
1362    // Check for a completely empty or invalid session state before we do anything else.
1363    let _ = state.root_key().map_err(|_| {
1364        SignalProtocolError::InvalidMessage(
1365            original_message_type,
1366            "No session available to decrypt",
1367        )
1368    })?;
1369
1370    let message = ciphertext.signal_message();
1371    let ciphertext_version = message.message_version() as u32;
1372    if ciphertext_version != state.session_version()? {
1373        return Err(SignalProtocolError::UnrecognizedMessageVersion(
1374            ciphertext_version,
1375        ));
1376    }
1377
1378    let their_ephemeral = *message.sender_ratchet_key();
1379    let counter = message.counter();
1380
1381    // Deferring the common in-order advance keeps cancellation rollback free.
1382    let receiver_chain = state.receiver_chain_state(&their_ephemeral)?;
1383    if let Some(ReceiverChainState::Open(chain_key)) = receiver_chain
1384        && chain_key.index() == counter
1385    {
1386        let (message_key_gen, next_chain) = chain_key.step_with_message_keys()?;
1387        let plaintext = decrypt_with_message_keys(
1388            current_or_previous,
1389            state,
1390            ciphertext,
1391            original_message_type,
1392            remote_address,
1393            message_key_gen,
1394        )?;
1395        return Ok(StateDecryptResult {
1396            plaintext,
1397            effect: StateDecryptEffect::DeferredChainKey {
1398                ratchet_key: their_ephemeral,
1399                chain_key: next_chain,
1400            },
1401        });
1402    }
1403
1404    let snapshot = state.decrypt_snapshot();
1405
1406    let result = decrypt_with_pending_state(
1407        current_or_previous,
1408        state,
1409        ciphertext,
1410        original_message_type,
1411        remote_address,
1412        csprng,
1413        &their_ephemeral,
1414        receiver_chain,
1415        counter,
1416    );
1417    match result {
1418        Ok((plaintext, sender_chain_reset)) => Ok(StateDecryptResult {
1419            plaintext,
1420            effect: StateDecryptEffect::Applied {
1421                snapshot,
1422                sender_chain_reset,
1423            },
1424        }),
1425        Err(e) => {
1426            state.restore_decrypt_snapshot(snapshot);
1427            Err(e)
1428        }
1429    }
1430}
1431
1432#[allow(clippy::too_many_arguments)]
1433fn decrypt_with_pending_state<R: Rng + CryptoRng>(
1434    current_or_previous: CurrentOrPrevious,
1435    state: &mut SessionState,
1436    ciphertext: &mut SignalDecryptInput<'_>,
1437    original_message_type: CiphertextMessageType,
1438    remote_address: &ProtocolAddress,
1439    csprng: &mut R,
1440    their_ephemeral: &PublicKey,
1441    receiver_chain: Option<ReceiverChainState>,
1442    counter: u32,
1443) -> Result<(Vec<u8>, bool)> {
1444    if let Some(ReceiverChainState::Closed { next_index }) = receiver_chain {
1445        if counter >= next_index {
1446            return Err(SignalProtocolError::InvalidSessionStructure(
1447                "receiver chain is closed",
1448            ));
1449        }
1450        let Some(message_key_gen) = state.get_message_keys(their_ephemeral, counter)? else {
1451            return Err(SignalProtocolError::DuplicatedMessage(next_index, counter));
1452        };
1453        return decrypt_with_message_keys(
1454            current_or_previous,
1455            state,
1456            ciphertext,
1457            original_message_type,
1458            remote_address,
1459            message_key_gen,
1460        )
1461        .map(|plaintext| (plaintext, false));
1462    }
1463
1464    let (chain_key, deferred_ratchet) =
1465        get_or_create_chain_key(state, their_ephemeral, remote_address, receiver_chain)?;
1466
1467    let message_key_gen = get_or_create_message_key(
1468        state,
1469        their_ephemeral,
1470        remote_address,
1471        original_message_type,
1472        &chain_key,
1473        counter,
1474    )?;
1475
1476    let plaintext = decrypt_with_message_keys(
1477        current_or_previous,
1478        state,
1479        ciphertext,
1480        original_message_type,
1481        remote_address,
1482        message_key_gen,
1483    )?;
1484
1485    // Generating a new sender ratchet requires fresh entropy and a second DH.
1486    // Neither can affect inbound MAC verification, so perform them only after
1487    // the candidate session has authenticated the message.
1488    let sender_chain_reset = if let Some(deferred_ratchet) = deferred_ratchet {
1489        deferred_ratchet.apply(state, their_ephemeral, csprng)?;
1490        true
1491    } else {
1492        false
1493    };
1494
1495    Ok((plaintext, sender_chain_reset))
1496}
1497
1498fn decrypt_with_message_keys(
1499    current_or_previous: CurrentOrPrevious,
1500    state: &SessionState,
1501    ciphertext: &mut SignalDecryptInput<'_>,
1502    original_message_type: CiphertextMessageType,
1503    remote_address: &ProtocolAddress,
1504    message_key_gen: MessageKeyGenerator,
1505) -> Result<Vec<u8>> {
1506    let message_keys = message_key_gen.generate_keys();
1507
1508    let their_identity_key =
1509        state
1510            .remote_identity_key()?
1511            .ok_or(SignalProtocolError::InvalidSessionStructure(
1512                "cannot decrypt without remote identity key",
1513            ))?;
1514
1515    let local_identity_key = state.local_identity_key()?;
1516
1517    let mac_valid = ciphertext.signal_message().verify_mac(
1518        &their_identity_key,
1519        &local_identity_key,
1520        message_keys.mac_key(),
1521    )?;
1522
1523    if !mac_valid {
1524        let their_id_fingerprint = hex::encode(their_identity_key.public_key().public_key_bytes());
1525        let local_id_fingerprint = hex::encode(local_identity_key.public_key().public_key_bytes());
1526
1527        let mac_key_bytes = message_keys.mac_key();
1528        let mac_key_fingerprint: String = hex::encode(mac_key_bytes).chars().take(8).collect();
1529
1530        log::error!(
1531            "MAC verification failed for message from {}. \
1532             Remote Identity: {}, \
1533             Local Identity: {}, \
1534             MAC Key Fingerprint: {}...",
1535            remote_address,
1536            their_id_fingerprint,
1537            local_id_fingerprint,
1538            mac_key_fingerprint
1539        );
1540        return Err(SignalProtocolError::BadMac(original_message_type));
1541    }
1542
1543    match ciphertext.decrypt(message_keys.cipher_key(), message_keys.iv()) {
1544        Ok(plaintext) => Ok(plaintext),
1545        Err(DecryptionErrorCrypto::BadKeyOrIv) => {
1546            log::warn!("{current_or_previous} session state corrupt for {remote_address}",);
1547            Err(SignalProtocolError::InvalidSessionStructure(
1548                "invalid receiver chain message keys",
1549            ))
1550        }
1551        Err(DecryptionErrorCrypto::BadCiphertext(msg)) => {
1552            log::warn!("failed to decrypt 1:1 message: {msg}");
1553            Err(SignalProtocolError::InvalidMessage(
1554                original_message_type,
1555                "failed to decrypt",
1556            ))
1557        }
1558    }
1559}
1560
1561#[must_use = "the deferred sender ratchet must be applied after authenticated decryption succeeds"]
1562struct DeferredSenderRatchet {
1563    receiver_root_key: RootKey,
1564    previous_counter: u32,
1565}
1566
1567impl DeferredSenderRatchet {
1568    fn apply<R: Rng + CryptoRng>(
1569        self,
1570        state: &mut SessionState,
1571        their_ephemeral: &PublicKey,
1572        csprng: &mut R,
1573    ) -> Result<()> {
1574        let our_new_ephemeral = KeyPair::generate(csprng);
1575        let sender_chain = self
1576            .receiver_root_key
1577            .create_chain(their_ephemeral, &our_new_ephemeral.private_key)?;
1578
1579        state.set_root_key(&sender_chain.0);
1580        state.set_previous_counter(self.previous_counter);
1581        state.set_sender_chain(&our_new_ephemeral, &sender_chain.1);
1582        Ok(())
1583    }
1584}
1585
1586fn get_or_create_chain_key(
1587    state: &mut SessionState,
1588    their_ephemeral: &PublicKey,
1589    remote_address: &ProtocolAddress,
1590    receiver_chain: Option<ReceiverChainState>,
1591) -> Result<(ChainKey, Option<DeferredSenderRatchet>)> {
1592    match receiver_chain {
1593        Some(ReceiverChainState::Open(chain)) => return Ok((chain, None)),
1594        Some(ReceiverChainState::Closed { .. }) => {
1595            return Err(SignalProtocolError::InvalidSessionStructure(
1596                "receiver chain is closed",
1597            ));
1598        }
1599        None => {}
1600    }
1601
1602    log::debug!("{remote_address} creating new chains.");
1603
1604    let root_key = state.root_key()?;
1605    let our_ephemeral = state.sender_ratchet_private_key()?;
1606    let receiver_chain = root_key.create_chain(their_ephemeral, &our_ephemeral)?;
1607
1608    let current_index = state.get_sender_chain_key()?.index();
1609    let deferred_ratchet = DeferredSenderRatchet {
1610        receiver_root_key: receiver_chain.0,
1611        previous_counter: current_index.saturating_sub(1),
1612    };
1613    state.add_receiver_chain(their_ephemeral, &receiver_chain.1);
1614
1615    Ok((receiver_chain.1, Some(deferred_ratchet)))
1616}
1617
1618fn get_or_create_message_key(
1619    state: &mut SessionState,
1620    their_ephemeral: &PublicKey,
1621    remote_address: &ProtocolAddress,
1622    original_message_type: CiphertextMessageType,
1623    chain_key: &ChainKey,
1624    counter: u32,
1625) -> Result<MessageKeyGenerator> {
1626    let chain_index = chain_key.index();
1627
1628    if chain_index > counter {
1629        return match state.get_message_keys(their_ephemeral, counter)? {
1630            Some(keys) => Ok(keys),
1631            None => Err(SignalProtocolError::DuplicatedMessage(chain_index, counter)),
1632        };
1633    }
1634
1635    assert!(chain_index <= counter);
1636
1637    let jump = (counter - chain_index) as usize;
1638
1639    // Trusted self-sessions get a wider (but no longer unbounded) ceiling; peer
1640    // sessions match WA Web's `signalFutureMessagesMax`.
1641    let limit = forward_jump_limit(state.session_with_self()?);
1642    if jump > limit {
1643        log::error!(
1644            "{remote_address} Exceeded future message limit: {limit}, index: {chain_index}, counter: {counter})"
1645        );
1646        return Err(SignalProtocolError::InvalidMessage(
1647            original_message_type,
1648            "message from too far into the future",
1649        ));
1650    }
1651
1652    let mut chain_key = *chain_key;
1653
1654    while chain_key.index() < counter {
1655        let (message_keys, next_chain) = chain_key.step_with_message_keys()?;
1656        state.set_message_keys(their_ephemeral, message_keys)?;
1657        chain_key = next_chain;
1658    }
1659
1660    let (result_message_keys, next_chain) = chain_key.step_with_message_keys()?;
1661    state.set_receiver_chain_key(their_ephemeral, &next_chain)?;
1662    Ok(result_message_keys)
1663}
1664
1665#[cfg(test)]
1666mod tests {
1667    use super::*;
1668    use crate::protocol::*;
1669    use async_trait::async_trait;
1670    use std::collections::HashMap;
1671    use std::future::Future;
1672    use std::num::NonZeroU64;
1673    use std::sync::Mutex as SyncMutex;
1674    use std::sync::atomic::{AtomicBool, Ordering};
1675    use std::task::{Context, Poll};
1676
1677    #[test]
1678    fn forward_jump_limit_matches_wa_web_signal_future_messages_max() {
1679        // Peer sessions cap at WA Web's `signalFutureMessagesMax` (2000); the
1680        // self ceiling stays wider but bounded (previously unbounded).
1681        assert_eq!(forward_jump_limit(false), 2_000);
1682        assert_eq!(forward_jump_limit(false), MAX_FORWARD_JUMPS);
1683        assert_eq!(forward_jump_limit(true), 25_000);
1684        assert_eq!(forward_jump_limit(true), consts::MAX_FORWARD_JUMPS_SELF);
1685        assert!(forward_jump_limit(true) > forward_jump_limit(false));
1686    }
1687
1688    // -- In-memory stores for test isolation --
1689
1690    struct MemSessionStore(HashMap<String, SessionRecord>);
1691    impl MemSessionStore {
1692        fn new() -> Self {
1693            Self(HashMap::new())
1694        }
1695    }
1696
1697    struct DestructiveSessionStore(SyncMutex<Option<SessionRecord>>);
1698
1699    impl DestructiveSessionStore {
1700        const CHECKOUT: SessionCheckoutKey = SessionCheckoutKey::new(0, NonZeroU64::MIN);
1701
1702        fn new(record: Option<SessionRecord>) -> Self {
1703            Self(SyncMutex::new(record))
1704        }
1705
1706        fn take(&self) -> Option<SessionRecord> {
1707            self.0.lock().expect("test store lock poisoned").take()
1708        }
1709
1710        fn replace(&self, record: SessionRecord) {
1711            *self.0.lock().expect("test store lock poisoned") = Some(record);
1712        }
1713    }
1714
1715    #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
1716    impl SessionStore for DestructiveSessionStore {
1717        async fn load_session(&self, _address: &ProtocolAddress) -> Result<Option<SessionRecord>> {
1718            Ok(self.0.lock().expect("test store lock poisoned").clone())
1719        }
1720
1721        fn try_load_session_for_update(
1722            &self,
1723            _address: &ProtocolAddress,
1724        ) -> Option<Result<(Option<SessionRecord>, Option<SessionCheckoutKey>)>> {
1725            Some(Ok((self.take(), Some(Self::CHECKOUT))))
1726        }
1727
1728        async fn has_session(&self, _address: &ProtocolAddress) -> Result<bool> {
1729            Ok(self.0.lock().expect("test store lock poisoned").is_some())
1730        }
1731
1732        async fn store_session(
1733            &mut self,
1734            _address: &ProtocolAddress,
1735            record: SessionRecord,
1736        ) -> Result<()> {
1737            self.replace(record);
1738            Ok(())
1739        }
1740
1741        fn try_store_session_from_checkout(
1742            &mut self,
1743            _address: &ProtocolAddress,
1744            record: SessionRecord,
1745            checkout: Option<SessionCheckoutKey>,
1746            _had_session: bool,
1747        ) -> SessionCheckoutStoreResult {
1748            if checkout != Some(Self::CHECKOUT)
1749                || self.0.lock().expect("test store lock poisoned").is_some()
1750            {
1751                return SessionCheckoutStoreResult::Rejected;
1752            }
1753            self.replace(record);
1754            SessionCheckoutStoreResult::Stored
1755        }
1756    }
1757    #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
1758    impl SessionStore for MemSessionStore {
1759        async fn load_session(&self, address: &ProtocolAddress) -> Result<Option<SessionRecord>> {
1760            Ok(self.0.get(address.as_str()).cloned())
1761        }
1762        async fn has_session(&self, address: &ProtocolAddress) -> Result<bool> {
1763            Ok(self.0.contains_key(address.as_str()))
1764        }
1765        async fn store_session(
1766            &mut self,
1767            address: &ProtocolAddress,
1768            record: SessionRecord,
1769        ) -> Result<()> {
1770            self.0.insert(address.as_str().to_string(), record);
1771            Ok(())
1772        }
1773    }
1774
1775    struct MemIdentityStore {
1776        pair: IdentityKeyPair,
1777        reg_id: u32,
1778        known: HashMap<String, IdentityKey>,
1779    }
1780
1781    #[derive(Clone, Copy)]
1782    enum PendingIdentityCall {
1783        Trust,
1784        Save,
1785    }
1786
1787    struct PendingIdentityStore {
1788        inner: MemIdentityStore,
1789        call: PendingIdentityCall,
1790        entered: AtomicBool,
1791    }
1792
1793    impl PendingIdentityStore {
1794        fn new(inner: MemIdentityStore, call: PendingIdentityCall) -> Self {
1795            Self {
1796                inner,
1797                call,
1798                entered: AtomicBool::new(false),
1799            }
1800        }
1801
1802        fn wait_forever<T>(&self) -> impl Future<Output = T> {
1803            self.entered.store(true, Ordering::Release);
1804            futures::future::pending()
1805        }
1806    }
1807
1808    #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
1809    impl IdentityKeyStore for PendingIdentityStore {
1810        async fn get_identity_key_pair(&self) -> Result<IdentityKeyPair> {
1811            self.inner.get_identity_key_pair().await
1812        }
1813
1814        async fn get_local_registration_id(&self) -> Result<u32> {
1815            self.inner.get_local_registration_id().await
1816        }
1817
1818        async fn save_identity(
1819            &mut self,
1820            address: &ProtocolAddress,
1821            identity: &IdentityKey,
1822        ) -> Result<IdentityChange> {
1823            if matches!(self.call, PendingIdentityCall::Save) {
1824                self.wait_forever().await
1825            } else {
1826                self.inner.save_identity(address, identity).await
1827            }
1828        }
1829
1830        async fn is_trusted_identity(
1831            &self,
1832            address: &ProtocolAddress,
1833            identity: &IdentityKey,
1834            direction: Direction,
1835        ) -> Result<bool> {
1836            if matches!(self.call, PendingIdentityCall::Trust) {
1837                self.wait_forever().await
1838            } else {
1839                self.inner
1840                    .is_trusted_identity(address, identity, direction)
1841                    .await
1842            }
1843        }
1844
1845        async fn get_identity(&self, address: &ProtocolAddress) -> Result<Option<IdentityKey>> {
1846            self.inner.get_identity(address).await
1847        }
1848    }
1849    impl MemIdentityStore {
1850        fn new(pair: IdentityKeyPair, reg_id: u32) -> Self {
1851            Self {
1852                pair,
1853                reg_id,
1854                known: HashMap::new(),
1855            }
1856        }
1857    }
1858    #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
1859    impl IdentityKeyStore for MemIdentityStore {
1860        async fn get_identity_key_pair(&self) -> Result<IdentityKeyPair> {
1861            Ok(self.pair.clone())
1862        }
1863        async fn get_local_registration_id(&self) -> Result<u32> {
1864            Ok(self.reg_id)
1865        }
1866        async fn save_identity(
1867            &mut self,
1868            address: &ProtocolAddress,
1869            identity: &IdentityKey,
1870        ) -> Result<IdentityChange> {
1871            let changed = self
1872                .known
1873                .get(address.as_str())
1874                .is_some_and(|k| k != identity);
1875            self.known.insert(address.as_str().to_string(), *identity);
1876            Ok(IdentityChange::from_changed(changed))
1877        }
1878        async fn is_trusted_identity(
1879            &self,
1880            _address: &ProtocolAddress,
1881            _identity: &IdentityKey,
1882            _direction: Direction,
1883        ) -> Result<bool> {
1884            Ok(true)
1885        }
1886        async fn get_identity(&self, address: &ProtocolAddress) -> Result<Option<IdentityKey>> {
1887            Ok(self.known.get(address.as_str()).copied())
1888        }
1889    }
1890
1891    struct MemPreKeyStore(HashMap<PreKeyId, PreKeyRecord>);
1892    impl MemPreKeyStore {
1893        fn new() -> Self {
1894            Self(HashMap::new())
1895        }
1896    }
1897    #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
1898    impl PreKeyStore for MemPreKeyStore {
1899        async fn get_pre_key(&self, id: PreKeyId) -> Result<PreKeyRecord> {
1900            self.0
1901                .get(&id)
1902                .cloned()
1903                .ok_or(SignalProtocolError::InvalidPreKeyId)
1904        }
1905        async fn save_pre_key(&mut self, id: PreKeyId, record: &PreKeyRecord) -> Result<()> {
1906            self.0.insert(id, record.clone());
1907            Ok(())
1908        }
1909        async fn remove_pre_key(&mut self, id: PreKeyId) -> Result<()> {
1910            self.0.remove(&id);
1911            Ok(())
1912        }
1913    }
1914
1915    struct MemSignedPreKeyStore(HashMap<SignedPreKeyId, SignedPreKeyRecord>);
1916    impl MemSignedPreKeyStore {
1917        fn new() -> Self {
1918            Self(HashMap::new())
1919        }
1920    }
1921    #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
1922    impl SignedPreKeyStore for MemSignedPreKeyStore {
1923        async fn get_signed_pre_key(&self, id: SignedPreKeyId) -> Result<SignedPreKeyRecord> {
1924            self.0
1925                .get(&id)
1926                .cloned()
1927                .ok_or(SignalProtocolError::InvalidSignedPreKeyId)
1928        }
1929        async fn save_signed_pre_key(
1930            &mut self,
1931            id: SignedPreKeyId,
1932            record: &SignedPreKeyRecord,
1933        ) -> Result<()> {
1934            self.0.insert(id, record.clone());
1935            Ok(())
1936        }
1937    }
1938
1939    struct TestPair {
1940        alice_addr: ProtocolAddress,
1941        alice_sessions: MemSessionStore,
1942        alice_identity: MemIdentityStore,
1943        bob_addr: ProtocolAddress,
1944        bob_sessions: MemSessionStore,
1945        bob_identity: MemIdentityStore,
1946        bob_prekeys: MemPreKeyStore,
1947        bob_signed: MemSignedPreKeyStore,
1948    }
1949
1950    fn setup_established_session() -> TestPair {
1951        let mut rng = rand::make_rng::<rand::rngs::StdRng>();
1952
1953        let alice_addr = ProtocolAddress::new("alice", 1.into());
1954        let alice_id = IdentityKeyPair::generate(&mut rng);
1955        let mut alice_sessions = MemSessionStore::new();
1956        let mut alice_identity = MemIdentityStore::new(alice_id, 1);
1957
1958        let bob_addr = ProtocolAddress::new("bob", 1.into());
1959        let bob_id = IdentityKeyPair::generate(&mut rng);
1960        let bob_identity_key = *bob_id.identity_key();
1961
1962        let prekey_id: PreKeyId = 1.into();
1963        let prekey_pair = KeyPair::generate(&mut rng);
1964        let mut bob_prekeys = MemPreKeyStore::new();
1965
1966        let signed_id: SignedPreKeyId = 1.into();
1967        let signed_pair = KeyPair::generate(&mut rng);
1968        let signed_sig = bob_id
1969            .private_key()
1970            .calculate_signature(&signed_pair.public_key.serialize(), &mut rng)
1971            .expect("signature");
1972
1973        let mut bob_sessions = MemSessionStore::new();
1974        let mut bob_identity = MemIdentityStore::new(bob_id, 2);
1975        let mut bob_signed = MemSignedPreKeyStore::new();
1976
1977        futures::executor::block_on(async {
1978            bob_prekeys
1979                .save_pre_key(prekey_id, &PreKeyRecord::new(prekey_id, &prekey_pair))
1980                .await
1981                .expect("save prekey");
1982            bob_signed
1983                .save_signed_pre_key(
1984                    signed_id,
1985                    &SignedPreKeyRecord::new(
1986                        signed_id,
1987                        Timestamp::from_epoch_millis(0),
1988                        &signed_pair,
1989                        &signed_sig,
1990                    ),
1991                )
1992                .await
1993                .expect("save signed prekey");
1994        });
1995
1996        let bundle = PreKeyBundle::new(
1997            2,
1998            1.into(),
1999            Some((prekey_id, prekey_pair.public_key)),
2000            signed_id,
2001            signed_pair.public_key,
2002            signed_sig.to_vec(),
2003            bob_identity_key,
2004        )
2005        .expect("valid bundle");
2006
2007        // Alice processes Bob's bundle → establishes session
2008        futures::executor::block_on(async {
2009            process_prekey_bundle(
2010                &bob_addr,
2011                &mut alice_sessions,
2012                &mut alice_identity,
2013                &bundle,
2014                &mut rng,
2015                UsePQRatchet::No,
2016            )
2017            .await
2018            .expect("process prekey bundle");
2019
2020            // Alice sends → Bob receives (completes handshake)
2021            let ct = message_encrypt(
2022                b"hello",
2023                &bob_addr,
2024                &mut alice_sessions,
2025                &mut alice_identity,
2026            )
2027            .await
2028            .expect("encrypt first message");
2029
2030            let ct_msg = CiphertextMessage::PreKeySignalMessage(
2031                PreKeySignalMessage::try_from(ct.serialize())
2032                    .expect("parse as PreKeySignalMessage"),
2033            );
2034            message_decrypt(
2035                &ct_msg,
2036                &alice_addr,
2037                &mut bob_sessions,
2038                &mut bob_identity,
2039                &mut bob_prekeys,
2040                &bob_signed,
2041                &mut rng,
2042                UsePQRatchet::No,
2043            )
2044            .await
2045            .expect("decrypt first message");
2046        });
2047
2048        TestPair {
2049            alice_addr,
2050            alice_sessions,
2051            alice_identity,
2052            bob_addr,
2053            bob_sessions,
2054            bob_identity,
2055            bob_prekeys,
2056            bob_signed,
2057        }
2058    }
2059
2060    /// Establishes a session, ratchets once, and leaves Bob's stored record
2061    /// with a receiver chain holding a skipped key for the returned `first`
2062    /// message (counter 0), decoupled into components for surgery.
2063    fn setup_skipped_key_scenario() -> (TestPair, SignalMessage, SessionRecordComponents) {
2064        let mut tp = setup_established_session();
2065        let mut rng = rand::make_rng::<rand::rngs::StdRng>();
2066
2067        let first = futures::executor::block_on(async {
2068            let reply = message_encrypt(
2069                b"ratchet reply",
2070                &tp.alice_addr,
2071                &mut tp.bob_sessions,
2072                &mut tp.bob_identity,
2073            )
2074            .await
2075            .expect("encrypt reply");
2076            let CiphertextMessage::SignalMessage(reply) = reply else {
2077                panic!("established responder sends a signal message");
2078            };
2079            message_decrypt_signal(
2080                &reply,
2081                &tp.bob_addr,
2082                &mut tp.alice_sessions,
2083                &mut tp.alice_identity,
2084                &mut rng,
2085            )
2086            .await
2087            .expect("decrypt reply");
2088
2089            let first = message_encrypt(
2090                b"first",
2091                &tp.bob_addr,
2092                &mut tp.alice_sessions,
2093                &mut tp.alice_identity,
2094            )
2095            .await
2096            .expect("encrypt first");
2097            let second = message_encrypt(
2098                b"second",
2099                &tp.bob_addr,
2100                &mut tp.alice_sessions,
2101                &mut tp.alice_identity,
2102            )
2103            .await
2104            .expect("encrypt second");
2105            let CiphertextMessage::SignalMessage(first) = first else {
2106                panic!("pending pre-key was acknowledged");
2107            };
2108            let CiphertextMessage::SignalMessage(second) = second else {
2109                panic!("pending pre-key was acknowledged");
2110            };
2111
2112            message_decrypt_signal(
2113                &second,
2114                &tp.alice_addr,
2115                &mut tp.bob_sessions,
2116                &mut tp.bob_identity,
2117                &mut rng,
2118            )
2119            .await
2120            .expect("decrypt out of order");
2121            first
2122        });
2123
2124        let stored = tp
2125            .bob_sessions
2126            .0
2127            .remove(tp.alice_addr.as_str())
2128            .expect("stored session");
2129        let components = stored.into_components().expect("components");
2130        (tp, first, components)
2131    }
2132
2133    fn receiver_chain_for<'a>(
2134        session: &'a mut SessionComponents,
2135        message: &SignalMessage,
2136    ) -> &'a mut SessionChainComponents {
2137        session
2138            .receiver_chains
2139            .iter_mut()
2140            .find(|chain| {
2141                chain.sender_ratchet_key.as_deref()
2142                    == Some(message.sender_ratchet_key().serialize().as_slice())
2143            })
2144            .expect("matching receiver chain")
2145    }
2146
2147    fn install_record(tp: &mut TestPair, components: SessionRecordComponents) {
2148        let rebuilt = SessionRecord::from_components(components).expect("rebuilt record");
2149        // Reload through the wire format so the state under test is proven to
2150        // survive persistence, not just the in-memory record.
2151        let reloaded = SessionRecord::deserialize(&rebuilt.serialize().expect("serialize"))
2152            .expect("reload persisted record");
2153        tp.bob_sessions
2154            .0
2155            .insert(tp.alice_addr.as_str().to_owned(), reloaded);
2156    }
2157
2158    #[test]
2159    fn closed_receiver_chain_searches_archives_for_a_persisted_skipped_key() {
2160        let (mut tp, first, mut components) = setup_skipped_key_scenario();
2161        let mut rng = rand::make_rng::<rand::rngs::StdRng>();
2162
2163        let current = components.current_session.as_mut().expect("current");
2164        let mut previous = current.clone();
2165        let receiver = receiver_chain_for(current, &first);
2166        receiver.chain_key.as_mut().expect("chain key").key = None;
2167        receiver.message_keys.clear();
2168        let previous_receiver = receiver_chain_for(&mut previous, &first);
2169        previous_receiver.chain_key.as_mut().expect("chain key").key = None;
2170        components.previous_sessions.push(previous);
2171        install_record(&mut tp, components);
2172
2173        futures::executor::block_on(async {
2174            let decrypted = message_decrypt_signal(
2175                &first,
2176                &tp.alice_addr,
2177                &mut tp.bob_sessions,
2178                &mut tp.bob_identity,
2179                &mut rng,
2180            )
2181            .await
2182            .expect("consume skipped key from archived closed chain");
2183            assert_eq!(decrypted.plaintext, b"first");
2184
2185            let replay = message_decrypt_signal(
2186                &first,
2187                &tp.alice_addr,
2188                &mut tp.bob_sessions,
2189                &mut tp.bob_identity,
2190                &mut rng,
2191            )
2192            .await
2193            .expect_err("consumed key remains a duplicate after every session is searched");
2194            assert!(matches!(
2195                replay,
2196                SignalProtocolError::DuplicatedMessage(_, _)
2197            ));
2198        });
2199    }
2200
2201    #[test]
2202    fn open_receiver_chain_searches_archives_for_a_persisted_skipped_key() {
2203        let (mut tp, first, mut components) = setup_skipped_key_scenario();
2204        let mut rng = rand::make_rng::<rand::rngs::StdRng>();
2205
2206        let current = components.current_session.as_mut().expect("current");
2207        let previous = current.clone();
2208        // Both chains stay open; only the newer state lost the skipped key.
2209        receiver_chain_for(current, &first).message_keys.clear();
2210        components.previous_sessions.push(previous);
2211        install_record(&mut tp, components);
2212
2213        futures::executor::block_on(async {
2214            let decrypted = message_decrypt_signal(
2215                &first,
2216                &tp.alice_addr,
2217                &mut tp.bob_sessions,
2218                &mut tp.bob_identity,
2219                &mut rng,
2220            )
2221            .await
2222            .expect("consume skipped key from archived open chain");
2223            assert_eq!(decrypted.plaintext, b"first");
2224
2225            let replay = message_decrypt_signal(
2226                &first,
2227                &tp.alice_addr,
2228                &mut tp.bob_sessions,
2229                &mut tp.bob_identity,
2230                &mut rng,
2231            )
2232            .await
2233            .expect_err("consumed key remains a duplicate after every session is searched");
2234            assert!(matches!(
2235                replay,
2236                SignalProtocolError::DuplicatedMessage(_, _)
2237            ));
2238        });
2239    }
2240
2241    #[test]
2242    fn a_recognized_duplicate_outranks_candidate_session_mac_failures() {
2243        let (mut tp, first, mut components) = setup_skipped_key_scenario();
2244        let mut rng = rand::make_rng::<rand::rngs::StdRng>();
2245
2246        let current = components.current_session.as_mut().expect("current");
2247        let mut tampered = current.clone();
2248        let receiver = receiver_chain_for(current, &first);
2249        receiver.chain_key.as_mut().expect("chain key").key = None;
2250        receiver.message_keys.clear();
2251        // An archived sibling sharing the ratchet key derives different keys
2252        // for the same counter and fails its MAC; the recognized duplicate
2253        // must still win the classification, or a replay would trigger a
2254        // retry receipt for an already-processed message.
2255        let tampered_receiver = receiver_chain_for(&mut tampered, &first);
2256        let tampered_chain_key = tampered_receiver.chain_key.as_mut().expect("chain key");
2257        tampered_chain_key.index = Some(0);
2258        tampered_chain_key.key = Some(vec![0x42; 32]);
2259        tampered_receiver.message_keys.clear();
2260        components.previous_sessions.push(tampered);
2261        install_record(&mut tp, components);
2262
2263        futures::executor::block_on(async {
2264            let outcome = message_decrypt_signal(
2265                &first,
2266                &tp.alice_addr,
2267                &mut tp.bob_sessions,
2268                &mut tp.bob_identity,
2269                &mut rng,
2270            )
2271            .await
2272            .expect_err("closed chain without its key classifies as a duplicate");
2273            assert!(matches!(
2274                outcome,
2275                SignalProtocolError::DuplicatedMessage(_, _)
2276            ));
2277        });
2278    }
2279
2280    #[test]
2281    fn owned_prekey_decrypt_consumes_the_envelope_on_success() {
2282        const PLAINTEXT_BYTES: usize = 64 * 1024;
2283
2284        let mut tp = setup_established_session();
2285        let mut rng = rand::make_rng::<rand::rngs::StdRng>();
2286        let plaintext = vec![0x7a; PLAINTEXT_BYTES];
2287        let ciphertext = futures::executor::block_on(message_encrypt(
2288            &plaintext,
2289            &tp.bob_addr,
2290            &mut tp.alice_sessions,
2291            &mut tp.alice_identity,
2292        ))
2293        .expect("encrypt owned prekey test message");
2294        assert!(matches!(
2295            &ciphertext,
2296            CiphertextMessage::PreKeySignalMessage(_)
2297        ));
2298        let mut ciphertext = OwnedCiphertextMessage::from(ciphertext);
2299
2300        let decrypted = futures::executor::block_on(message_decrypt_owned(
2301            &mut ciphertext,
2302            &tp.alice_addr,
2303            &mut tp.bob_sessions,
2304            &mut tp.bob_identity,
2305            &mut tp.bob_prekeys,
2306            &tp.bob_signed,
2307            &mut rng,
2308            UsePQRatchet::No,
2309        ))
2310        .expect("decrypt caller-owned prekey message");
2311
2312        assert_eq!(decrypted.plaintext, plaintext);
2313        assert!(!ciphertext.is_available());
2314    }
2315
2316    #[test]
2317    fn owned_prekey_bad_mac_preserves_the_envelope_for_retry() {
2318        let mut tp = setup_established_session();
2319        let mut rng = rand::make_rng::<rand::rngs::StdRng>();
2320        let ciphertext = futures::executor::block_on(message_encrypt(
2321            b"authenticated retry",
2322            &tp.bob_addr,
2323            &mut tp.alice_sessions,
2324            &mut tp.alice_identity,
2325        ))
2326        .expect("encrypt owned retry test message");
2327        let CiphertextMessage::PreKeySignalMessage(pre_key) = ciphertext else {
2328            panic!("pending session must emit a PreKey envelope");
2329        };
2330        let mut nested_wire = pre_key.message().serialized().to_vec();
2331        let mac_byte = nested_wire
2332            .len()
2333            .checked_sub(4)
2334            .expect("test Signal envelope contains a MAC");
2335        nested_wire[mac_byte] ^= 0xff;
2336        let nested = SignalMessage::try_from(nested_wire.as_slice())
2337            .expect("corrupted MAC keeps the nested envelope parseable");
2338        let corrupted = PreKeySignalMessage::new(
2339            pre_key.message_version(),
2340            pre_key.registration_id(),
2341            pre_key.pre_key_id(),
2342            pre_key.signed_pre_key_id(),
2343            *pre_key.base_key(),
2344            *pre_key.identity_key(),
2345            nested,
2346        )
2347        .expect("rebuild test PreKey envelope");
2348        let mut corrupted =
2349            OwnedCiphertextMessage::from(CiphertextMessage::PreKeySignalMessage(corrupted));
2350
2351        let error = futures::executor::block_on(message_decrypt_owned(
2352            &mut corrupted,
2353            &tp.alice_addr,
2354            &mut tp.bob_sessions,
2355            &mut tp.bob_identity,
2356            &mut tp.bob_prekeys,
2357            &tp.bob_signed,
2358            &mut rng,
2359            UsePQRatchet::No,
2360        ))
2361        .expect_err("a corrupted nested MAC must fail authentication");
2362
2363        assert!(matches!(error, SignalProtocolError::BadMac(_)));
2364        assert!(corrupted.is_available());
2365    }
2366
2367    #[test]
2368    fn cancelled_signal_decrypt_restores_the_ratchet_for_retry() {
2369        let mut tp = setup_established_session();
2370        let mut rng = rand::make_rng::<rand::rngs::StdRng>();
2371        let ciphertext = futures::executor::block_on(async {
2372            let reply = message_encrypt(
2373                b"ack",
2374                &tp.alice_addr,
2375                &mut tp.bob_sessions,
2376                &mut tp.bob_identity,
2377            )
2378            .await
2379            .expect("encrypt reply");
2380            let CiphertextMessage::SignalMessage(reply) = reply else {
2381                panic!("established reply must be a SignalMessage");
2382            };
2383            message_decrypt_signal(
2384                &reply,
2385                &tp.bob_addr,
2386                &mut tp.alice_sessions,
2387                &mut tp.alice_identity,
2388                &mut rng,
2389            )
2390            .await
2391            .expect("decrypt reply");
2392
2393            let message = message_encrypt(
2394                b"retry-safe",
2395                &tp.bob_addr,
2396                &mut tp.alice_sessions,
2397                &mut tp.alice_identity,
2398            )
2399            .await
2400            .expect("encrypt test message");
2401            let CiphertextMessage::SignalMessage(message) = message else {
2402                panic!("acknowledged session must emit a SignalMessage");
2403            };
2404            message
2405        });
2406
2407        let record = tp
2408            .bob_sessions
2409            .0
2410            .remove(tp.alice_addr.as_str())
2411            .expect("Bob session");
2412        let before = record.serialize().expect("serialize baseline");
2413        let mut sessions = DestructiveSessionStore::new(Some(record));
2414        let mut identity = PendingIdentityStore::new(tp.bob_identity, PendingIdentityCall::Trust);
2415
2416        let mut decrypt = Box::pin(message_decrypt_signal(
2417            &ciphertext,
2418            &tp.alice_addr,
2419            &mut sessions,
2420            &mut identity,
2421            &mut rng,
2422        ));
2423        let waker = futures::task::noop_waker();
2424        let mut context = Context::from_waker(&waker);
2425        assert!(matches!(decrypt.as_mut().poll(&mut context), Poll::Pending));
2426        drop(decrypt);
2427        assert!(identity.entered.load(Ordering::Acquire));
2428
2429        let restored = sessions.take().expect("cancelled checkout restored");
2430        assert_eq!(restored.serialize().expect("serialize restored"), before);
2431        sessions.replace(restored);
2432        let retried = futures::executor::block_on(message_decrypt_signal(
2433            &ciphertext,
2434            &tp.alice_addr,
2435            &mut sessions,
2436            &mut identity.inner,
2437            &mut rng,
2438        ))
2439        .expect("redelivery must decrypt");
2440        assert_eq!(retried.plaintext, b"retry-safe");
2441    }
2442
2443    #[test]
2444    fn cancelled_fresh_prekey_decrypt_discards_the_promoted_session() {
2445        let mut tp = setup_established_session();
2446        let mut rng = rand::make_rng::<rand::rngs::StdRng>();
2447        let ciphertext = futures::executor::block_on(message_encrypt(
2448            b"fresh-retry",
2449            &tp.bob_addr,
2450            &mut tp.alice_sessions,
2451            &mut tp.alice_identity,
2452        ))
2453        .expect("encrypt prekey test message");
2454        let CiphertextMessage::PreKeySignalMessage(ciphertext) = ciphertext else {
2455            panic!("unacknowledged session must emit a PreKeySignalMessage");
2456        };
2457
2458        let mut sessions = DestructiveSessionStore::new(None);
2459        let mut identity = PendingIdentityStore::new(tp.bob_identity, PendingIdentityCall::Save);
2460        let mut decrypt = Box::pin(message_decrypt_prekey(
2461            &ciphertext,
2462            &tp.alice_addr,
2463            &mut sessions,
2464            &mut identity,
2465            &mut tp.bob_prekeys,
2466            &tp.bob_signed,
2467            &mut rng,
2468            UsePQRatchet::No,
2469        ));
2470        let waker = futures::task::noop_waker();
2471        let mut context = Context::from_waker(&waker);
2472        assert!(matches!(decrypt.as_mut().poll(&mut context), Poll::Pending));
2473        drop(decrypt);
2474        assert!(identity.entered.load(Ordering::Acquire));
2475        assert!(sessions.take().is_none());
2476
2477        let retried = futures::executor::block_on(message_decrypt_prekey(
2478            &ciphertext,
2479            &tp.alice_addr,
2480            &mut sessions,
2481            &mut identity.inner,
2482            &mut tp.bob_prekeys,
2483            &tp.bob_signed,
2484            &mut rng,
2485            UsePQRatchet::No,
2486        ))
2487        .expect("redelivery must establish the session");
2488        assert_eq!(retried.plaintext, b"fresh-retry");
2489    }
2490
2491    /// Builds Bob's prekey stores plus a self-signed `PreKeyBundle`, without
2492    /// establishing any session. Each call uses a fresh identity, so two bundles
2493    /// for the same address model a peer reinstall.
2494    #[allow(clippy::type_complexity)]
2495    fn fresh_bob(
2496        rng: &mut rand::rngs::StdRng,
2497    ) -> (
2498        ProtocolAddress,
2499        MemSessionStore,
2500        MemIdentityStore,
2501        MemPreKeyStore,
2502        MemSignedPreKeyStore,
2503        PreKeyBundle,
2504    ) {
2505        let bob_addr = ProtocolAddress::new("bob", 1.into());
2506        let bob_id = IdentityKeyPair::generate(rng);
2507        let bob_identity_key = *bob_id.identity_key();
2508
2509        let prekey_id: PreKeyId = 1.into();
2510        let prekey_pair = KeyPair::generate(rng);
2511        let signed_id: SignedPreKeyId = 1.into();
2512        let signed_pair = KeyPair::generate(rng);
2513        let signed_sig = bob_id
2514            .private_key()
2515            .calculate_signature(&signed_pair.public_key.serialize(), rng)
2516            .expect("signature");
2517
2518        let mut bob_prekeys = MemPreKeyStore::new();
2519        let mut bob_signed = MemSignedPreKeyStore::new();
2520        futures::executor::block_on(async {
2521            bob_prekeys
2522                .save_pre_key(prekey_id, &PreKeyRecord::new(prekey_id, &prekey_pair))
2523                .await
2524                .expect("save prekey");
2525            bob_signed
2526                .save_signed_pre_key(
2527                    signed_id,
2528                    &SignedPreKeyRecord::new(
2529                        signed_id,
2530                        Timestamp::from_epoch_millis(0),
2531                        &signed_pair,
2532                        &signed_sig,
2533                    ),
2534                )
2535                .await
2536                .expect("save signed prekey");
2537        });
2538
2539        let bundle = PreKeyBundle::new(
2540            2,
2541            1.into(),
2542            Some((prekey_id, prekey_pair.public_key)),
2543            signed_id,
2544            signed_pair.public_key,
2545            signed_sig.to_vec(),
2546            bob_identity_key,
2547        )
2548        .expect("valid bundle");
2549
2550        (
2551            bob_addr,
2552            MemSessionStore::new(),
2553            MemIdentityStore::new(bob_id, 2),
2554            bob_prekeys,
2555            bob_signed,
2556            bundle,
2557        )
2558    }
2559
2560    /// `process_prekey_bundle` must report `NewOrUnchanged` on first contact and
2561    /// `ReplacedExisting` when a later bundle carries a different identity for the
2562    /// same address (peer reinstall). This is the signal the high-level client
2563    /// threads up to mirror WA Web `saveIdentity` -> `handleNewIdentity`.
2564    #[test]
2565    fn process_prekey_bundle_reports_identity_change() {
2566        let mut rng = rand::make_rng::<rand::rngs::StdRng>();
2567        let (bob_addr, _bs, _bi, _bp, _bsp, bundle1) = fresh_bob(&mut rng);
2568        let (_bob_addr2, _bs2, _bi2, _bp2, _bsp2, bundle2) = fresh_bob(&mut rng);
2569
2570        let alice_id = IdentityKeyPair::generate(&mut rng);
2571        let mut alice_sessions = MemSessionStore::new();
2572        let mut alice_identity = MemIdentityStore::new(alice_id, 1);
2573
2574        futures::executor::block_on(async {
2575            let first = process_prekey_bundle(
2576                &bob_addr,
2577                &mut alice_sessions,
2578                &mut alice_identity,
2579                &bundle1,
2580                &mut rng,
2581                UsePQRatchet::No,
2582            )
2583            .await
2584            .expect("first bundle");
2585            assert_eq!(first, IdentityChange::NewOrUnchanged, "first contact");
2586
2587            let second = process_prekey_bundle(
2588                &bob_addr,
2589                &mut alice_sessions,
2590                &mut alice_identity,
2591                &bundle2,
2592                &mut rng,
2593                UsePQRatchet::No,
2594            )
2595            .await
2596            .expect("second bundle");
2597            assert_eq!(
2598                second,
2599                IdentityChange::ReplacedExisting,
2600                "different identity for same address"
2601            );
2602        });
2603    }
2604
2605    /// `message_decrypt` of a pkmsg reports `NewOrUnchanged` on first contact and
2606    /// `ReplacedExisting` when the sender's stored identity differs (reinstall),
2607    /// while still returning the correct plaintext.
2608    #[test]
2609    fn decrypt_prekey_reports_identity_change() {
2610        let mut rng = rand::make_rng::<rand::rngs::StdRng>();
2611
2612        for (preseed_stale, expected) in [
2613            (false, IdentityChange::NewOrUnchanged),
2614            (true, IdentityChange::ReplacedExisting),
2615        ] {
2616            let alice_addr = ProtocolAddress::new("alice", 1.into());
2617            let alice_id = IdentityKeyPair::generate(&mut rng);
2618            let mut alice_sessions = MemSessionStore::new();
2619            let mut alice_identity = MemIdentityStore::new(alice_id, 1);
2620
2621            let (bob_addr, mut bob_sessions, mut bob_identity, mut bob_prekeys, bob_signed, bundle) =
2622                fresh_bob(&mut rng);
2623
2624            futures::executor::block_on(async {
2625                if preseed_stale {
2626                    // Bob already knows a different identity for Alice → reinstall.
2627                    let stale = *IdentityKeyPair::generate(&mut rng).identity_key();
2628                    bob_identity
2629                        .save_identity(&alice_addr, &stale)
2630                        .await
2631                        .expect("seed stale identity");
2632                }
2633
2634                process_prekey_bundle(
2635                    &bob_addr,
2636                    &mut alice_sessions,
2637                    &mut alice_identity,
2638                    &bundle,
2639                    &mut rng,
2640                    UsePQRatchet::No,
2641                )
2642                .await
2643                .expect("process bundle");
2644
2645                let ct =
2646                    message_encrypt(b"hi", &bob_addr, &mut alice_sessions, &mut alice_identity)
2647                        .await
2648                        .expect("encrypt");
2649                let pkmsg = CiphertextMessage::PreKeySignalMessage(
2650                    PreKeySignalMessage::try_from(ct.serialize()).expect("parse pkmsg"),
2651                );
2652
2653                let res = message_decrypt(
2654                    &pkmsg,
2655                    &alice_addr,
2656                    &mut bob_sessions,
2657                    &mut bob_identity,
2658                    &mut bob_prekeys,
2659                    &bob_signed,
2660                    &mut rng,
2661                    UsePQRatchet::No,
2662                )
2663                .await
2664                .expect("decrypt pkmsg");
2665
2666                assert_eq!(res.plaintext, b"hi".to_vec());
2667                assert_eq!(res.identity_change, expected);
2668            });
2669        }
2670    }
2671
2672    /// `process_prekey` must signal session reuse when a pkmsg matches an
2673    /// already-established session. The caller relies on this to avoid treating
2674    /// a duplicate/out-of-order pkmsg's (possibly stale) identity as a fresh
2675    /// rotation and firing a spurious local identity-change reaction.
2676    #[test]
2677    fn process_prekey_signals_reuse_for_established_session() {
2678        let mut rng = rand::make_rng::<rand::rngs::StdRng>();
2679        let alice_addr = ProtocolAddress::new("alice", 1.into());
2680        let alice_id = IdentityKeyPair::generate(&mut rng);
2681        let mut alice_sessions = MemSessionStore::new();
2682        let mut alice_identity = MemIdentityStore::new(alice_id, 1);
2683
2684        let (bob_addr, _bs, bob_identity, bob_prekeys, bob_signed, bundle) = fresh_bob(&mut rng);
2685
2686        futures::executor::block_on(async {
2687            process_prekey_bundle(
2688                &bob_addr,
2689                &mut alice_sessions,
2690                &mut alice_identity,
2691                &bundle,
2692                &mut rng,
2693                UsePQRatchet::No,
2694            )
2695            .await
2696            .expect("process bundle");
2697            let ct = message_encrypt(b"hi", &bob_addr, &mut alice_sessions, &mut alice_identity)
2698                .await
2699                .expect("encrypt");
2700            let pkmsg = PreKeySignalMessage::try_from(ct.serialize()).expect("parse pkmsg");
2701
2702            let mut record = SessionRecord::new_fresh();
2703            let (_used, _save, reused_first) = process_prekey(
2704                &pkmsg,
2705                &alice_addr,
2706                &mut record,
2707                &bob_identity,
2708                &bob_prekeys,
2709                &bob_signed,
2710                UsePQRatchet::No,
2711            )
2712            .await
2713            .expect("first process_prekey");
2714            assert!(!reused_first, "first pkmsg establishes a new session");
2715
2716            let (_used2, _save2, reused_again) = process_prekey(
2717                &pkmsg,
2718                &alice_addr,
2719                &mut record,
2720                &bob_identity,
2721                &bob_prekeys,
2722                &bob_signed,
2723                UsePQRatchet::No,
2724            )
2725            .await
2726            .expect("second process_prekey");
2727            assert!(
2728                reused_again,
2729                "re-processing the same pkmsg must signal session reuse"
2730            );
2731        });
2732    }
2733
2734    /// P0: MAC verification failure must return `BadMac`, not `InvalidMessage`.
2735    ///
2736    /// Establishes a full Alice↔Bob session with a complete round-trip, then
2737    /// encrypts a Whisper message, corrupts the MAC, and verifies that decryption
2738    /// produces `BadMac` (not `InvalidMessage`).
2739    #[test]
2740    fn decrypt_corrupted_mac_returns_bad_mac() {
2741        let mut tp = setup_established_session();
2742        let mut rng = rand::make_rng::<rand::rngs::StdRng>();
2743
2744        futures::executor::block_on(async {
2745            // Step 1: Bob replies → creates Bob's sending chain
2746            let bob_reply = message_encrypt(
2747                b"ack",
2748                &tp.alice_addr,
2749                &mut tp.bob_sessions,
2750                &mut tp.bob_identity,
2751            )
2752            .await
2753            .expect("Bob encrypt reply");
2754
2755            // Step 2: Alice decrypts Bob's reply.
2756            // Bob's reply is a SignalMessage (Bob has no pending prekey).
2757            let bob_msg = SignalMessage::try_from(bob_reply.serialize())
2758                .expect("Bob's reply should be a SignalMessage");
2759            message_decrypt_signal(
2760                &bob_msg,
2761                &tp.bob_addr,
2762                &mut tp.alice_sessions,
2763                &mut tp.alice_identity,
2764                &mut rng,
2765            )
2766            .await
2767            .expect("Alice should decrypt Bob's reply");
2768
2769            // Step 3: Alice sends a second message. After receiving Bob's reply,
2770            // Alice's pending prekey is cleared, so this is a plain SignalMessage.
2771            let ct = message_encrypt(
2772                b"secret payload",
2773                &tp.bob_addr,
2774                &mut tp.alice_sessions,
2775                &mut tp.alice_identity,
2776            )
2777            .await
2778            .expect("Alice encrypt second message");
2779
2780            // Verify it's a SignalMessage, not PreKey
2781            assert!(
2782                matches!(ct, CiphertextMessage::SignalMessage(_)),
2783                "Expected SignalMessage after full round-trip, got {:?}",
2784                ct.message_type()
2785            );
2786
2787            // Step 4: Corrupt the MAC (last 8 bytes) without disturbing protobuf
2788            let raw = ct.serialize();
2789            let mut corrupted_bytes = raw.to_vec();
2790            let mac_offset = corrupted_bytes.len() - 4;
2791            corrupted_bytes[mac_offset] ^= 0xFF;
2792
2793            let corrupted = SignalMessage::try_from(corrupted_bytes.as_slice())
2794                .expect("protobuf is intact, only MAC region is modified");
2795
2796            // This message carries a fresh inbound ratchet key. Authentication
2797            // failure must not generate the deferred outbound ratchet or consume
2798            // caller entropy.
2799            const DECRYPT_RNG_SEED: u64 = 0xD3FE_22ED;
2800            let mut decrypt_rng =
2801                <rand::rngs::StdRng as rand::SeedableRng>::seed_from_u64(DECRYPT_RNG_SEED);
2802            let mut untouched_rng =
2803                <rand::rngs::StdRng as rand::SeedableRng>::seed_from_u64(DECRYPT_RNG_SEED);
2804
2805            // Bob tries to decrypt the corrupted message
2806            let err = message_decrypt_signal(
2807                &corrupted,
2808                &tp.alice_addr,
2809                &mut tp.bob_sessions,
2810                &mut tp.bob_identity,
2811                &mut decrypt_rng,
2812            )
2813            .await
2814            .expect_err("decryption should fail due to corrupted MAC");
2815
2816            // Must be BadMac (error code 7), not InvalidMessage (error code 4)
2817            assert!(
2818                matches!(err, SignalProtocolError::BadMac(_)),
2819                "expected BadMac, got: {err}"
2820            );
2821            use rand::RngExt as _;
2822            assert_eq!(decrypt_rng.random::<u64>(), untouched_rng.random::<u64>());
2823        });
2824    }
2825
2826    /// Fast-path rollback: a MAC failure on an in-order message (the path that
2827    /// now rolls back via the single saved chain key instead of the full
2828    /// `decrypt_snapshot` clone) must leave the receiver chain untouched, so the
2829    /// next legitimate message on the same chain still decrypts. A regressed
2830    /// rollback would leave the chain advanced and surface `DuplicatedMessage`.
2831    #[test]
2832    fn inorder_macfail_rolls_back_chain_and_recovers() {
2833        let mut tp = setup_established_session();
2834        let mut rng = rand::make_rng::<rand::rngs::StdRng>();
2835
2836        futures::executor::block_on(async {
2837            // Bob replies so Alice clears her pending prekey and sends plain
2838            // SignalMessages from here on.
2839            let bob_reply = message_encrypt(
2840                b"ack",
2841                &tp.alice_addr,
2842                &mut tp.bob_sessions,
2843                &mut tp.bob_identity,
2844            )
2845            .await
2846            .expect("Bob encrypt reply");
2847            let bob_msg =
2848                SignalMessage::try_from(bob_reply.serialize()).expect("reply is a SignalMessage");
2849            message_decrypt_signal(
2850                &bob_msg,
2851                &tp.bob_addr,
2852                &mut tp.alice_sessions,
2853                &mut tp.alice_identity,
2854                &mut rng,
2855            )
2856            .await
2857            .expect("Alice decrypts reply");
2858
2859            // Alice sends two consecutive messages on the same sending chain.
2860            let m1 = message_encrypt(
2861                b"first",
2862                &tp.bob_addr,
2863                &mut tp.alice_sessions,
2864                &mut tp.alice_identity,
2865            )
2866            .await
2867            .expect("encrypt m1");
2868            let m2 = message_encrypt(
2869                b"second",
2870                &tp.bob_addr,
2871                &mut tp.alice_sessions,
2872                &mut tp.alice_identity,
2873            )
2874            .await
2875            .expect("encrypt m2");
2876            assert!(
2877                matches!(m2, CiphertextMessage::SignalMessage(_)),
2878                "m2 should be a SignalMessage"
2879            );
2880
2881            // Bob decrypts m1 → advances the receiver chain, so m2 is an in-order
2882            // message on an existing chain (the fast path).
2883            let m1_sig = SignalMessage::try_from(m1.serialize()).expect("m1 SignalMessage");
2884            let p1 = message_decrypt_signal(
2885                &m1_sig,
2886                &tp.alice_addr,
2887                &mut tp.bob_sessions,
2888                &mut tp.bob_identity,
2889                &mut rng,
2890            )
2891            .await
2892            .expect("Bob decrypts m1");
2893            assert_eq!(p1.plaintext, b"first");
2894
2895            // Corrupt m2's MAC and decrypt → BadMac, exercising the fast-path rollback.
2896            let raw = m2.serialize();
2897            let mut corrupted = raw.to_vec();
2898            let off = corrupted.len() - 4;
2899            corrupted[off] ^= 0xFF;
2900            let corrupted_msg =
2901                SignalMessage::try_from(corrupted.as_slice()).expect("protobuf intact");
2902            let err = message_decrypt_signal(
2903                &corrupted_msg,
2904                &tp.alice_addr,
2905                &mut tp.bob_sessions,
2906                &mut tp.bob_identity,
2907                &mut rng,
2908            )
2909            .await
2910            .expect_err("corrupted m2 must fail");
2911            assert!(
2912                matches!(err, SignalProtocolError::BadMac(_)),
2913                "expected BadMac, got: {err}"
2914            );
2915
2916            // The legit m2 must still decrypt — proving the chain key was rolled back.
2917            let m2_sig = SignalMessage::try_from(m2.serialize()).expect("m2 SignalMessage");
2918            let p2 = message_decrypt_signal(
2919                &m2_sig,
2920                &tp.alice_addr,
2921                &mut tp.bob_sessions,
2922                &mut tp.bob_identity,
2923                &mut rng,
2924            )
2925            .await
2926            .expect("Bob decrypts legit m2 after rollback");
2927            assert_eq!(p2.plaintext, b"second");
2928        });
2929    }
2930
2931    /// Reusing the encrypt buffer must not pin an oversized allocation: a
2932    /// plaintext larger than MAX_CAPACITY grows the thread-local buffer, which
2933    /// has to be released after the message is built (the old take+realloc path
2934    /// did this implicitly).
2935    #[test]
2936    fn encrypt_releases_oversized_buffer() {
2937        let mut tp = setup_established_session();
2938        futures::executor::block_on(async {
2939            let big = vec![7u8; 32 * 1024]; // ciphertext far exceeds MAX_CAPACITY (16 KiB)
2940            message_encrypt(
2941                &big,
2942                &tp.bob_addr,
2943                &mut tp.alice_sessions,
2944                &mut tp.alice_identity,
2945            )
2946            .await
2947            .expect("encrypt large message");
2948
2949            // The fix's contract is "buffer must not exceed MAX_CAPACITY after a
2950            // send". Asserting against MAX (not INITIAL) keeps the test robust:
2951            // Vec::with_capacity only guarantees a lower bound, so an allocator
2952            // that rounds up must not flake this. A 32 KiB plaintext without the
2953            // release leaves the buffer well above MAX (~32 KiB).
2954            let cap = encryption_buffer_capacity();
2955            assert!(
2956                cap <= EncryptionBuffer::MAX_CAPACITY,
2957                "encrypt buffer should be released after an oversized send, got capacity {cap}"
2958            );
2959        });
2960    }
2961
2962    /// P1: A session record that exists but has no usable state (no current session,
2963    /// 0 previous sessions) must return `SessionNotFound`, not `InvalidMessage`.
2964    #[test]
2965    fn decrypt_with_empty_session_returns_session_not_found() {
2966        let mut rng = rand::make_rng::<rand::rngs::StdRng>();
2967
2968        let alice_addr = ProtocolAddress::new("alice", 1.into());
2969        let alice_id = IdentityKeyPair::generate(&mut rng);
2970        let bob_id = IdentityKeyPair::generate(&mut rng);
2971        let alice_identity_key = *alice_id.identity_key();
2972        let bob_identity_key = *bob_id.identity_key();
2973
2974        // Store an empty (degenerate) session record for Alice
2975        let mut bob_sessions = MemSessionStore::new();
2976        let mut bob_identity = MemIdentityStore::new(bob_id, 2);
2977
2978        futures::executor::block_on(async {
2979            // Store empty session — record exists but has no ratchet state
2980            bob_sessions
2981                .store_session(&alice_addr, SessionRecord::new_fresh())
2982                .await
2983                .expect("store empty session");
2984
2985            // Verify the session "exists" in the store
2986            let loaded = bob_sessions
2987                .load_session(&alice_addr)
2988                .await
2989                .expect("load session");
2990            assert!(loaded.is_some(), "record should exist");
2991
2992            // Craft a plausible SignalMessage (it won't actually decrypt, but we
2993            // need it to reach the session-check code path)
2994            let ratchet_key = KeyPair::generate(&mut rng).public_key;
2995            let msg = SignalMessage::new(
2996                4,
2997                &[0u8; 32],
2998                ratchet_key,
2999                0,
3000                0,
3001                b"dummy",
3002                &alice_identity_key,
3003                &bob_identity_key,
3004            )
3005            .expect("craft SignalMessage");
3006
3007            let err = message_decrypt_signal(
3008                &msg,
3009                &alice_addr,
3010                &mut bob_sessions,
3011                &mut bob_identity,
3012                &mut rng,
3013            )
3014            .await
3015            .expect_err("should fail on empty session");
3016
3017            assert!(
3018                matches!(err, SignalProtocolError::SessionNotFound(_)),
3019                "expected SessionNotFound for degenerate session, got: {err}"
3020            );
3021        });
3022    }
3023}