1use std::{convert::TryFrom, fmt, time::SystemTime};
2
3use aes::cipher::block_padding::{Iso7816, Padding};
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 PlaintextContent, PreKeySignalMessage, PreKeyStore, ProtocolAddress,
12 ProtocolStore, PublicKey, SealedSenderDecryptionResult, SenderCertificate,
13 SenderKeyDistributionMessage, SenderKeyStore, ServiceId, SessionNotFound,
14 SessionStore, SessionUsabilityRequirements, SignalMessage,
15 SignalProtocolError, SignedPreKeyStore, Timestamp,
16 UnidentifiedSenderMessageContent,
17};
18use prost::Message;
19use rand::{rng, CryptoRng, Rng};
20use uuid::Uuid;
21
22use crate::{
23 content::{Content, Metadata},
24 envelope::Envelope,
25 push_service::ServiceError,
26 sender::OutgoingPushMessage,
27 session_store::SessionStoreExt,
28 utils::BASE64_RELAXED,
29 ServiceIdExt,
30};
31
32#[derive(Clone)]
36pub struct ServiceCipher<S> {
37 protocol_store: S,
38 trust_roots: Vec<PublicKey>,
39 local_address: ProtocolAddress,
40}
41
42impl<S> fmt::Debug for ServiceCipher<S> {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 f.debug_struct("ServiceCipher")
45 .field("protocol_store", &"...")
46 .field("trust_root", &"...")
47 .field("local_address", &self.local_address)
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.parse_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_address: ProtocolAddress,
81 ) -> Self {
82 Self {
83 protocol_store,
84 trust_roots,
85 local_address,
86 }
87 }
88
89 #[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 let local_service: ServiceId =
99 ServiceId::parse_from_service_id_string(self.local_address.name())
100 .expect("valid protocol address name");
101
102 if envelope.content.is_some() {
103 let plaintext = self.decrypt(&envelope, csprng).await?;
104 let was_plaintext = plaintext.metadata.was_plaintext;
105 let message =
106 crate::proto::Content::decode(plaintext.data.as_slice())?;
107
108 tracing::Span::current()
109 .record("envelope_metadata", plaintext.metadata.to_string());
110
111 if was_plaintext {
114 if let crate::proto::Content {
115 data_message: None,
116 sync_message: None,
117 call_message: None,
118 null_message: None,
119 receipt_message: None,
120 typing_message: None,
121 sender_key_distribution_message: None,
122 decryption_error_message: Some(decryption_error_message),
123 story_message: None,
124 pni_signature_message: None,
125 edit_message: None,
126 } = &message
127 {
128 tracing::warn!(
129 ?envelope,
130 "Received a decryption error message: {}.",
131 String::from_utf8_lossy(decryption_error_message)
132 );
133 } else {
134 tracing::error!(
135 ?envelope,
136 "Received a plaintext envelope with a non-decryption error message."
137 );
138 return Ok(None);
139 }
140 }
141
142 if message.sync_message.is_some()
143 && plaintext.metadata.sender.aci().map(Uuid::from)
144 != Some(local_service.raw_uuid())
145 && local_service.kind() == ServiceIdKind::Aci
146 {
147 tracing::warn!("Source is not ourself.");
148 return Ok(None);
149 }
150
151 if let Some(bytes) = &message.sender_key_distribution_message {
152 let skdm = SenderKeyDistributionMessage::try_from(&bytes[..])?;
153 process_sender_key_distribution_message(
154 &plaintext.metadata.protocol_address()?,
155 &skdm,
156 &mut self.protocol_store,
157 )
158 .await?;
159
160 match Content::from_proto(message, plaintext.metadata) {
161 Err(ServiceError::UnsupportedContent) => {
162 tracing::trace!("Sender key distribution message without additional content");
163 return Ok(None);
164 },
165 content => return Ok(Some(content?)),
166 }
167 }
168 let content = Content::from_proto(message, plaintext.metadata);
169 Ok(Some(content?))
170 } else {
171 Ok(None)
172 }
173 }
174
175 #[tracing::instrument(skip(envelope, csprng), fields(envelope = debug_envelope(envelope)))]
181 async fn decrypt<R: Rng + CryptoRng>(
182 &mut self,
183 envelope: &Envelope,
184 csprng: &mut R,
185 ) -> Result<Plaintext, ServiceError> {
186 let local_service: ServiceId =
187 ServiceId::parse_from_service_id_string(self.local_address.name())
188 .expect("valid protocol address name");
189
190 let ciphertext = if let Some(msg) = envelope.content.as_ref() {
191 msg
192 } else {
193 return Err(ServiceError::InvalidFrame {
194 reason:
195 "envelope should have either a legacy message or content.",
196 });
197 };
198
199 let server_guid = envelope.parse_server_guid();
200
201 let Some(destination_service_id) =
202 envelope.parse_destination_service_id()
203 else {
204 tracing::warn!(
205 "missing destination service id; ignoring invalid message."
206 );
207 return Err(ServiceError::InvalidFrame {
208 reason: "missing destination service id",
209 });
210 };
211
212 if destination_service_id != local_service {
213 tracing::warn!(
214 "mismatching destination service id; ignoring invalid message."
215 );
216 return Err(ServiceError::InvalidFrame {
217 reason: "mismatch destination service id",
218 });
219 }
220
221 let source_service_id = envelope.parse_source_service_id();
222
223 if destination_service_id.kind() == ServiceIdKind::Pni
224 && source_service_id.is_none()
225 {
226 tracing::warn!("received sealed sender message to our PNI; ignoring invalid message");
227 return Err(ServiceError::InvalidFrame {
228 reason: "sealed sender received on our PNI",
229 });
230 }
231
232 if let Some(source_service_id) = source_service_id {
234 if source_service_id.kind() == ServiceIdKind::Pni
235 && envelope.r#type() != Type::ServerDeliveryReceipt
236 {
237 tracing::warn!("got a message from a PNI that was not a ServerDeliveryReceipt; ignoring invalid message");
238 return Err(ServiceError::InvalidFrame {
239 reason: "PNI received a non-ServerDeliveryReceipt",
240 });
241 }
242 }
243
244 let timestamp = chrono::DateTime::from_timestamp_millis(
248 envelope.timestamp() as i64,
249 )
250 .ok_or(ServiceError::InvalidFrame {
251 reason: "unparseable timestamp",
252 });
253 let server_timestamp = chrono::DateTime::from_timestamp_millis(
254 envelope.server_timestamp() as i64,
255 )
256 .ok_or(ServiceError::InvalidFrame {
257 reason: "unparseable server timestamp",
258 });
259
260 use crate::proto::envelope::Type;
261 let plaintext = match envelope.r#type() {
262 Type::PrekeyBundle => {
263 let source_service_id = source_service_id
264 .expect("prekey bundle format contains source_service_id");
265 let sender = get_preferred_protocol_address(
266 &self.protocol_store,
267 &source_service_id,
268 envelope.source_device().try_into()?,
269 )
270 .await?;
271 let metadata = Metadata {
272 destination: envelope
273 .parse_destination_service_id()
274 .expect("prekey bundle format"),
275 sender: source_service_id,
276 sender_device: envelope.source_device().try_into()?,
277 timestamp: timestamp?,
278 server_timestamp: server_timestamp?,
279 needs_receipt: false,
280 unidentified_sender: false,
281 was_plaintext: false,
282
283 server_guid,
284 };
285
286 let mut data = message_decrypt_prekey(
287 &PreKeySignalMessage::try_from(&ciphertext[..])?,
288 &sender,
289 &self.local_address,
290 &mut self.protocol_store.clone(),
291 &mut self.protocol_store.clone(),
292 &mut self.protocol_store.clone(),
293 &self.protocol_store.clone(),
294 &mut self.protocol_store.clone(),
295 csprng,
296 )
297 .await?
298 .as_slice()
299 .to_vec();
300
301 let session_record = self
302 .protocol_store
303 .load_session(&sender)
304 .await?
305 .ok_or_else(|| {
306 SignalProtocolError::SessionNotFound(
307 SessionNotFound::new(sender, "decrypt"),
308 )
309 })?;
310
311 strip_padding_version(
312 session_record.session_version()?,
313 &mut data,
314 )?;
315 Plaintext { metadata, data }
316 },
317 Type::PlaintextContent => {
318 tracing::warn!(?envelope, "Envelope with plaintext content. This usually indicates a decryption retry.");
319 let source_service_id = source_service_id
320 .expect("prekey bundle format contains source_service_id");
321 let metadata = Metadata {
322 destination: envelope
323 .parse_destination_service_id()
324 .expect("plaintext content format"),
325 sender: source_service_id,
326 sender_device: envelope.source_device().try_into()?,
327 timestamp: timestamp?,
328 server_timestamp: server_timestamp?,
329 needs_receipt: false,
330 unidentified_sender: false,
331 was_plaintext: true,
332
333 server_guid,
334 };
335 let plaintext_content =
338 PlaintextContent::try_from(&ciphertext[..])?;
339 let mut data = plaintext_content.body().to_vec();
340 strip_padding(&mut data)?;
341 Plaintext { metadata, data }
342 },
343 Type::Ciphertext => {
344 let source_service_id = source_service_id
345 .expect("prekey bundle format contains source_service_id");
346 let sender = get_preferred_protocol_address(
347 &self.protocol_store,
348 &source_service_id,
349 envelope.source_device().try_into()?,
350 )
351 .await?;
352 let metadata = Metadata {
353 destination: envelope
354 .parse_destination_service_id()
355 .expect("ciphertext envelope format"),
356 sender: envelope
357 .parse_source_service_id()
358 .expect("ciphertext envelope format"),
359 sender_device: envelope.source_device().try_into()?,
360 timestamp: timestamp?,
361 server_timestamp: server_timestamp?,
362 needs_receipt: false,
363 unidentified_sender: false,
364 was_plaintext: false,
365
366 server_guid,
367 };
368
369 let mut data = message_decrypt_signal(
370 &SignalMessage::try_from(&ciphertext[..])?,
371 &sender,
372 &self.local_address,
373 &mut self.protocol_store.clone(),
374 &mut self.protocol_store.clone(),
375 csprng,
376 )
377 .await?
378 .as_slice()
379 .to_vec();
380
381 let session_record = self
382 .protocol_store
383 .load_session(&sender)
384 .await?
385 .ok_or_else(|| {
386 SignalProtocolError::SessionNotFound(
387 SessionNotFound::new(sender, "decrypt"),
388 )
389 })?;
390
391 strip_padding_version(
392 session_record.session_version()?,
393 &mut data,
394 )?;
395 Plaintext { metadata, data }
396 },
397 Type::UnidentifiedSender => {
398 let SealedSenderDecryptionResult {
399 sender_uuid,
400 sender_e164: _,
401 device_id,
402 mut message,
403 } = sealed_sender_decrypt(
404 ciphertext,
405 &self.trust_roots,
406 Timestamp::from_epoch_millis(envelope.timestamp()),
407 None,
408 self.local_address.clone(),
409 &mut self.protocol_store.clone(),
410 &mut self.protocol_store.clone(),
411 &mut self.protocol_store.clone(),
412 &mut self.protocol_store.clone(),
413 &mut self.protocol_store.clone(),
414 &mut self.protocol_store,
415 )
416 .await?;
417
418 let Some(sender) =
419 ServiceId::parse_from_service_id_string(&sender_uuid)
420 else {
421 return Err(
422 SignalProtocolError::InvalidSealedSenderMessage(
423 "invalid sender UUID".to_string(),
424 )
425 .into(),
426 );
427 };
428
429 let needs_receipt = if source_service_id.is_some() {
430 tracing::warn!(?envelope, "Received an unidentified delivery over an identified channel. Marking needs_receipt=false");
431 false
432 } else {
433 true
434 };
435
436 if sender.kind() == ServiceIdKind::Pni {
437 tracing::warn!(
438 "sealed sender used for PNI; ignoring invalid message"
439 );
440 return Err(ServiceError::InvalidFrame {
441 reason: "sealed sender used for PNI",
442 });
443 }
444
445 let metadata = Metadata {
446 destination: envelope
447 .parse_destination_service_id()
448 .expect("unidentified sender envelope format"),
449 sender,
450 sender_device: device_id,
451 timestamp: timestamp?,
452 server_timestamp: server_timestamp?,
453 unidentified_sender: true,
454 needs_receipt,
455 was_plaintext: false,
456
457 server_guid,
458 };
459
460 strip_padding(&mut message)?;
461
462 Plaintext {
463 metadata,
464 data: message,
465 }
466 },
467 _ => {
468 return Err(ServiceError::InvalidFrame {
470 reason: "envelope has unknown type",
471 });
472 },
473 };
474 Ok(plaintext)
475 }
476
477 #[tracing::instrument(
478 skip(address, unidentified_access, content, csprng),
479 fields(
480 address = %address,
481 with_unidentified_access = unidentified_access.is_some(),
482 content_length = content.len(),
483 )
484 )]
485 pub(crate) async fn encrypt<R: Rng + CryptoRng>(
486 &mut self,
487 address: &ProtocolAddress,
488 unidentified_access: Option<&SenderCertificate>,
489 content: &[u8],
490 csprng: &mut R,
491 ) -> Result<OutgoingPushMessage, ServiceError> {
492 let mut rng = rng();
493
494 let session_record = self
495 .protocol_store
496 .load_session(address)
497 .await?
498 .ok_or_else(|| {
499 SignalProtocolError::SessionNotFound(SessionNotFound::new(
500 address.clone(),
501 "encrypt",
502 ))
503 })?;
504
505 let record_usable = session_record
506 .has_usable_sender_chain(
507 SystemTime::now(),
508 SessionUsabilityRequirements::NotStale,
509 )
510 .unwrap_or(false);
511 if !record_usable {
512 Err(SignalProtocolError::SessionNotFound(SessionNotFound::new(
513 address.clone(),
514 "encrypt",
515 )))?;
516 }
517
518 let padded_content =
519 add_padding(session_record.session_version()?, content)?;
520
521 if let Some(unindentified_access) = unidentified_access {
522 let destination_registration_id =
523 session_record.remote_registration_id()?;
524
525 let message = sealed_sender_encrypt(
526 address,
527 unindentified_access,
528 &padded_content,
529 &mut self.protocol_store.clone(),
530 &mut self.protocol_store,
531 SystemTime::now(),
532 csprng,
533 )
534 .await?;
535
536 use crate::proto::envelope::Type;
537 Ok(OutgoingPushMessage {
538 r#type: Type::UnidentifiedSender as u32,
539 destination_device_id: address.device_id(),
540 destination_registration_id,
541 content: BASE64_RELAXED.encode(message),
542 })
543 } else {
544 let message = message_encrypt(
545 &padded_content,
546 address,
547 &self.local_address,
548 &mut self.protocol_store.clone(),
549 &mut self.protocol_store.clone(),
550 SystemTime::now(),
551 &mut rng,
552 )
553 .await?;
554
555 let destination_registration_id =
556 session_record.remote_registration_id()?;
557
558 let body = BASE64_RELAXED.encode(message.serialize());
559
560 use crate::proto::envelope::Type;
561 let message_type = match message.message_type() {
562 CiphertextMessageType::PreKey => Type::PrekeyBundle,
563 CiphertextMessageType::Whisper => Type::Ciphertext,
564 t => panic!("Bad type: {:?}", t),
565 } as u32;
566 Ok(OutgoingPushMessage {
567 r#type: message_type,
568 destination_device_id: address.device_id(),
569 destination_registration_id,
570 content: body,
571 })
572 }
573 }
574}
575
576struct Plaintext {
577 metadata: Metadata,
578 data: Vec<u8>,
579}
580
581#[expect(clippy::comparison_chain)]
582fn add_padding(version: u32, contents: &[u8]) -> Result<Vec<u8>, ServiceError> {
583 if version < 2 {
584 Err(ServiceError::PaddingVersion(version))
585 } else if version == 2 {
586 Ok(contents.to_vec())
587 } else {
588 let message_length = contents.len();
589 let message_length_with_terminator = contents.len() + 1;
590 let mut message_part_count = message_length_with_terminator / 160;
591 if !message_length_with_terminator.is_multiple_of(160) {
592 message_part_count += 1;
593 }
594
595 let message_length_with_padding = message_part_count * 160;
596
597 let mut buffer = vec![0u8; message_length_with_padding];
598 buffer[..message_length].copy_from_slice(contents);
599 Iso7816::raw_pad(&mut buffer, message_length);
600 Ok(buffer)
601 }
602}
603
604#[expect(clippy::comparison_chain)]
605fn strip_padding_version(
606 version: u32,
607 contents: &mut Vec<u8>,
608) -> Result<(), ServiceError> {
609 if version < 2 {
610 Err(ServiceError::InvalidFrame {
611 reason: "unknown version",
612 })
613 } else if version == 2 {
614 Ok(())
615 } else {
616 strip_padding(contents)?;
617 Ok(())
618 }
619}
620
621fn strip_padding(contents: &mut Vec<u8>) -> Result<(), ServiceError> {
622 let new_length = Iso7816::raw_unpad(contents)?.len();
623 contents.resize(new_length, 0);
624 Ok(())
625}
626
627pub async fn get_preferred_protocol_address<S: SessionStore>(
629 session_store: &S,
630 address: &ServiceId,
631 device_id: DeviceId,
632) -> Result<ProtocolAddress, libsignal_protocol::error::SignalProtocolError> {
633 let address = address.to_protocol_address(device_id);
634 if session_store.load_session(&address).await?.is_some() {
635 return Ok(address);
636 }
637
638 Ok(address)
639}
640
641#[derive(thiserror::Error)]
647#[error("error: {inner}, usmc: {}", sender.is_some())]
648pub struct SealedSenderDecryptionError {
649 pub inner: SignalProtocolError,
650 pub sender: Option<ProtocolAddress>,
651}
652
653impl fmt::Debug for SealedSenderDecryptionError {
654 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
655 f.debug_struct("SealedSenderDecryptionError")
656 .field("inner", &self.inner)
657 .field("sender", &self.sender)
658 .finish()
659 }
660}
661
662impl From<SignalProtocolError> for SealedSenderDecryptionError {
663 fn from(e: SignalProtocolError) -> Self {
664 SealedSenderDecryptionError {
665 inner: e,
666 sender: None,
667 }
668 }
669}
670
671#[allow(clippy::too_many_arguments)]
679#[tracing::instrument(
680 skip(
681 ciphertext,
682 trust_roots,
683 identity_store,
684 session_store,
685 pre_key_store,
686 signed_pre_key_store,
687 sender_key_store,
688 kyber_pre_key_store
689 ),
690 fields(
691 ciphertext = ciphertext.len(),
692 )
693)]
694async fn sealed_sender_decrypt(
695 ciphertext: &[u8],
696 trust_roots: &[PublicKey],
697 timestamp: Timestamp,
698 local_e164: Option<String>,
699 local_address: ProtocolAddress,
700 identity_store: &mut dyn IdentityKeyStore,
701 session_store: &mut dyn SessionStore,
702 pre_key_store: &mut dyn PreKeyStore,
703 signed_pre_key_store: &mut dyn SignedPreKeyStore,
704 sender_key_store: &mut dyn SenderKeyStore,
705 kyber_pre_key_store: &mut dyn KyberPreKeyStore,
706) -> Result<SealedSenderDecryptionResult, SealedSenderDecryptionError> {
707 let usmc =
708 sealed_sender_decrypt_to_usmc(ciphertext, identity_store).await?;
709
710 if !usmc
711 .sender()?
712 .validate_with_trust_roots(trust_roots, timestamp)?
713 {
714 return Err(SignalProtocolError::InvalidSealedSenderMessage(
715 "trust root validation failed".to_string(),
716 )
717 .into());
718 }
719
720 let local_service_id =
721 ServiceId::parse_from_service_id_string(local_address.name())
722 .expect("valid protocol address name");
723 let is_local_uuid = local_service_id.raw_uuid()
724 == usmc
725 .sender()?
726 .sender_uuid()?
727 .parse::<Uuid>()
728 .expect("valid uuid");
730
731 let is_local_e164 = match (local_e164, usmc.sender()?.sender_e164()?) {
732 (Some(l), Some(s)) => l == s,
733 (_, _) => false,
734 };
735
736 if (is_local_e164 || is_local_uuid)
737 && usmc.sender()?.sender_device_id()? == local_address.device_id()
738 {
739 return Err(SignalProtocolError::SealedSenderSelfSend.into());
740 }
741
742 let remote_address = ProtocolAddress::new(
743 usmc.sender()?.sender_uuid()?.to_string(),
744 usmc.sender()?.sender_device_id()?,
745 );
746
747 sealed_sender_decrypt_with_validated_usmc(
748 &usmc,
749 &remote_address,
750 &local_address,
751 identity_store,
752 session_store,
753 pre_key_store,
754 signed_pre_key_store,
755 sender_key_store,
756 kyber_pre_key_store,
757 )
758 .await
759 .map_err(|inner| SealedSenderDecryptionError {
760 inner,
761 sender: Some(remote_address),
762 })
763}
764
765#[allow(clippy::too_many_arguments)]
766async fn sealed_sender_decrypt_with_validated_usmc(
767 usmc: &UnidentifiedSenderMessageContent,
768 remote_address: &ProtocolAddress,
769 local_address: &ProtocolAddress,
770 identity_store: &mut dyn IdentityKeyStore,
771 session_store: &mut dyn SessionStore,
772 pre_key_store: &mut dyn PreKeyStore,
773 signed_pre_key_store: &mut dyn SignedPreKeyStore,
774 sender_key_store: &mut dyn SenderKeyStore,
775 kyber_pre_key_store: &mut dyn KyberPreKeyStore,
776) -> Result<SealedSenderDecryptionResult, SignalProtocolError> {
777 let mut rng = rng();
778
779 let message = match usmc.msg_type()? {
780 CiphertextMessageType::Whisper => {
781 let ctext = SignalMessage::try_from(usmc.contents()?)?;
782 message_decrypt_signal(
783 &ctext,
784 remote_address,
785 local_address,
786 session_store,
787 identity_store,
788 &mut rng,
789 )
790 .await?
791 },
792 CiphertextMessageType::PreKey => {
793 let ctext = PreKeySignalMessage::try_from(usmc.contents()?)?;
794 message_decrypt_prekey(
795 &ctext,
796 remote_address,
797 local_address,
798 session_store,
799 identity_store,
800 pre_key_store,
801 signed_pre_key_store,
802 kyber_pre_key_store,
803 &mut rng,
804 )
805 .await?
806 },
807 CiphertextMessageType::SenderKey => {
808 group_decrypt(usmc.contents()?, sender_key_store, remote_address)
809 .await?
810 },
811 CiphertextMessageType::Plaintext => {
812 let plaintext_content =
815 PlaintextContent::try_from(usmc.contents()?)?;
816 plaintext_content.body().to_vec()
817 },
818 };
819
820 Ok(SealedSenderDecryptionResult {
821 sender_uuid: usmc.sender()?.sender_uuid()?.to_string(),
822 sender_e164: usmc.sender()?.sender_e164()?.map(|s| s.to_string()),
823 device_id: usmc.sender()?.sender_device_id()?,
824 message,
825 })
826}