Skip to main content

libsignal_service/
cipher.rs

1use std::{convert::TryFrom, fmt, time::SystemTime};
2
3use aes::cipher::block_padding::{Iso7816, RawPadding};
4use base64::prelude::*;
5use libsignal_core::ServiceIdKind;
6use libsignal_protocol::{
7    group_decrypt, message_decrypt_prekey, message_decrypt_signal,
8    message_encrypt, process_sender_key_distribution_message,
9    sealed_sender_decrypt_to_usmc, sealed_sender_encrypt,
10    CiphertextMessageType, DeviceId, IdentityKeyStore, KyberPreKeyStore,
11    PlaintextContent, PreKeySignalMessage, PreKeyStore, ProtocolAddress,
12    ProtocolStore, PublicKey, SealedSenderDecryptionResult, SenderCertificate,
13    SenderKeyDistributionMessage, SenderKeyStore, ServiceId, SessionStore,
14    SignalMessage, SignalProtocolError, SignedPreKeyStore, Timestamp,
15    UnidentifiedSenderMessageContent,
16};
17use prost::Message;
18use rand::{rng, CryptoRng, Rng};
19use uuid::Uuid;
20
21use crate::{
22    content::{Content, Metadata},
23    envelope::Envelope,
24    push_service::ServiceError,
25    sender::OutgoingPushMessage,
26    session_store::SessionStoreExt,
27    utils::BASE64_RELAXED,
28    ServiceIdExt,
29};
30
31/// Decrypts incoming messages and encrypts outgoing messages.
32///
33/// Equivalent of SignalServiceCipher in Java.
34#[derive(Clone)]
35pub struct ServiceCipher<S> {
36    protocol_store: S,
37    trust_roots: Vec<PublicKey>,
38    local_address: ProtocolAddress,
39}
40
41impl<S> fmt::Debug for ServiceCipher<S> {
42    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43        f.debug_struct("ServiceCipher")
44            .field("protocol_store", &"...")
45            .field("trust_root", &"...")
46            .field("local_address", &self.local_address)
47            .finish()
48    }
49}
50
51fn debug_envelope(envelope: &Envelope) -> String {
52    if envelope.content.is_none() {
53        "Envelope { empty }".to_string()
54    } else {
55        format!(
56            "Envelope {{ \
57                 source_address: {:?}, \
58                 source_device: {:?}, \
59                 server_guid: {:?}, \
60                 timestamp: {:?}, \
61                 content: {} bytes, \
62             }}",
63            envelope.source_service_id,
64            envelope.source_device(),
65            envelope.server_guid(),
66            envelope.timestamp(),
67            envelope.content().len(),
68        )
69    }
70}
71
72impl<S> ServiceCipher<S>
73where
74    S: ProtocolStore + SenderKeyStore + SessionStoreExt + Clone,
75{
76    pub fn new(
77        protocol_store: S,
78        trust_roots: Vec<PublicKey>,
79        local_address: ProtocolAddress,
80    ) -> Self {
81        Self {
82            protocol_store,
83            trust_roots,
84            local_address,
85        }
86    }
87
88    /// Opens ("decrypts") an envelope.
89    ///
90    /// Envelopes may be empty, in which case this method returns `Ok(None)`
91    #[tracing::instrument(skip(envelope, csprng), fields(envelope = debug_envelope(&envelope)))]
92    pub async fn open_envelope<R: Rng + CryptoRng>(
93        &mut self,
94        envelope: Envelope,
95        csprng: &mut R,
96    ) -> Result<Option<Content>, ServiceError> {
97        let local_service: ServiceId =
98            ServiceId::parse_from_service_id_string(self.local_address.name())
99                .expect("valid protocol address name");
100
101        if envelope.content.is_some() {
102            let plaintext = self.decrypt(&envelope, csprng).await?;
103            let was_plaintext = plaintext.metadata.was_plaintext;
104            let message =
105                crate::proto::Content::decode(plaintext.data.as_slice())?;
106
107            tracing::Span::current()
108                .record("envelope_metadata", plaintext.metadata.to_string());
109
110            // Sanity test: if the envelope was plaintext, the message should *only* be a
111            // decryption failure error
112            if was_plaintext {
113                if let crate::proto::Content {
114                    data_message: None,
115                    sync_message: None,
116                    call_message: None,
117                    null_message: None,
118                    receipt_message: None,
119                    typing_message: None,
120                    sender_key_distribution_message: None,
121                    decryption_error_message: Some(decryption_error_message),
122                    story_message: None,
123                    pni_signature_message: None,
124                    edit_message: None,
125                } = &message
126                {
127                    tracing::warn!(
128                        ?envelope,
129                        "Received a decryption error message: {}.",
130                        String::from_utf8_lossy(decryption_error_message)
131                    );
132                } else {
133                    tracing::error!(
134                        ?envelope,
135                        "Received a plaintext envelope with a non-decryption error message."
136                    );
137                    return Ok(None);
138                }
139            }
140
141            if message.sync_message.is_some()
142                && plaintext.metadata.sender.aci().map(Uuid::from)
143                    != Some(local_service.raw_uuid())
144                && local_service.kind() == ServiceIdKind::Aci
145            {
146                tracing::warn!("Source is not ourself.");
147                return Ok(None);
148            }
149
150            if let Some(bytes) = &message.sender_key_distribution_message {
151                let skdm = SenderKeyDistributionMessage::try_from(&bytes[..])?;
152                process_sender_key_distribution_message(
153                    &plaintext.metadata.protocol_address()?,
154                    &skdm,
155                    &mut self.protocol_store,
156                )
157                .await?;
158
159                match Content::from_proto(message, plaintext.metadata) {
160                    Err(ServiceError::UnsupportedContent) => {
161                        tracing::trace!("Sender key distribution message without additional content");
162                        return Ok(None);
163                    },
164                    content => return Ok(Some(content?)),
165                }
166            }
167            let content = Content::from_proto(message, plaintext.metadata);
168            Ok(Some(content?))
169        } else {
170            Ok(None)
171        }
172    }
173
174    /// Equivalent of decrypt(Envelope, ciphertext)
175    ///
176    /// Triage of legacy messages happens inside this method, as opposed to the
177    /// Java implementation, because it makes the borrow checker and the
178    /// author happier.
179    #[tracing::instrument(skip(envelope, csprng), fields(envelope = debug_envelope(envelope)))]
180    async fn decrypt<R: Rng + CryptoRng>(
181        &mut self,
182        envelope: &Envelope,
183        csprng: &mut R,
184    ) -> Result<Plaintext, ServiceError> {
185        let local_service: ServiceId =
186            ServiceId::parse_from_service_id_string(self.local_address.name())
187                .expect("valid protocol address name");
188
189        let ciphertext = if let Some(msg) = envelope.content.as_ref() {
190            msg
191        } else {
192            return Err(ServiceError::InvalidFrame {
193                reason:
194                    "envelope should have either a legacy message or content.",
195            });
196        };
197
198        let server_guid = envelope.parse_server_guid();
199
200        let Some(destination_service_id) =
201            envelope.parse_destination_service_id()
202        else {
203            tracing::warn!(
204                "missing destination service id; ignoring invalid message."
205            );
206            return Err(ServiceError::InvalidFrame {
207                reason: "missing destination service id",
208            });
209        };
210
211        if destination_service_id != local_service {
212            tracing::warn!(
213                "mismatching destination service id; ignoring invalid message."
214            );
215            return Err(ServiceError::InvalidFrame {
216                reason: "mismatch destination service id",
217            });
218        }
219
220        if destination_service_id.kind() == ServiceIdKind::Pni
221            && envelope.source_service_id.is_none()
222        {
223            tracing::warn!("received sealed sender message to our PNI; ignoring invalid message");
224            return Err(ServiceError::InvalidFrame {
225                reason: "sealed sender received on our PNI",
226            });
227        }
228
229        // TODO: let chain in edition 2024
230        if let Some(source_service_id) = envelope.parse_source_service_id() {
231            if source_service_id.kind() == ServiceIdKind::Pni
232                && envelope.r#type() != Type::ServerDeliveryReceipt
233            {
234                tracing::warn!("got a message from a PNI that was not a ServerDeliveryReceipt; ignoring invalid message");
235                return Err(ServiceError::InvalidFrame {
236                    reason: "PNI received a non-ServerDeliveryReceipt",
237                });
238            }
239        }
240
241        // Extract both kinds of timestamps.
242        // Note that we do not `?` here, but rather only later, in case we ever have a branch which
243        // is not concerned with envelope metadata.
244        let timestamp = chrono::DateTime::from_timestamp_millis(
245            envelope.timestamp() as i64,
246        )
247        .ok_or(ServiceError::InvalidFrame {
248            reason: "unparseable timestamp",
249        });
250        let server_timestamp = chrono::DateTime::from_timestamp_millis(
251            envelope.server_timestamp() as i64,
252        )
253        .ok_or(ServiceError::InvalidFrame {
254            reason: "unparseable server timestamp",
255        });
256
257        use crate::proto::envelope::Type;
258        let plaintext = match envelope.r#type() {
259            Type::PrekeyBundle => {
260                let sender = get_preferred_protocol_address(
261                    &self.protocol_store,
262                    &envelope
263                        .parse_source_service_id()
264                        .expect("prekey bundle format"),
265                    envelope.source_device().try_into()?,
266                )
267                .await?;
268                let metadata = Metadata {
269                    destination: envelope
270                        .parse_destination_service_id()
271                        .expect("prekey bundle format"),
272                    sender: envelope
273                        .parse_source_service_id()
274                        .expect("prekey bundle format"),
275                    sender_device: envelope.source_device().try_into()?,
276                    timestamp: timestamp?,
277                    server_timestamp: server_timestamp?,
278                    needs_receipt: false,
279                    unidentified_sender: false,
280                    was_plaintext: false,
281
282                    server_guid,
283                };
284
285                let mut data = message_decrypt_prekey(
286                    &PreKeySignalMessage::try_from(&ciphertext[..])?,
287                    &sender,
288                    &self.local_address,
289                    &mut self.protocol_store.clone(),
290                    &mut self.protocol_store.clone(),
291                    &mut self.protocol_store.clone(),
292                    &self.protocol_store.clone(),
293                    &mut self.protocol_store.clone(),
294                    csprng,
295                )
296                .await?
297                .as_slice()
298                .to_vec();
299
300                let session_record = self
301                    .protocol_store
302                    .load_session(&sender)
303                    .await?
304                    .ok_or(SignalProtocolError::SessionNotFound(sender))?;
305
306                strip_padding_version(
307                    session_record.session_version()?,
308                    &mut data,
309                )?;
310                Plaintext { metadata, data }
311            },
312            Type::PlaintextContent => {
313                tracing::warn!(?envelope, "Envelope with plaintext content.  This usually indicates a decryption retry.");
314                let metadata = Metadata {
315                    destination: envelope
316                        .parse_destination_service_id()
317                        .expect("plaintext content format"),
318                    sender: envelope
319                        .parse_source_service_id()
320                        .expect("plaintext content format"),
321                    sender_device: envelope.source_device().try_into()?,
322                    timestamp: timestamp?,
323                    server_timestamp: server_timestamp?,
324                    needs_receipt: false,
325                    unidentified_sender: false,
326                    was_plaintext: true,
327
328                    server_guid,
329                };
330                Plaintext {
331                    metadata,
332                    data: ciphertext.clone(),
333                }
334            },
335            Type::Ciphertext => {
336                let sender = get_preferred_protocol_address(
337                    &self.protocol_store,
338                    &envelope
339                        .parse_source_service_id()
340                        .expect("ciphertext envelope format"),
341                    envelope.source_device().try_into()?,
342                )
343                .await?;
344                let metadata = Metadata {
345                    destination: envelope
346                        .parse_destination_service_id()
347                        .expect("ciphertext envelope format"),
348                    sender: envelope
349                        .parse_source_service_id()
350                        .expect("ciphertext envelope format"),
351                    sender_device: envelope.source_device().try_into()?,
352                    timestamp: timestamp?,
353                    server_timestamp: server_timestamp?,
354                    needs_receipt: false,
355                    unidentified_sender: false,
356                    was_plaintext: false,
357
358                    server_guid,
359                };
360
361                let mut data = message_decrypt_signal(
362                    &SignalMessage::try_from(&ciphertext[..])?,
363                    &sender,
364                    &mut self.protocol_store.clone(),
365                    &mut self.protocol_store.clone(),
366                    csprng,
367                )
368                .await?
369                .as_slice()
370                .to_vec();
371
372                let session_record = self
373                    .protocol_store
374                    .load_session(&sender)
375                    .await?
376                    .ok_or(SignalProtocolError::SessionNotFound(sender))?;
377
378                strip_padding_version(
379                    session_record.session_version()?,
380                    &mut data,
381                )?;
382                Plaintext { metadata, data }
383            },
384            Type::UnidentifiedSender => {
385                let SealedSenderDecryptionResult {
386                    sender_uuid,
387                    sender_e164: _,
388                    device_id,
389                    mut message,
390                } = sealed_sender_decrypt(
391                    ciphertext,
392                    &self.trust_roots,
393                    Timestamp::from_epoch_millis(envelope.timestamp()),
394                    None,
395                    self.local_address.clone(),
396                    &mut self.protocol_store.clone(),
397                    &mut self.protocol_store.clone(),
398                    &mut self.protocol_store.clone(),
399                    &mut self.protocol_store.clone(),
400                    &mut self.protocol_store.clone(),
401                    &mut self.protocol_store,
402                )
403                .await?;
404
405                let Some(sender) =
406                    ServiceId::parse_from_service_id_string(&sender_uuid)
407                else {
408                    return Err(
409                        SignalProtocolError::InvalidSealedSenderMessage(
410                            "invalid sender UUID".to_string(),
411                        )
412                        .into(),
413                    );
414                };
415
416                let needs_receipt = if envelope.source_service_id.is_some() {
417                    tracing::warn!(?envelope, "Received an unidentified delivery over an identified channel.  Marking needs_receipt=false");
418                    false
419                } else {
420                    true
421                };
422
423                if sender.kind() == ServiceIdKind::Pni {
424                    tracing::warn!(
425                        "sealed sender used for PNI; ignoring invalid message"
426                    );
427                    return Err(ServiceError::InvalidFrame {
428                        reason: "sealed sender used for PNI",
429                    });
430                }
431
432                let metadata = Metadata {
433                    destination: envelope
434                        .parse_destination_service_id()
435                        .expect("unidentified sender envelope format"),
436                    sender,
437                    sender_device: device_id,
438                    timestamp: timestamp?,
439                    server_timestamp: server_timestamp?,
440                    unidentified_sender: true,
441                    needs_receipt,
442                    was_plaintext: false,
443
444                    server_guid,
445                };
446
447                strip_padding(&mut message)?;
448
449                Plaintext {
450                    metadata,
451                    data: message,
452                }
453            },
454            _ => {
455                // else
456                return Err(ServiceError::InvalidFrame {
457                    reason: "envelope has unknown type",
458                });
459            },
460        };
461        Ok(plaintext)
462    }
463
464    #[tracing::instrument(
465        skip(address, unidentified_access, content, csprng),
466        fields(
467            address = %address,
468            with_unidentified_access = unidentified_access.is_some(),
469            content_length = content.len(),
470        )
471    )]
472    pub(crate) async fn encrypt<R: Rng + CryptoRng>(
473        &mut self,
474        address: &ProtocolAddress,
475        unidentified_access: Option<&SenderCertificate>,
476        content: &[u8],
477        csprng: &mut R,
478    ) -> Result<OutgoingPushMessage, ServiceError> {
479        let mut rng = rng();
480
481        let session_record = self
482            .protocol_store
483            .load_session(address)
484            .await?
485            .ok_or_else(|| {
486            SignalProtocolError::SessionNotFound(address.clone())
487        })?;
488
489        let padded_content =
490            add_padding(session_record.session_version()?, content)?;
491
492        if let Some(unindentified_access) = unidentified_access {
493            let destination_registration_id =
494                session_record.remote_registration_id()?;
495
496            let message = sealed_sender_encrypt(
497                address,
498                unindentified_access,
499                &padded_content,
500                &mut self.protocol_store.clone(),
501                &mut self.protocol_store,
502                SystemTime::now(),
503                csprng,
504            )
505            .await?;
506
507            use crate::proto::envelope::Type;
508            Ok(OutgoingPushMessage {
509                r#type: Type::UnidentifiedSender as u32,
510                destination_device_id: address.device_id(),
511                destination_registration_id,
512                content: BASE64_RELAXED.encode(message),
513            })
514        } else {
515            let message = message_encrypt(
516                &padded_content,
517                address,
518                &self.local_address,
519                &mut self.protocol_store.clone(),
520                &mut self.protocol_store.clone(),
521                SystemTime::now(),
522                &mut rng,
523            )
524            .await?;
525
526            let destination_registration_id =
527                session_record.remote_registration_id()?;
528
529            let body = BASE64_RELAXED.encode(message.serialize());
530
531            use crate::proto::envelope::Type;
532            let message_type = match message.message_type() {
533                CiphertextMessageType::PreKey => Type::PrekeyBundle,
534                CiphertextMessageType::Whisper => Type::Ciphertext,
535                t => panic!("Bad type: {:?}", t),
536            } as u32;
537            Ok(OutgoingPushMessage {
538                r#type: message_type,
539                destination_device_id: address.device_id(),
540                destination_registration_id,
541                content: body,
542            })
543        }
544    }
545}
546
547struct Plaintext {
548    metadata: Metadata,
549    data: Vec<u8>,
550}
551
552#[expect(clippy::comparison_chain)]
553fn add_padding(version: u32, contents: &[u8]) -> Result<Vec<u8>, ServiceError> {
554    if version < 2 {
555        Err(ServiceError::PaddingVersion(version))
556    } else if version == 2 {
557        Ok(contents.to_vec())
558    } else {
559        let message_length = contents.len();
560        let message_length_with_terminator = contents.len() + 1;
561        let mut message_part_count = message_length_with_terminator / 160;
562        if !message_length_with_terminator.is_multiple_of(160) {
563            message_part_count += 1;
564        }
565
566        let message_length_with_padding = message_part_count * 160;
567
568        let mut buffer = vec![0u8; message_length_with_padding];
569        buffer[..message_length].copy_from_slice(contents);
570        Iso7816::raw_pad(&mut buffer, message_length);
571        Ok(buffer)
572    }
573}
574
575#[expect(clippy::comparison_chain)]
576fn strip_padding_version(
577    version: u32,
578    contents: &mut Vec<u8>,
579) -> Result<(), ServiceError> {
580    if version < 2 {
581        Err(ServiceError::InvalidFrame {
582            reason: "unknown version",
583        })
584    } else if version == 2 {
585        Ok(())
586    } else {
587        strip_padding(contents)?;
588        Ok(())
589    }
590}
591
592fn strip_padding(contents: &mut Vec<u8>) -> Result<(), ServiceError> {
593    let new_length = Iso7816::raw_unpad(contents)?.len();
594    contents.resize(new_length, 0);
595    Ok(())
596}
597
598/// Equivalent of `SignalServiceCipher::getPreferredProtocolAddress`
599pub async fn get_preferred_protocol_address<S: SessionStore>(
600    session_store: &S,
601    address: &ServiceId,
602    device_id: DeviceId,
603) -> Result<ProtocolAddress, libsignal_protocol::error::SignalProtocolError> {
604    let address = address.to_protocol_address(device_id);
605    if session_store.load_session(&address).await?.is_some() {
606        return Ok(address);
607    }
608
609    Ok(address)
610}
611
612/// Error thrown when the sealed sending decryption fails.
613///
614/// The USMC sender field is only populated when the USMC could be validated against the trust roots;
615/// hence the sender information can be trusted, give or take an active attacker on the Signal
616/// side.
617#[derive(thiserror::Error)]
618#[error("error: {inner}, usmc: {}", sender.is_some())]
619pub struct SealedSenderDecryptionError {
620    pub inner: SignalProtocolError,
621    pub sender: Option<ProtocolAddress>,
622}
623
624impl fmt::Debug for SealedSenderDecryptionError {
625    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
626        f.debug_struct("SealedSenderDecryptionError")
627            .field("inner", &self.inner)
628            .field("sender", &self.sender)
629            .finish()
630    }
631}
632
633impl From<SignalProtocolError> for SealedSenderDecryptionError {
634    fn from(e: SignalProtocolError) -> Self {
635        SealedSenderDecryptionError {
636            inner: e,
637            sender: None,
638        }
639    }
640}
641
642/// Decrypt a Sealed Sender message `ciphertext` in either the v1 or v2 format, validate its sender
643/// certificate, and then decrypt the inner message payload.
644///
645/// This method calls [`sealed_sender_decrypt_to_usmc`] to extract the sender information, including
646/// the embedded [`SenderCertificate`]. The sender certificate (signed by the [`ServerCertificate`])
647/// is then validated against the `trust_root` baked into the client to ensure that the sender's
648/// identity was not forged.
649#[allow(clippy::too_many_arguments)]
650#[tracing::instrument(
651    skip(
652        ciphertext,
653        trust_roots,
654        identity_store,
655        session_store,
656        pre_key_store,
657        signed_pre_key_store,
658        sender_key_store,
659        kyber_pre_key_store
660    ),
661    fields(
662        ciphertext = ciphertext.len(),
663    )
664)]
665async fn sealed_sender_decrypt(
666    ciphertext: &[u8],
667    trust_roots: &[PublicKey],
668    timestamp: Timestamp,
669    local_e164: Option<String>,
670    local_address: ProtocolAddress,
671    identity_store: &mut dyn IdentityKeyStore,
672    session_store: &mut dyn SessionStore,
673    pre_key_store: &mut dyn PreKeyStore,
674    signed_pre_key_store: &mut dyn SignedPreKeyStore,
675    sender_key_store: &mut dyn SenderKeyStore,
676    kyber_pre_key_store: &mut dyn KyberPreKeyStore,
677) -> Result<SealedSenderDecryptionResult, SealedSenderDecryptionError> {
678    let usmc =
679        sealed_sender_decrypt_to_usmc(ciphertext, identity_store).await?;
680
681    if !usmc
682        .sender()?
683        .validate_with_trust_roots(trust_roots, timestamp)?
684    {
685        return Err(SignalProtocolError::InvalidSealedSenderMessage(
686            "trust root validation failed".to_string(),
687        )
688        .into());
689    }
690
691    let local_service_id =
692        ServiceId::parse_from_service_id_string(local_address.name())
693            .expect("valid protocol address name");
694    let is_local_uuid = local_service_id.raw_uuid()
695        == usmc
696            .sender()?
697            .sender_uuid()?
698            .parse::<Uuid>()
699            // Validity checked inside certificate checker
700            .expect("valid uuid");
701
702    let is_local_e164 = match (local_e164, usmc.sender()?.sender_e164()?) {
703        (Some(l), Some(s)) => l == s,
704        (_, _) => false,
705    };
706
707    if (is_local_e164 || is_local_uuid)
708        && usmc.sender()?.sender_device_id()? == local_address.device_id()
709    {
710        return Err(SignalProtocolError::SealedSenderSelfSend.into());
711    }
712
713    let remote_address = ProtocolAddress::new(
714        usmc.sender()?.sender_uuid()?.to_string(),
715        usmc.sender()?.sender_device_id()?,
716    );
717
718    sealed_sender_decrypt_with_validated_usmc(
719        &usmc,
720        &remote_address,
721        &local_address,
722        identity_store,
723        session_store,
724        pre_key_store,
725        signed_pre_key_store,
726        sender_key_store,
727        kyber_pre_key_store,
728    )
729    .await
730    .map_err(|inner| SealedSenderDecryptionError {
731        inner,
732        sender: Some(remote_address),
733    })
734}
735
736#[allow(clippy::too_many_arguments)]
737async fn sealed_sender_decrypt_with_validated_usmc(
738    usmc: &UnidentifiedSenderMessageContent,
739    remote_address: &ProtocolAddress,
740    local_address: &ProtocolAddress,
741    identity_store: &mut dyn IdentityKeyStore,
742    session_store: &mut dyn SessionStore,
743    pre_key_store: &mut dyn PreKeyStore,
744    signed_pre_key_store: &mut dyn SignedPreKeyStore,
745    sender_key_store: &mut dyn SenderKeyStore,
746    kyber_pre_key_store: &mut dyn KyberPreKeyStore,
747) -> Result<SealedSenderDecryptionResult, SignalProtocolError> {
748    let mut rng = rng();
749
750    let message = match usmc.msg_type()? {
751        CiphertextMessageType::Whisper => {
752            let ctext = SignalMessage::try_from(usmc.contents()?)?;
753            message_decrypt_signal(
754                &ctext,
755                remote_address,
756                session_store,
757                identity_store,
758                &mut rng,
759            )
760            .await?
761        },
762        CiphertextMessageType::PreKey => {
763            let ctext = PreKeySignalMessage::try_from(usmc.contents()?)?;
764            message_decrypt_prekey(
765                &ctext,
766                remote_address,
767                local_address,
768                session_store,
769                identity_store,
770                pre_key_store,
771                signed_pre_key_store,
772                kyber_pre_key_store,
773                &mut rng,
774            )
775            .await?
776        },
777        CiphertextMessageType::SenderKey => {
778            group_decrypt(usmc.contents()?, sender_key_store, remote_address)
779                .await?
780        },
781        CiphertextMessageType::Plaintext => {
782            // Sealed sender envelope wrapping a PlaintextContent.
783            // Should contain a DecryptionErrorMessage.
784            let plaintext_content =
785                PlaintextContent::try_from(usmc.contents()?)?;
786            plaintext_content.body().to_vec()
787        },
788    };
789
790    Ok(SealedSenderDecryptionResult {
791        sender_uuid: usmc.sender()?.sender_uuid()?.to_string(),
792        sender_e164: usmc.sender()?.sender_e164()?.map(|s| s.to_string()),
793        device_id: usmc.sender()?.sender_device_id()?,
794        message,
795    })
796}