libsignal_service/
cipher.rs

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