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