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 client_timestamp: {:?}, \
62 content: {} bytes, \
63 }}",
64 envelope.parse_source_service_id(),
65 envelope.source_device_id(),
66 envelope.server_guid(),
67 envelope.client_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 let Some(content) = &message.content else {
112 tracing::warn!("empty decrypted content");
113 return Ok(None);
114 };
115
116 if was_plaintext {
119 let crate::proto::content::Content::DecryptionErrorMessage(dme) =
120 content
121 else {
122 tracing::error!(
123 ?envelope,
124 "Received a plaintext envelope with a non-decryption error message."
125 );
126 return Ok(None);
127 };
128 tracing::warn!(
129 ?envelope,
130 "Received a decryption error message: {}.",
131 String::from_utf8_lossy(dme)
132 );
133 }
134
135 if matches!(content, crate::proto::content::Content::SyncMessage(_))
136 && plaintext.metadata.sender.aci().map(Uuid::from)
137 != Some(local_service.raw_uuid())
138 && local_service.kind() == ServiceIdKind::Aci
139 {
140 tracing::warn!("Source is not ourself.");
141 return Ok(None);
142 }
143
144 if let Some(bytes) = &message.sender_key_distribution_message {
145 let skdm = SenderKeyDistributionMessage::try_from(&bytes[..])?;
146 process_sender_key_distribution_message(
147 &plaintext.metadata.protocol_address()?,
148 &skdm,
149 &mut self.protocol_store,
150 )
151 .await?;
152
153 match Content::from_proto(message, plaintext.metadata) {
154 Err(ServiceError::UnsupportedContent) => {
155 tracing::trace!("Sender key distribution message without additional content");
156 return Ok(None);
157 },
158 content => return Ok(Some(content?)),
159 }
160 }
161 let content = Content::from_proto(message, plaintext.metadata);
162 Ok(Some(content?))
163 } else {
164 Ok(None)
165 }
166 }
167
168 #[tracing::instrument(skip(envelope, csprng), fields(envelope = debug_envelope(envelope)))]
174 async fn decrypt<R: Rng + CryptoRng>(
175 &mut self,
176 envelope: &Envelope,
177 csprng: &mut R,
178 ) -> Result<Plaintext, ServiceError> {
179 let local_service: ServiceId =
180 ServiceId::parse_from_service_id_string(self.local_address.name())
181 .expect("valid protocol address name");
182
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 != local_service {
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 let source_service_id = envelope.parse_source_service_id();
215
216 if destination_service_id.kind() == ServiceIdKind::Pni
217 && source_service_id.is_none()
218 {
219 tracing::warn!("received sealed sender message to our PNI; ignoring invalid message");
220 return Err(ServiceError::InvalidFrame {
221 reason: "sealed sender received on our PNI",
222 });
223 }
224
225 if let Some(source_service_id) = source_service_id {
227 if source_service_id.kind() == ServiceIdKind::Pni
228 && envelope.r#type() != Type::ServerDeliveryReceipt
229 {
230 tracing::warn!("got a message from a PNI that was not a ServerDeliveryReceipt; ignoring invalid message");
231 return Err(ServiceError::InvalidFrame {
232 reason: "PNI received a non-ServerDeliveryReceipt",
233 });
234 }
235 }
236
237 let client_timestamp = chrono::DateTime::from_timestamp_millis(
241 envelope.client_timestamp() as i64,
242 )
243 .ok_or(ServiceError::InvalidFrame {
244 reason: "unparseable timestamp",
245 });
246 let server_timestamp = chrono::DateTime::from_timestamp_millis(
247 envelope.server_timestamp() as i64,
248 )
249 .ok_or(ServiceError::InvalidFrame {
250 reason: "unparseable server timestamp",
251 });
252
253 use crate::proto::envelope::Type;
254 let plaintext = match envelope.r#type() {
255 Type::PrekeyMessage => {
256 let source_service_id = source_service_id
257 .expect("prekey bundle format contains source_service_id");
258 let sender = get_preferred_protocol_address(
259 &self.protocol_store,
260 &source_service_id,
261 envelope.source_device_id().try_into()?,
262 )
263 .await?;
264 let metadata = Metadata {
265 destination: envelope
266 .parse_destination_service_id()
267 .expect("prekey bundle format"),
268 sender: source_service_id,
269 sender_device: envelope.source_device_id().try_into()?,
270 client_timestamp: client_timestamp?,
271 server_timestamp: server_timestamp?,
272 needs_receipt: false,
273 unidentified_sender: false,
274 was_plaintext: false,
275
276 server_guid,
277 };
278
279 let mut data = message_decrypt_prekey(
280 &PreKeySignalMessage::try_from(&ciphertext[..])?,
281 &sender,
282 &self.local_address,
283 &mut self.protocol_store.clone(),
284 &mut self.protocol_store.clone(),
285 &mut self.protocol_store.clone(),
286 &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_else(|| {
299 SignalProtocolError::SessionNotFound(
300 SessionNotFound::new(sender, "decrypt"),
301 )
302 })?;
303
304 strip_padding_version(
305 session_record.session_version()?,
306 &mut data,
307 )?;
308 Plaintext { metadata, data }
309 },
310 Type::PlaintextContent => {
311 tracing::warn!(?envelope, "Envelope with plaintext content. This usually indicates a decryption retry.");
312 let source_service_id = source_service_id
313 .expect("prekey bundle format contains source_service_id");
314 let metadata = Metadata {
315 destination: envelope
316 .parse_destination_service_id()
317 .expect("plaintext content format"),
318 sender: source_service_id,
319 sender_device: envelope.source_device_id().try_into()?,
320 client_timestamp: client_timestamp?,
321 server_timestamp: server_timestamp?,
322 needs_receipt: false,
323 unidentified_sender: false,
324 was_plaintext: true,
325
326 server_guid,
327 };
328 let plaintext_content =
331 PlaintextContent::try_from(&ciphertext[..])?;
332 let mut data = plaintext_content.body().to_vec();
333 strip_padding(&mut data)?;
334 Plaintext { metadata, data }
335 },
336 Type::DoubleRatchet => {
337 let source_service_id = source_service_id
338 .expect("prekey bundle format contains source_service_id");
339 let sender = get_preferred_protocol_address(
340 &self.protocol_store,
341 &source_service_id,
342 envelope.source_device_id().try_into()?,
343 )
344 .await?;
345 let metadata = Metadata {
346 destination: envelope
347 .parse_destination_service_id()
348 .expect("ciphertext envelope format"),
349 sender: envelope
350 .parse_source_service_id()
351 .expect("ciphertext envelope format"),
352 sender_device: envelope.source_device_id().try_into()?,
353 client_timestamp: client_timestamp?,
354 server_timestamp: server_timestamp?,
355 needs_receipt: false,
356 unidentified_sender: false,
357 was_plaintext: false,
358
359 server_guid,
360 };
361
362 let mut data = message_decrypt_signal(
363 &SignalMessage::try_from(&ciphertext[..])?,
364 &sender,
365 &self.local_address,
366 &mut self.protocol_store.clone(),
367 &mut self.protocol_store.clone(),
368 csprng,
369 )
370 .await?
371 .as_slice()
372 .to_vec();
373
374 let session_record = self
375 .protocol_store
376 .load_session(&sender)
377 .await?
378 .ok_or_else(|| {
379 SignalProtocolError::SessionNotFound(
380 SessionNotFound::new(sender, "decrypt"),
381 )
382 })?;
383
384 strip_padding_version(
385 session_record.session_version()?,
386 &mut data,
387 )?;
388 Plaintext { metadata, data }
389 },
390 Type::UnidentifiedSender => {
391 let SealedSenderDecryptionResult {
392 sender_uuid,
393 sender_e164: _,
394 device_id,
395 mut message,
396 } = sealed_sender_decrypt(
397 ciphertext,
398 &self.trust_roots,
399 Timestamp::from_epoch_millis(envelope.client_timestamp()),
400 None,
401 self.local_address.clone(),
402 &mut self.protocol_store.clone(),
403 &mut self.protocol_store.clone(),
404 &mut self.protocol_store.clone(),
405 &mut self.protocol_store.clone(),
406 &mut self.protocol_store.clone(),
407 &mut self.protocol_store,
408 )
409 .await?;
410
411 let Some(sender) =
412 ServiceId::parse_from_service_id_string(&sender_uuid)
413 else {
414 return Err(
415 SignalProtocolError::InvalidSealedSenderMessage(
416 "invalid sender UUID".to_string(),
417 )
418 .into(),
419 );
420 };
421
422 let needs_receipt = if source_service_id.is_some() {
423 tracing::warn!(?envelope, "Received an unidentified delivery over an identified channel. Marking needs_receipt=false");
424 false
425 } else {
426 true
427 };
428
429 if sender.kind() == ServiceIdKind::Pni {
430 tracing::warn!(
431 "sealed sender used for PNI; ignoring invalid message"
432 );
433 return Err(ServiceError::InvalidFrame {
434 reason: "sealed sender used for PNI",
435 });
436 }
437
438 let metadata = Metadata {
439 destination: envelope
440 .parse_destination_service_id()
441 .expect("unidentified sender envelope format"),
442 sender,
443 sender_device: device_id,
444 client_timestamp: client_timestamp?,
445 server_timestamp: server_timestamp?,
446 unidentified_sender: true,
447 needs_receipt,
448 was_plaintext: false,
449
450 server_guid,
451 };
452
453 strip_padding(&mut message)?;
454
455 Plaintext {
456 metadata,
457 data: message,
458 }
459 },
460 _ => {
461 return Err(ServiceError::InvalidFrame {
463 reason: "envelope has unknown type",
464 });
465 },
466 };
467 Ok(plaintext)
468 }
469
470 #[tracing::instrument(
471 skip(address, unidentified_access, content, csprng),
472 fields(
473 address = %address,
474 with_unidentified_access = unidentified_access.is_some(),
475 content_length = content.len(),
476 )
477 )]
478 pub(crate) async fn encrypt<R: Rng + CryptoRng>(
479 &mut self,
480 address: &ProtocolAddress,
481 unidentified_access: Option<&SenderCertificate>,
482 content: &[u8],
483 csprng: &mut R,
484 ) -> Result<OutgoingPushMessage, ServiceError> {
485 let mut rng = rng();
486
487 let session_record = self
488 .protocol_store
489 .load_session(address)
490 .await?
491 .ok_or_else(|| {
492 SignalProtocolError::SessionNotFound(SessionNotFound::new(
493 address.clone(),
494 "encrypt",
495 ))
496 })?;
497
498 let record_usable = session_record
499 .has_usable_sender_chain(
500 SystemTime::now(),
501 SessionUsabilityRequirements::NotStale,
502 )
503 .unwrap_or(false);
504 if !record_usable {
505 Err(SignalProtocolError::SessionNotFound(SessionNotFound::new(
506 address.clone(),
507 "encrypt",
508 )))?;
509 }
510
511 let padded_content =
512 add_padding(session_record.session_version()?, content)?;
513
514 if let Some(unindentified_access) = unidentified_access {
515 let destination_registration_id =
516 session_record.remote_registration_id()?;
517
518 let message = sealed_sender_encrypt(
519 address,
520 unindentified_access,
521 &padded_content,
522 &mut self.protocol_store.clone(),
523 &mut self.protocol_store,
524 SystemTime::now(),
525 csprng,
526 )
527 .await?;
528
529 use crate::proto::envelope::Type;
530 Ok(OutgoingPushMessage {
531 r#type: Type::UnidentifiedSender as u32,
532 destination_device_id: address.device_id(),
533 destination_registration_id,
534 content: BASE64_RELAXED.encode(message),
535 })
536 } else {
537 let message = message_encrypt(
538 &padded_content,
539 address,
540 &self.local_address,
541 &mut self.protocol_store.clone(),
542 &mut self.protocol_store.clone(),
543 SystemTime::now(),
544 &mut rng,
545 )
546 .await?;
547
548 let destination_registration_id =
549 session_record.remote_registration_id()?;
550
551 let body = BASE64_RELAXED.encode(message.serialize());
552
553 use crate::proto::envelope::Type;
554 let message_type = match message.message_type() {
555 CiphertextMessageType::PreKey => Type::PrekeyMessage,
556 CiphertextMessageType::Whisper => Type::DoubleRatchet,
557 t => panic!("Bad type: {:?}", t),
558 } as u32;
559 Ok(OutgoingPushMessage {
560 r#type: message_type,
561 destination_device_id: address.device_id(),
562 destination_registration_id,
563 content: body,
564 })
565 }
566 }
567}
568
569struct Plaintext {
570 metadata: Metadata,
571 data: Vec<u8>,
572}
573
574#[expect(clippy::comparison_chain)]
575fn add_padding(version: u32, contents: &[u8]) -> Result<Vec<u8>, ServiceError> {
576 if version < 2 {
577 Err(ServiceError::PaddingVersion(version))
578 } else if version == 2 {
579 Ok(contents.to_vec())
580 } else {
581 let message_length = contents.len();
582 let message_length_with_terminator = contents.len() + 1;
583 let mut message_part_count = message_length_with_terminator / 160;
584 if !message_length_with_terminator.is_multiple_of(160) {
585 message_part_count += 1;
586 }
587
588 let message_length_with_padding = message_part_count * 160;
589
590 let mut buffer = vec![0u8; message_length_with_padding];
591 buffer[..message_length].copy_from_slice(contents);
592 Iso7816::raw_pad(&mut buffer, message_length);
593 Ok(buffer)
594 }
595}
596
597#[expect(clippy::comparison_chain)]
598fn strip_padding_version(
599 version: u32,
600 contents: &mut Vec<u8>,
601) -> Result<(), ServiceError> {
602 if version < 2 {
603 Err(ServiceError::InvalidFrame {
604 reason: "unknown version",
605 })
606 } else if version == 2 {
607 Ok(())
608 } else {
609 strip_padding(contents)?;
610 Ok(())
611 }
612}
613
614fn strip_padding(contents: &mut Vec<u8>) -> Result<(), ServiceError> {
615 let new_length = Iso7816::raw_unpad(contents)?.len();
616 contents.resize(new_length, 0);
617 Ok(())
618}
619
620pub async fn get_preferred_protocol_address<S: SessionStore>(
622 session_store: &S,
623 address: &ServiceId,
624 device_id: DeviceId,
625) -> Result<ProtocolAddress, libsignal_protocol::error::SignalProtocolError> {
626 let address = address.to_protocol_address(device_id);
627 if session_store.load_session(&address).await?.is_some() {
628 return Ok(address);
629 }
630
631 Ok(address)
632}
633
634#[derive(thiserror::Error)]
640#[error("error: {inner}, usmc: {}", sender.is_some())]
641pub struct SealedSenderDecryptionError {
642 pub inner: SignalProtocolError,
643 pub sender: Option<ProtocolAddress>,
644}
645
646impl fmt::Debug for SealedSenderDecryptionError {
647 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
648 f.debug_struct("SealedSenderDecryptionError")
649 .field("inner", &self.inner)
650 .field("sender", &self.sender)
651 .finish()
652 }
653}
654
655impl From<SignalProtocolError> for SealedSenderDecryptionError {
656 fn from(e: SignalProtocolError) -> Self {
657 SealedSenderDecryptionError {
658 inner: e,
659 sender: None,
660 }
661 }
662}
663
664#[allow(clippy::too_many_arguments)]
672#[tracing::instrument(
673 skip(
674 ciphertext,
675 trust_roots,
676 identity_store,
677 session_store,
678 pre_key_store,
679 signed_pre_key_store,
680 sender_key_store,
681 kyber_pre_key_store
682 ),
683 fields(
684 ciphertext = ciphertext.len(),
685 )
686)]
687async fn sealed_sender_decrypt(
688 ciphertext: &[u8],
689 trust_roots: &[PublicKey],
690 timestamp: Timestamp,
691 local_e164: Option<String>,
692 local_address: ProtocolAddress,
693 identity_store: &mut dyn IdentityKeyStore,
694 session_store: &mut dyn SessionStore,
695 pre_key_store: &mut dyn PreKeyStore,
696 signed_pre_key_store: &mut dyn SignedPreKeyStore,
697 sender_key_store: &mut dyn SenderKeyStore,
698 kyber_pre_key_store: &mut dyn KyberPreKeyStore,
699) -> Result<SealedSenderDecryptionResult, SealedSenderDecryptionError> {
700 let usmc =
701 sealed_sender_decrypt_to_usmc(ciphertext, identity_store).await?;
702
703 if !usmc
704 .sender()?
705 .validate_with_trust_roots(trust_roots, timestamp)?
706 {
707 return Err(SignalProtocolError::InvalidSealedSenderMessage(
708 "trust root validation failed".to_string(),
709 )
710 .into());
711 }
712
713 let local_service_id =
714 ServiceId::parse_from_service_id_string(local_address.name())
715 .expect("valid protocol address name");
716 let is_local_uuid = local_service_id.raw_uuid()
717 == usmc
718 .sender()?
719 .sender_uuid()?
720 .parse::<Uuid>()
721 .expect("valid uuid");
723
724 let is_local_e164 = match (local_e164, usmc.sender()?.sender_e164()?) {
725 (Some(l), Some(s)) => l == s,
726 (_, _) => false,
727 };
728
729 if (is_local_e164 || is_local_uuid)
730 && usmc.sender()?.sender_device_id()? == local_address.device_id()
731 {
732 return Err(SignalProtocolError::SealedSenderSelfSend.into());
733 }
734
735 let remote_address = ProtocolAddress::new(
736 usmc.sender()?.sender_uuid()?.to_string(),
737 usmc.sender()?.sender_device_id()?,
738 );
739
740 sealed_sender_decrypt_with_validated_usmc(
741 &usmc,
742 &remote_address,
743 &local_address,
744 identity_store,
745 session_store,
746 pre_key_store,
747 signed_pre_key_store,
748 sender_key_store,
749 kyber_pre_key_store,
750 )
751 .await
752 .map_err(|inner| SealedSenderDecryptionError {
753 inner,
754 sender: Some(remote_address),
755 })
756}
757
758#[allow(clippy::too_many_arguments)]
759async fn sealed_sender_decrypt_with_validated_usmc(
760 usmc: &UnidentifiedSenderMessageContent,
761 remote_address: &ProtocolAddress,
762 local_address: &ProtocolAddress,
763 identity_store: &mut dyn IdentityKeyStore,
764 session_store: &mut dyn SessionStore,
765 pre_key_store: &mut dyn PreKeyStore,
766 signed_pre_key_store: &mut dyn SignedPreKeyStore,
767 sender_key_store: &mut dyn SenderKeyStore,
768 kyber_pre_key_store: &mut dyn KyberPreKeyStore,
769) -> Result<SealedSenderDecryptionResult, SignalProtocolError> {
770 let mut rng = rng();
771
772 let message = match usmc.msg_type()? {
773 CiphertextMessageType::Whisper => {
774 let ctext = SignalMessage::try_from(usmc.contents()?)?;
775 message_decrypt_signal(
776 &ctext,
777 remote_address,
778 local_address,
779 session_store,
780 identity_store,
781 &mut rng,
782 )
783 .await?
784 },
785 CiphertextMessageType::PreKey => {
786 let ctext = PreKeySignalMessage::try_from(usmc.contents()?)?;
787 message_decrypt_prekey(
788 &ctext,
789 remote_address,
790 local_address,
791 session_store,
792 identity_store,
793 pre_key_store,
794 signed_pre_key_store,
795 kyber_pre_key_store,
796 &mut rng,
797 )
798 .await?
799 },
800 CiphertextMessageType::SenderKey => {
801 group_decrypt(usmc.contents()?, sender_key_store, remote_address)
802 .await?
803 },
804 CiphertextMessageType::Plaintext => {
805 let plaintext_content =
808 PlaintextContent::try_from(usmc.contents()?)?;
809 plaintext_content.body().to_vec()
810 },
811 };
812
813 Ok(SealedSenderDecryptionResult {
814 sender_uuid: usmc.sender()?.sender_uuid()?.to_string(),
815 sender_e164: usmc.sender()?.sender_e164()?.map(|s| s.to_string()),
816 device_id: usmc.sender()?.sender_device_id()?,
817 message,
818 })
819}