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    UsePQRatchet,
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/// 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_root: 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_root: PublicKey,
79        local_uuid: Uuid,
80        local_device_id: DeviceId,
81    ) -> Self {
82        Self {
83            protocol_store,
84            trust_root,
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                    UsePQRatchet::No,
229                )
230                .await?
231                .as_slice()
232                .to_vec();
233
234                let session_record = self
235                    .protocol_store
236                    .load_session(&sender)
237                    .await?
238                    .ok_or(SignalProtocolError::SessionNotFound(sender))?;
239
240                strip_padding_version(
241                    session_record.session_version()?,
242                    &mut data,
243                )?;
244                Plaintext { metadata, data }
245            },
246            Type::PlaintextContent => {
247                tracing::warn!(?envelope, "Envelope with plaintext content.  This usually indicates a decryption retry.");
248                let metadata = Metadata {
249                    destination: envelope.destination_address(),
250                    sender: envelope.source_address(),
251                    sender_device: envelope.source_device().try_into()?,
252                    timestamp: envelope.server_timestamp(),
253                    needs_receipt: false,
254                    unidentified_sender: false,
255                    was_plaintext: true,
256
257                    server_guid,
258                };
259                Plaintext {
260                    metadata,
261                    data: ciphertext.clone(),
262                }
263            },
264            Type::Ciphertext => {
265                let sender = get_preferred_protocol_address(
266                    &self.protocol_store,
267                    &envelope.source_address(),
268                    envelope.source_device().try_into()?,
269                )
270                .await?;
271                let metadata = Metadata {
272                    destination: envelope.destination_address(),
273                    sender: envelope.source_address(),
274                    sender_device: envelope.source_device().try_into()?,
275                    timestamp: envelope.timestamp(),
276                    needs_receipt: false,
277                    unidentified_sender: false,
278                    was_plaintext: false,
279
280                    server_guid,
281                };
282
283                let mut data = message_decrypt_signal(
284                    &SignalMessage::try_from(&ciphertext[..])?,
285                    &sender,
286                    &mut self.protocol_store.clone(),
287                    &mut self.protocol_store.clone(),
288                    csprng,
289                )
290                .await?
291                .as_slice()
292                .to_vec();
293
294                let session_record = self
295                    .protocol_store
296                    .load_session(&sender)
297                    .await?
298                    .ok_or(SignalProtocolError::SessionNotFound(sender))?;
299
300                strip_padding_version(
301                    session_record.session_version()?,
302                    &mut data,
303                )?;
304                Plaintext { metadata, data }
305            },
306            Type::UnidentifiedSender => {
307                let SealedSenderDecryptionResult {
308                    sender_uuid,
309                    sender_e164: _,
310                    device_id,
311                    mut message,
312                } = sealed_sender_decrypt(
313                    ciphertext,
314                    &self.trust_root,
315                    Timestamp::from_epoch_millis(envelope.timestamp()),
316                    None,
317                    self.local_uuid.to_string(),
318                    self.local_device_id,
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.clone(),
324                    &mut self.protocol_store,
325                )
326                .await?;
327
328                let Some(sender) =
329                    ServiceId::parse_from_service_id_string(&sender_uuid)
330                else {
331                    return Err(
332                        SignalProtocolError::InvalidSealedSenderMessage(
333                            "invalid sender UUID".to_string(),
334                        )
335                        .into(),
336                    );
337                };
338
339                let needs_receipt = if envelope.source_service_id.is_some() {
340                    tracing::warn!(?envelope, "Received an unidentified delivery over an identified channel.  Marking needs_receipt=false");
341                    false
342                } else {
343                    true
344                };
345
346                let metadata = Metadata {
347                    destination: envelope.destination_address(),
348                    sender,
349                    sender_device: device_id,
350                    timestamp: envelope.timestamp(),
351                    unidentified_sender: true,
352                    needs_receipt,
353                    was_plaintext: false,
354
355                    server_guid,
356                };
357
358                strip_padding(&mut message)?;
359
360                Plaintext {
361                    metadata,
362                    data: message,
363                }
364            },
365            _ => {
366                // else
367                return Err(ServiceError::InvalidFrame {
368                    reason: "envelope has unknown type",
369                });
370            },
371        };
372        Ok(plaintext)
373    }
374
375    #[tracing::instrument(
376        skip(address, unidentified_access, content, csprng),
377        fields(
378            address = %address,
379            with_unidentified_access = unidentified_access.is_some(),
380            content_length = content.len(),
381        )
382    )]
383    pub(crate) async fn encrypt<R: Rng + CryptoRng>(
384        &mut self,
385        address: &ProtocolAddress,
386        unidentified_access: Option<&SenderCertificate>,
387        content: &[u8],
388        csprng: &mut R,
389    ) -> Result<OutgoingPushMessage, ServiceError> {
390        let mut rng = rng();
391
392        let session_record = self
393            .protocol_store
394            .load_session(address)
395            .await?
396            .ok_or_else(|| {
397            SignalProtocolError::SessionNotFound(address.clone())
398        })?;
399
400        let padded_content =
401            add_padding(session_record.session_version()?, content)?;
402
403        if let Some(unindentified_access) = unidentified_access {
404            let destination_registration_id =
405                session_record.remote_registration_id()?;
406
407            let message = sealed_sender_encrypt(
408                address,
409                unindentified_access,
410                &padded_content,
411                &mut self.protocol_store.clone(),
412                &mut self.protocol_store,
413                SystemTime::now(),
414                csprng,
415            )
416            .await?;
417
418            use crate::proto::envelope::Type;
419            Ok(OutgoingPushMessage {
420                r#type: Type::UnidentifiedSender as u32,
421                destination_device_id: address.device_id().into(),
422                destination_registration_id,
423                content: BASE64_RELAXED.encode(message),
424            })
425        } else {
426            let message = message_encrypt(
427                &padded_content,
428                address,
429                &mut self.protocol_store.clone(),
430                &mut self.protocol_store.clone(),
431                SystemTime::now(),
432                &mut rng,
433            )
434            .await?;
435
436            let destination_registration_id =
437                session_record.remote_registration_id()?;
438
439            let body = BASE64_RELAXED.encode(message.serialize());
440
441            use crate::proto::envelope::Type;
442            let message_type = match message.message_type() {
443                CiphertextMessageType::PreKey => Type::PrekeyBundle,
444                CiphertextMessageType::Whisper => Type::Ciphertext,
445                t => panic!("Bad type: {:?}", t),
446            } as u32;
447            Ok(OutgoingPushMessage {
448                r#type: message_type,
449                destination_device_id: address.device_id().into(),
450                destination_registration_id,
451                content: body,
452            })
453        }
454    }
455}
456
457struct Plaintext {
458    metadata: Metadata,
459    data: Vec<u8>,
460}
461
462#[expect(clippy::comparison_chain, clippy::result_large_err)]
463fn add_padding(version: u32, contents: &[u8]) -> Result<Vec<u8>, ServiceError> {
464    if version < 2 {
465        Err(ServiceError::PaddingVersion(version))
466    } else if version == 2 {
467        Ok(contents.to_vec())
468    } else {
469        let message_length = contents.len();
470        let message_length_with_terminator = contents.len() + 1;
471        let mut message_part_count = message_length_with_terminator / 160;
472        if message_length_with_terminator % 160 != 0 {
473            message_part_count += 1;
474        }
475
476        let message_length_with_padding = message_part_count * 160;
477
478        let mut buffer = vec![0u8; message_length_with_padding];
479        buffer[..message_length].copy_from_slice(contents);
480        Iso7816::raw_pad(&mut buffer, message_length);
481        Ok(buffer)
482    }
483}
484
485#[expect(clippy::comparison_chain, clippy::result_large_err)]
486fn strip_padding_version(
487    version: u32,
488    contents: &mut Vec<u8>,
489) -> Result<(), ServiceError> {
490    if version < 2 {
491        Err(ServiceError::InvalidFrame {
492            reason: "unknown version",
493        })
494    } else if version == 2 {
495        Ok(())
496    } else {
497        strip_padding(contents)?;
498        Ok(())
499    }
500}
501
502#[expect(clippy::result_large_err)]
503fn strip_padding(contents: &mut Vec<u8>) -> Result<(), ServiceError> {
504    let new_length = Iso7816::raw_unpad(contents)?.len();
505    contents.resize(new_length, 0);
506    Ok(())
507}
508
509/// Equivalent of `SignalServiceCipher::getPreferredProtocolAddress`
510pub async fn get_preferred_protocol_address<S: SessionStore>(
511    session_store: &S,
512    address: &ServiceId,
513    device_id: DeviceId,
514) -> Result<ProtocolAddress, libsignal_protocol::error::SignalProtocolError> {
515    let address = address.to_protocol_address(device_id);
516    if session_store.load_session(&address).await?.is_some() {
517        return Ok(address);
518    }
519
520    Ok(address)
521}
522
523/// Decrypt a Sealed Sender message `ciphertext` in either the v1 or v2 format, validate its sender
524/// certificate, and then decrypt the inner message payload.
525///
526/// This method calls [`sealed_sender_decrypt_to_usmc`] to extract the sender information, including
527/// the embedded [`SenderCertificate`]. The sender certificate (signed by the [`ServerCertificate`])
528/// is then validated against the `trust_root` baked into the client to ensure that the sender's
529/// identity was not forged.
530#[allow(clippy::too_many_arguments)]
531#[tracing::instrument(
532    skip(
533        ciphertext,
534        trust_root,
535        identity_store,
536        session_store,
537        pre_key_store,
538        signed_pre_key_store,
539        sender_key_store,
540        kyber_pre_key_store
541    ),
542    fields(
543        ciphertext = ciphertext.len(),
544    )
545)]
546async fn sealed_sender_decrypt(
547    ciphertext: &[u8],
548    trust_root: &PublicKey,
549    timestamp: Timestamp,
550    local_e164: Option<String>,
551    local_uuid: String,
552    local_device_id: DeviceId,
553    identity_store: &mut dyn IdentityKeyStore,
554    session_store: &mut dyn SessionStore,
555    pre_key_store: &mut dyn PreKeyStore,
556    signed_pre_key_store: &mut dyn SignedPreKeyStore,
557    sender_key_store: &mut dyn SenderKeyStore,
558    kyber_pre_key_store: &mut dyn KyberPreKeyStore,
559) -> Result<SealedSenderDecryptionResult, SignalProtocolError> {
560    let usmc =
561        sealed_sender_decrypt_to_usmc(ciphertext, identity_store).await?;
562
563    if !usmc.sender()?.validate(trust_root, timestamp)? {
564        return Err(SignalProtocolError::InvalidSealedSenderMessage(
565            "trust root validation failed".to_string(),
566        ));
567    }
568
569    let is_local_uuid = local_uuid == usmc.sender()?.sender_uuid()?;
570
571    let is_local_e164 = match (local_e164, usmc.sender()?.sender_e164()?) {
572        (Some(l), Some(s)) => l == s,
573        (_, _) => false,
574    };
575
576    if (is_local_e164 || is_local_uuid)
577        && usmc.sender()?.sender_device_id()? == local_device_id
578    {
579        return Err(SignalProtocolError::SealedSenderSelfSend);
580    }
581
582    let mut rng = rng();
583
584    let remote_address = ProtocolAddress::new(
585        usmc.sender()?.sender_uuid()?.to_string(),
586        usmc.sender()?.sender_device_id()?,
587    );
588
589    let message = match usmc.msg_type()? {
590        CiphertextMessageType::Whisper => {
591            let ctext = SignalMessage::try_from(usmc.contents()?)?;
592            message_decrypt_signal(
593                &ctext,
594                &remote_address,
595                session_store,
596                identity_store,
597                &mut rng,
598            )
599            .await?
600        },
601        CiphertextMessageType::PreKey => {
602            let ctext = PreKeySignalMessage::try_from(usmc.contents()?)?;
603            message_decrypt_prekey(
604                &ctext,
605                &remote_address,
606                session_store,
607                identity_store,
608                pre_key_store,
609                signed_pre_key_store,
610                kyber_pre_key_store,
611                &mut rng,
612                UsePQRatchet::No,
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}