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