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