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