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#[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 #[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 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
157 match Content::from_proto(message, plaintext.metadata) {
158 Err(ServiceError::UnsupportedContent) => {
159 tracing::trace!("Sender key distribution message without additional content");
160 return Ok(None);
161 },
162 content => return Ok(Some(content?)),
163 }
164 }
165 let content = Content::from_proto(message, plaintext.metadata);
166 Ok(Some(content?))
167 } else {
168 Ok(None)
169 }
170 }
171
172 #[tracing::instrument(skip(envelope, csprng), fields(envelope = debug_envelope(envelope)))]
178 async fn decrypt<R: Rng + CryptoRng>(
179 &mut self,
180 envelope: &Envelope,
181 csprng: &mut R,
182 ) -> Result<Plaintext, ServiceError> {
183 let ciphertext = if let Some(msg) = envelope.content.as_ref() {
184 msg
185 } else {
186 return Err(ServiceError::InvalidFrame {
187 reason:
188 "envelope should have either a legacy message or content.",
189 });
190 };
191
192 let server_guid = envelope.parse_server_guid();
193
194 let Some(destination_service_id) =
195 envelope.parse_destination_service_id()
196 else {
197 tracing::warn!(
198 "missing destination service id; ignoring invalid message."
199 );
200 return Err(ServiceError::InvalidFrame {
201 reason: "missing destination service id",
202 });
203 };
204
205 if destination_service_id.raw_uuid() != self.local_uuid {
206 tracing::warn!(
207 "mismatching destination service id; ignoring invalid message."
208 );
209 return Err(ServiceError::InvalidFrame {
210 reason: "mismatch destination service id",
211 });
212 }
213
214 if destination_service_id.kind() == ServiceIdKind::Pni
215 && envelope.source_service_id.is_none()
216 {
217 tracing::warn!("received sealed sender message to our PNI; ignoring invalid message");
218 return Err(ServiceError::InvalidFrame {
219 reason: "sealed sender received on our PNI",
220 });
221 }
222
223 if let Some(source_service_id) = envelope.parse_source_service_id() {
225 if source_service_id.kind() == ServiceIdKind::Pni
226 && envelope.r#type() != Type::ServerDeliveryReceipt
227 {
228 tracing::warn!("got a message from a PNI that was not a ServerDeliveryReceipt; ignoring invalid message");
229 return Err(ServiceError::InvalidFrame {
230 reason: "PNI received a non-ServerDeliveryReceipt",
231 });
232 }
233 }
234
235 use crate::proto::envelope::Type;
236 let plaintext = match envelope.r#type() {
237 Type::PrekeyBundle => {
238 let sender = get_preferred_protocol_address(
239 &self.protocol_store,
240 &envelope
241 .parse_source_service_id()
242 .expect("prekey bundle format"),
243 envelope.source_device().try_into()?,
244 )
245 .await?;
246 let metadata = Metadata {
247 destination: envelope
248 .parse_destination_service_id()
249 .expect("prekey bundle format"),
250 sender: envelope
251 .parse_source_service_id()
252 .expect("prekey bundle format"),
253 sender_device: envelope.source_device().try_into()?,
254 timestamp: envelope.server_timestamp(),
255 needs_receipt: false,
256 unidentified_sender: false,
257 was_plaintext: false,
258
259 server_guid,
260 };
261
262 let mut data = message_decrypt_prekey(
263 &PreKeySignalMessage::try_from(&ciphertext[..])?,
264 &sender,
265 &mut self.protocol_store.clone(),
266 &mut self.protocol_store.clone(),
267 &mut self.protocol_store.clone(),
268 &self.protocol_store.clone(),
269 &mut self.protocol_store.clone(),
270 csprng,
271 )
272 .await?
273 .as_slice()
274 .to_vec();
275
276 let session_record = self
277 .protocol_store
278 .load_session(&sender)
279 .await?
280 .ok_or(SignalProtocolError::SessionNotFound(sender))?;
281
282 strip_padding_version(
283 session_record.session_version()?,
284 &mut data,
285 )?;
286 Plaintext { metadata, data }
287 },
288 Type::PlaintextContent => {
289 tracing::warn!(?envelope, "Envelope with plaintext content. This usually indicates a decryption retry.");
290 let metadata = Metadata {
291 destination: envelope
292 .parse_destination_service_id()
293 .expect("plaintext content format"),
294 sender: envelope
295 .parse_source_service_id()
296 .expect("plaintext content format"),
297 sender_device: envelope.source_device().try_into()?,
298 timestamp: envelope.server_timestamp(),
299 needs_receipt: false,
300 unidentified_sender: false,
301 was_plaintext: true,
302
303 server_guid,
304 };
305 Plaintext {
306 metadata,
307 data: ciphertext.clone(),
308 }
309 },
310 Type::Ciphertext => {
311 let sender = get_preferred_protocol_address(
312 &self.protocol_store,
313 &envelope
314 .parse_source_service_id()
315 .expect("ciphertext envelope format"),
316 envelope.source_device().try_into()?,
317 )
318 .await?;
319 let metadata = Metadata {
320 destination: envelope
321 .parse_destination_service_id()
322 .expect("ciphertext envelope format"),
323 sender: envelope
324 .parse_source_service_id()
325 .expect("ciphertext envelope format"),
326 sender_device: envelope.source_device().try_into()?,
327 timestamp: envelope.timestamp(),
328 needs_receipt: false,
329 unidentified_sender: false,
330 was_plaintext: false,
331
332 server_guid,
333 };
334
335 let mut data = message_decrypt_signal(
336 &SignalMessage::try_from(&ciphertext[..])?,
337 &sender,
338 &mut self.protocol_store.clone(),
339 &mut self.protocol_store.clone(),
340 csprng,
341 )
342 .await?
343 .as_slice()
344 .to_vec();
345
346 let session_record = self
347 .protocol_store
348 .load_session(&sender)
349 .await?
350 .ok_or(SignalProtocolError::SessionNotFound(sender))?;
351
352 strip_padding_version(
353 session_record.session_version()?,
354 &mut data,
355 )?;
356 Plaintext { metadata, data }
357 },
358 Type::UnidentifiedSender => {
359 let SealedSenderDecryptionResult {
360 sender_uuid,
361 sender_e164: _,
362 device_id,
363 mut message,
364 } = sealed_sender_decrypt(
365 ciphertext,
366 &self.trust_roots,
367 Timestamp::from_epoch_millis(envelope.timestamp()),
368 None,
369 self.local_uuid.to_string(),
370 self.local_device_id,
371 &mut self.protocol_store.clone(),
372 &mut self.protocol_store.clone(),
373 &mut self.protocol_store.clone(),
374 &mut self.protocol_store.clone(),
375 &mut self.protocol_store.clone(),
376 &mut self.protocol_store,
377 )
378 .await?;
379
380 let Some(sender) =
381 ServiceId::parse_from_service_id_string(&sender_uuid)
382 else {
383 return Err(
384 SignalProtocolError::InvalidSealedSenderMessage(
385 "invalid sender UUID".to_string(),
386 )
387 .into(),
388 );
389 };
390
391 let needs_receipt = if envelope.source_service_id.is_some() {
392 tracing::warn!(?envelope, "Received an unidentified delivery over an identified channel. Marking needs_receipt=false");
393 false
394 } else {
395 true
396 };
397
398 if sender.kind() == ServiceIdKind::Pni {
399 tracing::warn!(
400 "sealed sender used for PNI; ignoring invalid message"
401 );
402 return Err(ServiceError::InvalidFrame {
403 reason: "sealed sender used for PNI",
404 });
405 }
406
407 let metadata = Metadata {
408 destination: envelope
409 .parse_destination_service_id()
410 .expect("unidentified sender envelope format"),
411 sender,
412 sender_device: device_id,
413 timestamp: envelope.timestamp(),
414 unidentified_sender: true,
415 needs_receipt,
416 was_plaintext: false,
417
418 server_guid,
419 };
420
421 strip_padding(&mut message)?;
422
423 Plaintext {
424 metadata,
425 data: message,
426 }
427 },
428 _ => {
429 return Err(ServiceError::InvalidFrame {
431 reason: "envelope has unknown type",
432 });
433 },
434 };
435 Ok(plaintext)
436 }
437
438 #[tracing::instrument(
439 skip(address, unidentified_access, content, csprng),
440 fields(
441 address = %address,
442 with_unidentified_access = unidentified_access.is_some(),
443 content_length = content.len(),
444 )
445 )]
446 pub(crate) async fn encrypt<R: Rng + CryptoRng>(
447 &mut self,
448 address: &ProtocolAddress,
449 unidentified_access: Option<&SenderCertificate>,
450 content: &[u8],
451 csprng: &mut R,
452 ) -> Result<OutgoingPushMessage, ServiceError> {
453 let mut rng = rng();
454
455 let session_record = self
456 .protocol_store
457 .load_session(address)
458 .await?
459 .ok_or_else(|| {
460 SignalProtocolError::SessionNotFound(address.clone())
461 })?;
462
463 let padded_content =
464 add_padding(session_record.session_version()?, content)?;
465
466 if let Some(unindentified_access) = unidentified_access {
467 let destination_registration_id =
468 session_record.remote_registration_id()?;
469
470 let message = sealed_sender_encrypt(
471 address,
472 unindentified_access,
473 &padded_content,
474 &mut self.protocol_store.clone(),
475 &mut self.protocol_store,
476 SystemTime::now(),
477 csprng,
478 )
479 .await?;
480
481 use crate::proto::envelope::Type;
482 Ok(OutgoingPushMessage {
483 r#type: Type::UnidentifiedSender as u32,
484 destination_device_id: address.device_id(),
485 destination_registration_id,
486 content: BASE64_RELAXED.encode(message),
487 })
488 } else {
489 let message = message_encrypt(
490 &padded_content,
491 address,
492 &mut self.protocol_store.clone(),
493 &mut self.protocol_store.clone(),
494 SystemTime::now(),
495 &mut rng,
496 )
497 .await?;
498
499 let destination_registration_id =
500 session_record.remote_registration_id()?;
501
502 let body = BASE64_RELAXED.encode(message.serialize());
503
504 use crate::proto::envelope::Type;
505 let message_type = match message.message_type() {
506 CiphertextMessageType::PreKey => Type::PrekeyBundle,
507 CiphertextMessageType::Whisper => Type::Ciphertext,
508 t => panic!("Bad type: {:?}", t),
509 } as u32;
510 Ok(OutgoingPushMessage {
511 r#type: message_type,
512 destination_device_id: address.device_id(),
513 destination_registration_id,
514 content: body,
515 })
516 }
517 }
518}
519
520struct Plaintext {
521 metadata: Metadata,
522 data: Vec<u8>,
523}
524
525#[expect(clippy::comparison_chain)]
526fn add_padding(version: u32, contents: &[u8]) -> Result<Vec<u8>, ServiceError> {
527 if version < 2 {
528 Err(ServiceError::PaddingVersion(version))
529 } else if version == 2 {
530 Ok(contents.to_vec())
531 } else {
532 let message_length = contents.len();
533 let message_length_with_terminator = contents.len() + 1;
534 let mut message_part_count = message_length_with_terminator / 160;
535 if !message_length_with_terminator.is_multiple_of(160) {
536 message_part_count += 1;
537 }
538
539 let message_length_with_padding = message_part_count * 160;
540
541 let mut buffer = vec![0u8; message_length_with_padding];
542 buffer[..message_length].copy_from_slice(contents);
543 Iso7816::raw_pad(&mut buffer, message_length);
544 Ok(buffer)
545 }
546}
547
548#[expect(clippy::comparison_chain)]
549fn strip_padding_version(
550 version: u32,
551 contents: &mut Vec<u8>,
552) -> Result<(), ServiceError> {
553 if version < 2 {
554 Err(ServiceError::InvalidFrame {
555 reason: "unknown version",
556 })
557 } else if version == 2 {
558 Ok(())
559 } else {
560 strip_padding(contents)?;
561 Ok(())
562 }
563}
564
565fn strip_padding(contents: &mut Vec<u8>) -> Result<(), ServiceError> {
566 let new_length = Iso7816::raw_unpad(contents)?.len();
567 contents.resize(new_length, 0);
568 Ok(())
569}
570
571pub async fn get_preferred_protocol_address<S: SessionStore>(
573 session_store: &S,
574 address: &ServiceId,
575 device_id: DeviceId,
576) -> Result<ProtocolAddress, libsignal_protocol::error::SignalProtocolError> {
577 let address = address.to_protocol_address(device_id);
578 if session_store.load_session(&address).await?.is_some() {
579 return Ok(address);
580 }
581
582 Ok(address)
583}
584
585#[allow(clippy::too_many_arguments)]
593#[tracing::instrument(
594 skip(
595 ciphertext,
596 trust_roots,
597 identity_store,
598 session_store,
599 pre_key_store,
600 signed_pre_key_store,
601 sender_key_store,
602 kyber_pre_key_store
603 ),
604 fields(
605 ciphertext = ciphertext.len(),
606 )
607)]
608async fn sealed_sender_decrypt(
609 ciphertext: &[u8],
610 trust_roots: &[PublicKey],
611 timestamp: Timestamp,
612 local_e164: Option<String>,
613 local_uuid: String,
614 local_device_id: DeviceId,
615 identity_store: &mut dyn IdentityKeyStore,
616 session_store: &mut dyn SessionStore,
617 pre_key_store: &mut dyn PreKeyStore,
618 signed_pre_key_store: &mut dyn SignedPreKeyStore,
619 sender_key_store: &mut dyn SenderKeyStore,
620 kyber_pre_key_store: &mut dyn KyberPreKeyStore,
621) -> Result<SealedSenderDecryptionResult, SignalProtocolError> {
622 let usmc =
623 sealed_sender_decrypt_to_usmc(ciphertext, identity_store).await?;
624
625 if !usmc
626 .sender()?
627 .validate_with_trust_roots(trust_roots, timestamp)?
628 {
629 return Err(SignalProtocolError::InvalidSealedSenderMessage(
630 "trust root validation failed".to_string(),
631 ));
632 }
633
634 let is_local_uuid = local_uuid == usmc.sender()?.sender_uuid()?;
635
636 let is_local_e164 = match (local_e164, usmc.sender()?.sender_e164()?) {
637 (Some(l), Some(s)) => l == s,
638 (_, _) => false,
639 };
640
641 if (is_local_e164 || is_local_uuid)
642 && usmc.sender()?.sender_device_id()? == local_device_id
643 {
644 return Err(SignalProtocolError::SealedSenderSelfSend);
645 }
646
647 let mut rng = rng();
648
649 let remote_address = ProtocolAddress::new(
650 usmc.sender()?.sender_uuid()?.to_string(),
651 usmc.sender()?.sender_device_id()?,
652 );
653
654 let message = match usmc.msg_type()? {
655 CiphertextMessageType::Whisper => {
656 let ctext = SignalMessage::try_from(usmc.contents()?)?;
657 message_decrypt_signal(
658 &ctext,
659 &remote_address,
660 session_store,
661 identity_store,
662 &mut rng,
663 )
664 .await?
665 },
666 CiphertextMessageType::PreKey => {
667 let ctext = PreKeySignalMessage::try_from(usmc.contents()?)?;
668 message_decrypt_prekey(
669 &ctext,
670 &remote_address,
671 session_store,
672 identity_store,
673 pre_key_store,
674 signed_pre_key_store,
675 kyber_pre_key_store,
676 &mut rng,
677 )
678 .await?
679 },
680 CiphertextMessageType::SenderKey => {
681 group_decrypt(usmc.contents()?, sender_key_store, &remote_address)
682 .await?
683 },
684 msg_type => {
685 return Err(SignalProtocolError::InvalidMessage(
686 msg_type,
687 "unexpected message type for sealed_sender_decrypt",
688 ));
689 },
690 };
691
692 Ok(SealedSenderDecryptionResult {
693 sender_uuid: usmc.sender()?.sender_uuid()?.to_string(),
694 sender_e164: usmc.sender()?.sender_e164()?.map(|s| s.to_string()),
695 device_id: usmc.sender()?.sender_device_id()?,
696 message,
697 })
698}