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