1use std::{convert::TryFrom, fmt, time::SystemTime};
2
3use aes::cipher::block_padding::{Iso7816, RawPadding};
4use base64::prelude::*;
5use libsignal_protocol::{
6 group_decrypt, message_decrypt_prekey, message_decrypt_signal,
7 message_encrypt, process_sender_key_distribution_message,
8 sealed_sender_decrypt_to_usmc, sealed_sender_encrypt,
9 CiphertextMessageType, DeviceId, IdentityKeyStore, KyberPreKeyStore,
10 PreKeySignalMessage, PreKeyStore, ProtocolAddress, ProtocolStore,
11 PublicKey, SealedSenderDecryptionResult, SenderCertificate,
12 SenderKeyDistributionMessage, SenderKeyStore, ServiceId, SessionStore,
13 SignalMessage, SignalProtocolError, SignedPreKeyStore, Timestamp,
14 UsePQRatchet,
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#[derive(Clone)]
33pub struct ServiceCipher<S> {
34 protocol_store: S,
35 trust_root: PublicKey,
36 local_uuid: Uuid,
37 local_device_id: DeviceId,
38}
39
40impl<S> fmt::Debug for ServiceCipher<S> {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 f.debug_struct("ServiceCipher")
43 .field("protocol_store", &"...")
44 .field("trust_root", &"...")
45 .field("local_uuid", &self.local_uuid)
46 .field("local_device_id", &self.local_device_id)
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_root: PublicKey,
79 local_uuid: Uuid,
80 local_device_id: DeviceId,
81 ) -> Self {
82 Self {
83 protocol_store,
84 trust_root,
85 local_uuid,
86 local_device_id,
87 }
88 }
89
90 #[tracing::instrument(skip(envelope, csprng), fields(envelope = debug_envelope(&envelope)))]
94 pub async fn open_envelope<R: Rng + CryptoRng>(
95 &mut self,
96 envelope: Envelope,
97 csprng: &mut R,
98 ) -> Result<Option<Content>, ServiceError> {
99 if envelope.content.is_some() {
100 let plaintext = self.decrypt(&envelope, csprng).await?;
101 let was_plaintext = plaintext.metadata.was_plaintext;
102 let message =
103 crate::proto::Content::decode(plaintext.data.as_slice())?;
104
105 tracing::Span::current()
106 .record("envelope_metadata", plaintext.metadata.to_string());
107
108 if was_plaintext {
111 if let crate::proto::Content {
112 data_message: None,
113 sync_message: None,
114 call_message: None,
115 null_message: None,
116 receipt_message: None,
117 typing_message: None,
118 sender_key_distribution_message: None,
119 decryption_error_message: Some(decryption_error_message),
120 story_message: None,
121 pni_signature_message: None,
122 edit_message: None,
123 } = &message
124 {
125 tracing::warn!(
126 ?envelope,
127 "Received a decryption error message: {}.",
128 String::from_utf8_lossy(decryption_error_message)
129 );
130 } else {
131 tracing::error!(
132 ?envelope,
133 "Received a plaintext envelope with a non-decryption error message."
134 );
135 return Ok(None);
136 }
137 }
138
139 if message.sync_message.is_some()
140 && plaintext.metadata.sender.aci().map(Into::into)
141 != Some(self.local_uuid)
142 {
143 tracing::warn!("Source is not ourself.");
144 return Ok(None);
145 }
146
147 if let Some(bytes) = message.sender_key_distribution_message {
148 let skdm = SenderKeyDistributionMessage::try_from(&bytes[..])?;
149 process_sender_key_distribution_message(
150 &plaintext.metadata.protocol_address()?,
151 &skdm,
152 &mut self.protocol_store,
153 )
154 .await?;
155 Ok(None)
156 } else {
157 let content = Content::from_proto(message, plaintext.metadata)?;
158 Ok(Some(content))
159 }
160 } else {
161 Ok(None)
162 }
163 }
164
165 #[tracing::instrument(skip(envelope, csprng), fields(envelope = debug_envelope(envelope)))]
171 async fn decrypt<R: Rng + CryptoRng>(
172 &mut self,
173 envelope: &Envelope,
174 csprng: &mut R,
175 ) -> Result<Plaintext, ServiceError> {
176 let ciphertext = if let Some(msg) = envelope.content.as_ref() {
177 msg
178 } else {
179 return Err(ServiceError::InvalidFrame {
180 reason:
181 "envelope should have either a legacy message or content.",
182 });
183 };
184
185 let server_guid =
186 envelope.server_guid.as_ref().and_then(|g| match g.parse() {
187 Ok(uuid) => Some(uuid),
188 Err(e) => {
189 tracing::error!(
190 ?envelope,
191 "Unparseable server_guid ({})",
192 e
193 );
194 None
195 },
196 });
197
198 use crate::proto::envelope::Type;
199 let plaintext = match envelope.r#type() {
200 Type::PrekeyBundle => {
201 let sender = get_preferred_protocol_address(
202 &self.protocol_store,
203 &envelope.source_address(),
204 envelope.source_device().try_into()?,
205 )
206 .await?;
207 let metadata = Metadata {
208 destination: envelope.destination_address(),
209 sender: envelope.source_address(),
210 sender_device: envelope.source_device().try_into()?,
211 timestamp: envelope.server_timestamp(),
212 needs_receipt: false,
213 unidentified_sender: false,
214 was_plaintext: false,
215
216 server_guid,
217 };
218
219 let mut data = message_decrypt_prekey(
220 &PreKeySignalMessage::try_from(&ciphertext[..]).unwrap(),
221 &sender,
222 &mut self.protocol_store.clone(),
223 &mut self.protocol_store.clone(),
224 &mut self.protocol_store.clone(),
225 &self.protocol_store.clone(),
226 &mut self.protocol_store.clone(),
227 csprng,
228 UsePQRatchet::No,
229 )
230 .await?
231 .as_slice()
232 .to_vec();
233
234 let session_record = self
235 .protocol_store
236 .load_session(&sender)
237 .await?
238 .ok_or(SignalProtocolError::SessionNotFound(sender))?;
239
240 strip_padding_version(
241 session_record.session_version()?,
242 &mut data,
243 )?;
244 Plaintext { metadata, data }
245 },
246 Type::PlaintextContent => {
247 tracing::warn!(?envelope, "Envelope with plaintext content. This usually indicates a decryption retry.");
248 let metadata = Metadata {
249 destination: envelope.destination_address(),
250 sender: envelope.source_address(),
251 sender_device: envelope.source_device().try_into()?,
252 timestamp: envelope.server_timestamp(),
253 needs_receipt: false,
254 unidentified_sender: false,
255 was_plaintext: true,
256
257 server_guid,
258 };
259 Plaintext {
260 metadata,
261 data: ciphertext.clone(),
262 }
263 },
264 Type::Ciphertext => {
265 let sender = get_preferred_protocol_address(
266 &self.protocol_store,
267 &envelope.source_address(),
268 envelope.source_device().try_into()?,
269 )
270 .await?;
271 let metadata = Metadata {
272 destination: envelope.destination_address(),
273 sender: envelope.source_address(),
274 sender_device: envelope.source_device().try_into()?,
275 timestamp: envelope.timestamp(),
276 needs_receipt: false,
277 unidentified_sender: false,
278 was_plaintext: false,
279
280 server_guid,
281 };
282
283 let mut data = message_decrypt_signal(
284 &SignalMessage::try_from(&ciphertext[..])?,
285 &sender,
286 &mut 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(SignalProtocolError::SessionNotFound(sender))?;
299
300 strip_padding_version(
301 session_record.session_version()?,
302 &mut data,
303 )?;
304 Plaintext { metadata, data }
305 },
306 Type::UnidentifiedSender => {
307 let SealedSenderDecryptionResult {
308 sender_uuid,
309 sender_e164: _,
310 device_id,
311 mut message,
312 } = sealed_sender_decrypt(
313 ciphertext,
314 &self.trust_root,
315 Timestamp::from_epoch_millis(envelope.timestamp()),
316 None,
317 self.local_uuid.to_string(),
318 self.local_device_id,
319 &mut self.protocol_store.clone(),
320 &mut self.protocol_store.clone(),
321 &mut self.protocol_store.clone(),
322 &mut self.protocol_store.clone(),
323 &mut self.protocol_store.clone(),
324 &mut self.protocol_store,
325 )
326 .await?;
327
328 let Some(sender) =
329 ServiceId::parse_from_service_id_string(&sender_uuid)
330 else {
331 return Err(
332 SignalProtocolError::InvalidSealedSenderMessage(
333 "invalid sender UUID".to_string(),
334 )
335 .into(),
336 );
337 };
338
339 let needs_receipt = if envelope.source_service_id.is_some() {
340 tracing::warn!(?envelope, "Received an unidentified delivery over an identified channel. Marking needs_receipt=false");
341 false
342 } else {
343 true
344 };
345
346 let metadata = Metadata {
347 destination: envelope.destination_address(),
348 sender,
349 sender_device: device_id,
350 timestamp: envelope.timestamp(),
351 unidentified_sender: true,
352 needs_receipt,
353 was_plaintext: false,
354
355 server_guid,
356 };
357
358 strip_padding(&mut message)?;
359
360 Plaintext {
361 metadata,
362 data: message,
363 }
364 },
365 _ => {
366 return Err(ServiceError::InvalidFrame {
368 reason: "envelope has unknown type",
369 });
370 },
371 };
372 Ok(plaintext)
373 }
374
375 #[tracing::instrument(
376 skip(address, unidentified_access, content, csprng),
377 fields(
378 address = %address,
379 with_unidentified_access = unidentified_access.is_some(),
380 content_length = content.len(),
381 )
382 )]
383 pub(crate) async fn encrypt<R: Rng + CryptoRng>(
384 &mut self,
385 address: &ProtocolAddress,
386 unidentified_access: Option<&SenderCertificate>,
387 content: &[u8],
388 csprng: &mut R,
389 ) -> Result<OutgoingPushMessage, ServiceError> {
390 let mut rng = rng();
391
392 let session_record = self
393 .protocol_store
394 .load_session(address)
395 .await?
396 .ok_or_else(|| {
397 SignalProtocolError::SessionNotFound(address.clone())
398 })?;
399
400 let padded_content =
401 add_padding(session_record.session_version()?, content)?;
402
403 if let Some(unindentified_access) = unidentified_access {
404 let destination_registration_id =
405 session_record.remote_registration_id()?;
406
407 let message = sealed_sender_encrypt(
408 address,
409 unindentified_access,
410 &padded_content,
411 &mut self.protocol_store.clone(),
412 &mut self.protocol_store,
413 SystemTime::now(),
414 csprng,
415 )
416 .await?;
417
418 use crate::proto::envelope::Type;
419 Ok(OutgoingPushMessage {
420 r#type: Type::UnidentifiedSender as u32,
421 destination_device_id: address.device_id().into(),
422 destination_registration_id,
423 content: BASE64_RELAXED.encode(message),
424 })
425 } else {
426 let message = message_encrypt(
427 &padded_content,
428 address,
429 &mut self.protocol_store.clone(),
430 &mut self.protocol_store.clone(),
431 SystemTime::now(),
432 &mut rng,
433 )
434 .await?;
435
436 let destination_registration_id =
437 session_record.remote_registration_id()?;
438
439 let body = BASE64_RELAXED.encode(message.serialize());
440
441 use crate::proto::envelope::Type;
442 let message_type = match message.message_type() {
443 CiphertextMessageType::PreKey => Type::PrekeyBundle,
444 CiphertextMessageType::Whisper => Type::Ciphertext,
445 t => panic!("Bad type: {:?}", t),
446 } as u32;
447 Ok(OutgoingPushMessage {
448 r#type: message_type,
449 destination_device_id: address.device_id().into(),
450 destination_registration_id,
451 content: body,
452 })
453 }
454 }
455}
456
457struct Plaintext {
458 metadata: Metadata,
459 data: Vec<u8>,
460}
461
462#[expect(clippy::comparison_chain, clippy::result_large_err)]
463fn add_padding(version: u32, contents: &[u8]) -> Result<Vec<u8>, ServiceError> {
464 if version < 2 {
465 Err(ServiceError::PaddingVersion(version))
466 } else if version == 2 {
467 Ok(contents.to_vec())
468 } else {
469 let message_length = contents.len();
470 let message_length_with_terminator = contents.len() + 1;
471 let mut message_part_count = message_length_with_terminator / 160;
472 if message_length_with_terminator % 160 != 0 {
473 message_part_count += 1;
474 }
475
476 let message_length_with_padding = message_part_count * 160;
477
478 let mut buffer = vec![0u8; message_length_with_padding];
479 buffer[..message_length].copy_from_slice(contents);
480 Iso7816::raw_pad(&mut buffer, message_length);
481 Ok(buffer)
482 }
483}
484
485#[expect(clippy::comparison_chain, clippy::result_large_err)]
486fn strip_padding_version(
487 version: u32,
488 contents: &mut Vec<u8>,
489) -> Result<(), ServiceError> {
490 if version < 2 {
491 Err(ServiceError::InvalidFrame {
492 reason: "unknown version",
493 })
494 } else if version == 2 {
495 Ok(())
496 } else {
497 strip_padding(contents)?;
498 Ok(())
499 }
500}
501
502#[expect(clippy::result_large_err)]
503fn strip_padding(contents: &mut Vec<u8>) -> Result<(), ServiceError> {
504 let new_length = Iso7816::raw_unpad(contents)?.len();
505 contents.resize(new_length, 0);
506 Ok(())
507}
508
509pub async fn get_preferred_protocol_address<S: SessionStore>(
511 session_store: &S,
512 address: &ServiceId,
513 device_id: DeviceId,
514) -> Result<ProtocolAddress, libsignal_protocol::error::SignalProtocolError> {
515 let address = address.to_protocol_address(device_id);
516 if session_store.load_session(&address).await?.is_some() {
517 return Ok(address);
518 }
519
520 Ok(address)
521}
522
523#[allow(clippy::too_many_arguments)]
531#[tracing::instrument(
532 skip(
533 ciphertext,
534 trust_root,
535 identity_store,
536 session_store,
537 pre_key_store,
538 signed_pre_key_store,
539 sender_key_store,
540 kyber_pre_key_store
541 ),
542 fields(
543 ciphertext = ciphertext.len(),
544 )
545)]
546async fn sealed_sender_decrypt(
547 ciphertext: &[u8],
548 trust_root: &PublicKey,
549 timestamp: Timestamp,
550 local_e164: Option<String>,
551 local_uuid: String,
552 local_device_id: DeviceId,
553 identity_store: &mut dyn IdentityKeyStore,
554 session_store: &mut dyn SessionStore,
555 pre_key_store: &mut dyn PreKeyStore,
556 signed_pre_key_store: &mut dyn SignedPreKeyStore,
557 sender_key_store: &mut dyn SenderKeyStore,
558 kyber_pre_key_store: &mut dyn KyberPreKeyStore,
559) -> Result<SealedSenderDecryptionResult, SignalProtocolError> {
560 let usmc =
561 sealed_sender_decrypt_to_usmc(ciphertext, identity_store).await?;
562
563 if !usmc.sender()?.validate(trust_root, timestamp)? {
564 return Err(SignalProtocolError::InvalidSealedSenderMessage(
565 "trust root validation failed".to_string(),
566 ));
567 }
568
569 let is_local_uuid = local_uuid == usmc.sender()?.sender_uuid()?;
570
571 let is_local_e164 = match (local_e164, usmc.sender()?.sender_e164()?) {
572 (Some(l), Some(s)) => l == s,
573 (_, _) => false,
574 };
575
576 if (is_local_e164 || is_local_uuid)
577 && usmc.sender()?.sender_device_id()? == local_device_id
578 {
579 return Err(SignalProtocolError::SealedSenderSelfSend);
580 }
581
582 let mut rng = rng();
583
584 let remote_address = ProtocolAddress::new(
585 usmc.sender()?.sender_uuid()?.to_string(),
586 usmc.sender()?.sender_device_id()?,
587 );
588
589 let message = match usmc.msg_type()? {
590 CiphertextMessageType::Whisper => {
591 let ctext = SignalMessage::try_from(usmc.contents()?)?;
592 message_decrypt_signal(
593 &ctext,
594 &remote_address,
595 session_store,
596 identity_store,
597 &mut rng,
598 )
599 .await?
600 },
601 CiphertextMessageType::PreKey => {
602 let ctext = PreKeySignalMessage::try_from(usmc.contents()?)?;
603 message_decrypt_prekey(
604 &ctext,
605 &remote_address,
606 session_store,
607 identity_store,
608 pre_key_store,
609 signed_pre_key_store,
610 kyber_pre_key_store,
611 &mut rng,
612 UsePQRatchet::No,
613 )
614 .await?
615 },
616 CiphertextMessageType::SenderKey => {
617 group_decrypt(usmc.contents()?, sender_key_store, &remote_address)
618 .await?
619 },
620 msg_type => {
621 return Err(SignalProtocolError::InvalidMessage(
622 msg_type,
623 "unexpected message type for sealed_sender_decrypt",
624 ));
625 },
626 };
627
628 Ok(SealedSenderDecryptionResult {
629 sender_uuid: usmc.sender()?.sender_uuid()?.to_string(),
630 sender_e164: usmc.sender()?.sender_e164()?.map(|s| s.to_string()),
631 device_id: usmc.sender()?.sender_device_id()?,
632 message,
633 })
634}