1use std::convert::TryInto;
2
3use base64::prelude::*;
4use bytes::Bytes;
5use libsignal_protocol::{Aci, Pni, ServiceId};
6use prost::Message;
7use zkgroup::{
8 groups::GroupSecretParams,
9 profiles::{
10 AnyProfileKeyCredentialPresentation, ExpiringProfileKeyCredential,
11 ProfileKey,
12 },
13 ServerPublicParams, PRESENTATION_VERSION_3,
14};
15
16use crate::{
17 groups_v2::model::Timer,
18 proto::{
19 self, group_attribute_blob, GroupAttributeBlob,
20 Member as EncryptedMember,
21 },
22 utils::BASE64_RELAXED,
23};
24
25use super::{
26 model::{
27 AccessControl, BannedMember, GroupMemberCandidate, Member,
28 PendingMember, PromotedMember, RequestingMember,
29 },
30 Group, GroupChange, GroupChanges,
31};
32
33pub struct GroupOperations {
34 pub group_secret_params: GroupSecretParams,
35}
36
37#[derive(Debug, thiserror::Error)]
38pub enum GroupDecodingError {
39 #[error("zero-knowledge group deserialization failure")]
40 ZkGroupDeserializationFailure,
41 #[error("zero-knowledge group verification failure")]
42 ZkGroupVerificationFailure,
43 #[error(transparent)]
44 BincodeError(#[from] bincode::Error),
45 #[error("protobuf message decoding error: {0}")]
46 ProtobufDecodeError(#[from] prost::DecodeError),
47 #[error("wrong group attribute blob")]
48 WrongBlob,
49 #[error("wrong enum value")]
50 WrongEnumValue,
51 #[error("wrong service ID type: should be ACI")]
52 NotAci,
53 #[error("wrong service ID type: should be PNI")]
54 NotPni,
55}
56
57impl From<zkgroup::ZkGroupDeserializationFailure> for GroupDecodingError {
58 fn from(_: zkgroup::ZkGroupDeserializationFailure) -> Self {
59 GroupDecodingError::ZkGroupDeserializationFailure
60 }
61}
62
63impl From<zkgroup::ZkGroupVerificationFailure> for GroupDecodingError {
64 fn from(_: zkgroup::ZkGroupVerificationFailure) -> Self {
65 GroupDecodingError::ZkGroupVerificationFailure
66 }
67}
68
69impl GroupOperations {
70 fn encrypt_service_id(
71 &self,
72 service_id: ServiceId,
73 ) -> Result<Vec<u8>, GroupDecodingError> {
74 let ciphertext =
75 self.group_secret_params.encrypt_service_id(service_id);
76 Ok(zkgroup::serialize(&ciphertext))
77 }
78
79 fn decrypt_service_id(
80 &self,
81 ciphertext: &[u8],
82 ) -> Result<ServiceId, GroupDecodingError> {
83 match self
84 .group_secret_params
85 .decrypt_service_id(zkgroup::deserialize(ciphertext)?)?
86 {
87 ServiceId::Aci(aci) => Ok(ServiceId::from(aci)),
88 ServiceId::Pni(pni) => Ok(ServiceId::from(pni)),
89 }
90 }
91
92 fn encrypt_aci(&self, aci: Aci) -> Result<Vec<u8>, GroupDecodingError> {
93 self.encrypt_service_id(aci.into())
94 }
95
96 fn decrypt_aci(
97 &self,
98 ciphertext: &[u8],
99 ) -> Result<Aci, GroupDecodingError> {
100 match self
101 .group_secret_params
102 .decrypt_service_id(zkgroup::deserialize(ciphertext)?)?
103 {
104 ServiceId::Aci(aci) => Ok(aci),
105 ServiceId::Pni(pni) => {
106 tracing::error!(
107 "Expected Aci, got Pni: {}",
108 pni.service_id_string()
109 );
110 Err(GroupDecodingError::NotAci)
111 },
112 }
113 }
114
115 fn decrypt_pni(
116 &self,
117 ciphertext: &[u8],
118 ) -> Result<Pni, GroupDecodingError> {
119 match self
120 .group_secret_params
121 .decrypt_service_id(zkgroup::deserialize(ciphertext)?)?
122 {
123 ServiceId::Pni(pni) => Ok(pni),
124 ServiceId::Aci(aci) => {
125 tracing::error!(
126 "Expected Pni, got Aci: {}",
127 aci.service_id_string()
128 );
129 Err(GroupDecodingError::NotPni)
130 },
131 }
132 }
133
134 fn encrypt_profile_key(
135 &self,
136 profile_key: ProfileKey,
137 aci: Aci,
138 ) -> Result<Vec<u8>, GroupDecodingError> {
139 let ciphertext = self
140 .group_secret_params
141 .encrypt_profile_key(profile_key, aci);
142 Ok(zkgroup::serialize(&ciphertext))
143 }
144
145 fn decrypt_profile_key(
146 &self,
147 encrypted_profile_key: &[u8],
148 decrypted_aci: libsignal_protocol::Aci,
149 ) -> Result<ProfileKey, GroupDecodingError> {
150 Ok(self.group_secret_params.decrypt_profile_key(
151 zkgroup::deserialize(encrypted_profile_key)?,
152 decrypted_aci,
153 )?)
154 }
155
156 fn decrypt_profile_key_presentation(
157 &self,
158 aci: &[u8],
159 profile_key: &[u8],
160 presentation: &[u8],
161 ) -> Result<(Aci, ProfileKey), GroupDecodingError> {
162 if presentation.is_empty() {
163 let aci = self.decrypt_aci(aci)?;
164 let profile_key = self.decrypt_profile_key(profile_key, aci)?;
165 return Ok((aci, profile_key));
166 }
167
168 let profile_key_credential_presentation =
169 AnyProfileKeyCredentialPresentation::new(presentation)?;
170
171 match self.group_secret_params.decrypt_service_id(
172 profile_key_credential_presentation.get_uuid_ciphertext(),
173 )? {
174 ServiceId::Aci(aci) => {
175 let profile_key =
176 self.group_secret_params.decrypt_profile_key(
177 profile_key_credential_presentation
178 .get_profile_key_ciphertext(),
179 aci,
180 )?;
181 Ok((aci, profile_key))
182 },
183 _ => Err(GroupDecodingError::NotAci),
184 }
185 }
186
187 fn decrypt_pni_aci_promotion_presentation(
188 &self,
189 member: &proto::group_change::actions::PromoteMemberPendingPniAciProfileKeyAction,
190 ) -> Result<PromotedMember, GroupDecodingError> {
191 let aci = self.decrypt_aci(&member.user_id)?;
192 let pni = self.decrypt_pni(&member.pni)?;
193 let profile_key = self.decrypt_profile_key(&member.profile_key, aci)?;
194 Ok(PromotedMember {
195 aci,
196 pni,
197 profile_key,
198 })
199 }
200
201 fn decrypt_member(
202 &self,
203 member: EncryptedMember,
204 ) -> Result<Member, GroupDecodingError> {
205 let (aci, profile_key) = self.decrypt_profile_key_presentation(
206 &member.user_id,
207 &member.profile_key,
208 &member.presentation,
209 )?;
210
211 let label = self.decrypt_member_label_text(&member.label_string);
212 let label_emoji = self.decrypt_member_label_emoji(&member.label_emoji);
213
214 Ok(Member {
215 aci,
216 profile_key,
217 role: member.role.try_into()?,
218 joined_at_version: member.joined_at_version,
219 label,
220 label_emoji,
221 })
222 }
223
224 fn decrypt_pending_member(
225 &self,
226 member: proto::MemberPendingProfileKey,
227 ) -> Result<PendingMember, GroupDecodingError> {
228 let inner_member =
229 member.member.ok_or(GroupDecodingError::WrongBlob)?;
230 let service_id = self.decrypt_service_id(&inner_member.user_id)?;
231 let added_by_aci = self.decrypt_aci(&member.added_by_user_id)?;
232
233 Ok(PendingMember {
234 address: service_id,
235 role: inner_member.role.try_into()?,
236 added_by_aci,
237 timestamp: member.timestamp,
238 })
239 }
240
241 fn decrypt_requesting_member(
242 &self,
243 member: proto::MemberPendingAdminApproval,
244 ) -> Result<RequestingMember, GroupDecodingError> {
245 let (aci, profile_key) = self.decrypt_profile_key_presentation(
246 &member.user_id,
247 &member.profile_key,
248 &member.presentation,
249 )?;
250 Ok(RequestingMember {
251 profile_key,
252 aci,
253 timestamp: member.timestamp,
254 })
255 }
256
257 fn decrypt_banned_member(
258 &self,
259 member: proto::MemberBanned,
260 ) -> Result<BannedMember, GroupDecodingError> {
261 Ok(BannedMember {
262 user_id: self.decrypt_service_id(&member.user_id)?,
263 timestamp: member.timestamp,
264 })
265 }
266
267 fn decrypt_string(
268 &self,
269 bytes: &[u8],
270 ) -> Result<String, GroupDecodingError> {
271 let bytes =
272 self.group_secret_params.decrypt_blob_with_padding(bytes)?;
273 String::from_utf8(bytes).map_err(|_| GroupDecodingError::WrongBlob)
274 }
275
276 fn maybe_decrypt_string(
279 &self,
280 bytes: &[u8],
281 ) -> Result<Option<String>, GroupDecodingError> {
282 if bytes.is_empty() {
283 return Ok(None);
284 }
285 self.decrypt_string(bytes).map(Some)
286 }
287
288 fn decrypt_member_label_text(&self, bytes: &[u8]) -> Option<String> {
291 match self.maybe_decrypt_string(bytes) {
292 Ok(s) => s,
293 Err(e) => {
294 tracing::warn!("failed to decrypt member label string: {e}");
295 None
296 },
297 }
298 }
299
300 fn decrypt_member_label_emoji(&self, bytes: &[u8]) -> Option<String> {
303 match self.maybe_decrypt_string(bytes) {
304 Ok(s) => s,
305 Err(e) => {
306 tracing::warn!("failed to decrypt member label emoji: {e}");
307 None
308 },
309 }
310 }
311
312 fn decrypt_blob(&self, bytes: &[u8]) -> GroupAttributeBlob {
313 if bytes.is_empty() {
314 GroupAttributeBlob::default()
315 } else if bytes.len() < 29 {
316 tracing::warn!("bad encrypted blob length");
317 GroupAttributeBlob::default()
318 } else {
319 self.group_secret_params
320 .decrypt_blob_with_padding(bytes)
321 .map_err(GroupDecodingError::from)
322 .and_then(|plaintext| {
323 GroupAttributeBlob::decode(Bytes::from(plaintext))
324 .map_err(GroupDecodingError::ProtobufDecodeError)
325 })
326 .unwrap_or_else(|e| {
327 tracing::warn!("bad encrypted blob: {}", e);
328 GroupAttributeBlob::default()
329 })
330 }
331 }
332
333 fn encrypt_blob_content<R: rand::Rng + rand::CryptoRng>(
352 &self,
353 content: group_attribute_blob::Content,
354 rng: &mut R,
355 ) -> Vec<u8> {
356 let blob = GroupAttributeBlob {
357 content: Some(content),
358 };
359 let buf = blob.encode_to_vec();
360
361 let mut randomness = [0u8; 32];
362 rng.fill_bytes(&mut randomness);
363 self.group_secret_params
364 .encrypt_blob_with_padding(randomness, &buf, 0)
365 }
366
367 pub fn encrypt_title<R: rand::Rng + rand::CryptoRng>(
368 &self,
369 title: &str,
370 rng: &mut R,
371 ) -> Vec<u8> {
372 self.encrypt_blob_content(
373 group_attribute_blob::Content::Title(title.to_string()),
374 rng,
375 )
376 }
377
378 pub fn encrypt_description<R: rand::Rng + rand::CryptoRng>(
379 &self,
380 description: Option<&str>,
381 rng: &mut R,
382 ) -> Vec<u8> {
383 self.encrypt_blob_content(
384 group_attribute_blob::Content::DescriptionText(
385 description.unwrap_or_default().to_string(),
386 ),
387 rng,
388 )
389 }
390
391 pub fn encrypt_disappearing_messages_timer<
392 R: rand::Rng + rand::CryptoRng,
393 >(
394 &self,
395 timer: Option<&Timer>,
396 rng: &mut R,
397 ) -> Vec<u8> {
398 self.encrypt_blob_content(
399 group_attribute_blob::Content::DisappearingMessagesDuration(
400 timer.map(|t| t.duration).unwrap_or(0),
401 ),
402 rng,
403 )
404 }
405
406 fn decrypt_title(&self, ciphertext: &[u8]) -> String {
407 use group_attribute_blob::Content;
408 match self.decrypt_blob(ciphertext).content {
409 Some(Content::Title(title)) => title,
410 _ => "".into(),
411 }
412 }
413
414 fn decrypt_description_text(&self, ciphertext: &[u8]) -> Option<String> {
415 use group_attribute_blob::Content;
416 match self.decrypt_blob(ciphertext).content {
417 Some(Content::DescriptionText(d)) => {
418 Some(d).filter(|d| !d.is_empty())
419 },
420 _ => None,
421 }
422 }
423
424 fn decrypt_disappearing_messages_timer(
425 &self,
426 ciphertext: &[u8],
427 ) -> Option<Timer> {
428 use group_attribute_blob::Content;
429 match self.decrypt_blob(ciphertext).content {
430 Some(Content::DisappearingMessagesDuration(duration)) => {
431 Some(Timer { duration })
432 },
433 _ => None,
434 }
435 }
436
437 pub fn new(group_secret_params: GroupSecretParams) -> Self {
438 Self {
439 group_secret_params,
440 }
441 }
442
443 pub fn decrypt_group(
444 &self,
445 group: proto::Group,
446 ) -> Result<Group, GroupDecodingError> {
447 let proto::Group {
449 public_key: _,
450 title,
451 avatar_url,
452 disappearing_messages_timer,
453 access_control,
454 version,
455 members,
456 members_pending_profile_key,
457 members_pending_admin_approval,
458 invite_link_password,
459 description,
460 announcements_only,
461 members_banned,
462 } = group;
463
464 let title = self.decrypt_title(&title);
465
466 let description_text = self.decrypt_description_text(&description);
467
468 let disappearing_messages_timer = self
469 .decrypt_disappearing_messages_timer(&disappearing_messages_timer);
470
471 let members = members
472 .into_iter()
473 .map(|m| self.decrypt_member(m))
474 .collect::<Result<_, _>>()?;
475
476 let members_pending_profile_key = members_pending_profile_key
477 .into_iter()
478 .map(|m| self.decrypt_pending_member(m))
479 .collect::<Result<_, _>>()?;
480
481 let members_pending_admin_approval = members_pending_admin_approval
482 .into_iter()
483 .map(|m| self.decrypt_requesting_member(m))
484 .collect::<Result<_, _>>()?;
485
486 let members_banned = members_banned
487 .into_iter()
488 .map(|m| self.decrypt_banned_member(m))
489 .collect::<Result<_, _>>()?;
490
491 let access_control =
492 access_control.map(TryInto::try_into).transpose()?;
493
494 Ok(Group {
495 title,
496 avatar: avatar_url,
497 disappearing_messages_timer,
498 access_control,
499 version,
500 members,
501 members_pending_profile_key,
502 members_pending_admin_approval,
503 invite_link_password,
504 description_text,
505 announcements_only,
506 members_banned,
507 })
508 }
509
510 pub fn decrypt_group_change(
511 &self,
512 group_change: proto::GroupChange,
513 ) -> Result<GroupChanges, GroupDecodingError> {
514 let proto::GroupChange {
516 actions,
517 server_signature: _,
518 change_epoch,
519 } = group_change;
520
521 let proto::group_change::Actions {
522 group_id,
523 source_user_id,
524 version,
525 add_members,
526 delete_members,
527 modify_member_roles,
528 modify_member_profile_keys,
529 add_members_pending_profile_key,
530 delete_members_pending_profile_key,
531 promote_members_pending_profile_key,
532 modify_title,
533 modify_avatar,
534 modify_disappearing_message_timer,
535 modify_attributes_access,
536 modify_member_access,
537 modify_add_from_invite_link_access,
538 add_members_pending_admin_approval,
539 delete_members_pending_admin_approval,
540 promote_members_pending_admin_approval,
541 modify_invite_link_password,
542 modify_description,
543 modify_announcements_only,
544 add_members_banned,
545 delete_members_banned,
546 promote_members_pending_pni_aci_profile_key,
547 modify_member_labels,
548 modify_member_label_access,
549 } = Message::decode(Bytes::from(actions))?;
550
551 let source_user_id = self.decrypt_aci(&source_user_id)?;
552
553 let new_members =
554 add_members
555 .into_iter()
556 .filter_map(|m| m.added)
557 .map(|added| {
558 Ok(GroupChange::NewMember(self.decrypt_member(added)?))
559 });
560
561 let delete_members = delete_members.into_iter().map(|c| {
562 Ok(GroupChange::DeleteMember(
563 self.decrypt_aci(&c.deleted_user_id)?,
564 ))
565 });
566
567 let modify_member_roles = modify_member_roles.into_iter().map(|m| {
568 Ok(GroupChange::ModifyMemberRole {
569 aci: self.decrypt_aci(&m.user_id)?,
570 role: m.role.try_into()?,
571 })
572 });
573
574 let modify_member_profile_keys =
575 modify_member_profile_keys.into_iter().map(|m| {
576 let (aci, profile_key) = self
577 .decrypt_profile_key_presentation(
578 &m.user_id,
579 &m.profile_key,
580 &m.presentation,
581 )?;
582 Ok(GroupChange::ModifyMemberProfileKey { aci, profile_key })
583 });
584
585 let add_members_pending_profile_key = add_members_pending_profile_key
586 .into_iter()
587 .filter_map(|m| m.added)
588 .map(|added| {
589 Ok(GroupChange::NewPendingMember(
590 self.decrypt_pending_member(added)?,
591 ))
592 });
593
594 let delete_members_pending_profile_key =
595 delete_members_pending_profile_key.into_iter().map(|m| {
596 Ok(GroupChange::DeletePendingMember(
597 self.decrypt_service_id(&m.deleted_user_id)?,
598 ))
599 });
600
601 let promote_members_pending_profile_key =
602 promote_members_pending_profile_key.into_iter().map(|m| {
603 let (aci, profile_key) = self
604 .decrypt_profile_key_presentation(
605 &m.user_id,
606 &m.profile_key,
607 &m.presentation,
608 )?;
609 Ok(GroupChange::PromotePendingMember {
610 address: aci.into(),
611 profile_key,
612 })
613 });
614
615 let modify_title = modify_title
616 .into_iter()
617 .map(|m| Ok(GroupChange::Title(self.decrypt_title(&m.title))));
618
619 let modify_avatar = modify_avatar
620 .into_iter()
621 .map(|m| Ok(GroupChange::Avatar(m.avatar)));
622
623 let modify_description = modify_description.into_iter().map(|m| {
624 Ok(GroupChange::Description(
625 self.decrypt_description_text(&m.description),
626 ))
627 });
628
629 let modify_disappearing_message_timer =
630 modify_disappearing_message_timer.into_iter().map(|m| {
631 Ok(GroupChange::Timer(
632 self.decrypt_disappearing_messages_timer(&m.timer),
633 ))
634 });
635
636 let modify_attributes_access =
637 modify_attributes_access.into_iter().map(|m| {
638 Ok(GroupChange::AttributeAccess(
639 m.attributes_access.try_into()?,
640 ))
641 });
642
643 let modify_member_access = modify_member_access.into_iter().map(|m| {
644 Ok(GroupChange::MemberAccess(m.members_access.try_into()?))
645 });
646
647 let add_members_banned = add_members_banned
648 .into_iter()
649 .filter_map(|m| m.added)
650 .map(|m| {
651 Ok(GroupChange::AddBannedMember(self.decrypt_banned_member(m)?))
652 });
653
654 let delete_members_banned =
655 delete_members_banned.into_iter().map(|m| {
656 Ok(GroupChange::DeleteBannedMember(
657 self.decrypt_service_id(&m.deleted_user_id)?,
658 ))
659 });
660
661 let promote_members_pending_pni_aci_profile_key =
662 promote_members_pending_pni_aci_profile_key
663 .into_iter()
664 .map(|m| {
665 let promoted =
666 self.decrypt_pni_aci_promotion_presentation(&m)?;
667 Ok(GroupChange::PromotePendingPniAciMemberProfileKey(
668 promoted,
669 ))
670 });
671
672 let modify_add_from_invite_link_access =
673 modify_add_from_invite_link_access.into_iter().map(|m| {
674 Ok(GroupChange::InviteLinkAccess(
675 m.add_from_invite_link_access.try_into()?,
676 ))
677 });
678
679 let add_members_pending_admin_approval =
680 add_members_pending_admin_approval
681 .into_iter()
682 .filter_map(|m| m.added)
683 .map(|added| {
684 Ok(GroupChange::NewRequestingMember(
685 self.decrypt_requesting_member(added)?,
686 ))
687 });
688
689 let delete_members_pending_admin_approval =
690 delete_members_pending_admin_approval.into_iter().map(|m| {
691 Ok(GroupChange::DeleteRequestingMember(
692 self.decrypt_aci(&m.deleted_user_id)?,
693 ))
694 });
695
696 let promote_members_pending_admin_approval =
697 promote_members_pending_admin_approval.into_iter().map(|m| {
698 Ok(GroupChange::PromoteRequestingMember {
699 aci: self.decrypt_aci(&m.user_id)?,
700 role: m.role.try_into()?,
701 })
702 });
703
704 let modify_invite_link_password =
705 modify_invite_link_password.into_iter().map(|m| {
706 Ok(GroupChange::InviteLinkPassword(
707 BASE64_RELAXED.encode(m.invite_link_password),
708 ))
709 });
710
711 let modify_announcements_only = modify_announcements_only
712 .into_iter()
713 .map(|m| Ok(GroupChange::AnnouncementOnly(m.announcements_only)));
714
715 let modify_member_labels = modify_member_labels.into_iter().map(|m| {
716 Ok(GroupChange::MemberLabel {
717 user_id: self.decrypt_service_id(&m.user_id)?,
718 label_emoji: self.decrypt_member_label_emoji(&m.label_emoji),
719 label_string: self.decrypt_member_label_text(&m.label_string),
720 })
721 });
722
723 let modify_member_label_access =
724 modify_member_label_access.into_iter().map(|m| {
725 Ok(GroupChange::MemberLabelAccess(
726 m.member_label_access.try_into()?,
727 ))
728 });
729
730 let changes: Result<Vec<GroupChange>, GroupDecodingError> = new_members
731 .chain(delete_members)
732 .chain(modify_member_roles)
733 .chain(modify_member_profile_keys)
734 .chain(add_members_pending_profile_key)
735 .chain(delete_members_pending_profile_key)
736 .chain(promote_members_pending_profile_key)
737 .chain(modify_title)
738 .chain(modify_avatar)
739 .chain(modify_disappearing_message_timer)
740 .chain(modify_attributes_access)
741 .chain(modify_description)
742 .chain(modify_member_access)
743 .chain(add_members_banned)
744 .chain(delete_members_banned)
745 .chain(promote_members_pending_pni_aci_profile_key)
746 .chain(modify_add_from_invite_link_access)
747 .chain(add_members_pending_admin_approval)
748 .chain(delete_members_pending_admin_approval)
749 .chain(promote_members_pending_admin_approval)
750 .chain(modify_invite_link_password)
751 .chain(modify_announcements_only)
752 .chain(modify_member_labels)
753 .chain(modify_member_label_access)
754 .collect();
755
756 Ok(GroupChanges {
757 group_id: group_id
758 .try_into()
759 .map_err(|_| GroupDecodingError::WrongBlob)?,
760 editor: source_user_id,
761 version,
762 changes: changes?,
763 change_epoch,
764 })
765 }
766
767 pub fn decrypt_avatar(&self, ciphertext: &[u8]) -> Option<Vec<u8>> {
768 use group_attribute_blob::Content;
769 match self.decrypt_blob(ciphertext).content {
770 Some(Content::Avatar(d)) => Some(d).filter(|d| !d.is_empty()),
771 _ => None,
772 }
773 }
774
775 pub fn build_add_member_action(
788 &self,
789 aci: Aci,
790 profile_key: ProfileKey,
791 role: super::model::Role,
792 ) -> Result<proto::group_change::actions::AddMemberAction, GroupDecodingError>
793 {
794 Ok(proto::group_change::actions::AddMemberAction {
795 added: Some(proto::Member {
796 user_id: self.encrypt_aci(aci)?,
797 profile_key: self.encrypt_profile_key(profile_key, aci)?,
798 presentation: vec![],
799 role: role.into(),
800 joined_at_version: 0, label_emoji: vec![],
803 label_string: vec![],
804 }),
805 join_from_invite_link: false,
806 })
807 }
808
809 pub fn build_remove_member_action(
811 &self,
812 aci: Aci,
813 ) -> Result<
814 proto::group_change::actions::DeleteMemberAction,
815 GroupDecodingError,
816 > {
817 Ok(proto::group_change::actions::DeleteMemberAction {
818 deleted_user_id: self.encrypt_aci(aci)?,
819 })
820 }
821
822 pub fn build_remove_pending_member_action(
830 &self,
831 invitee: ServiceId,
832 ) -> Result<
833 proto::group_change::actions::DeleteMemberPendingProfileKeyAction,
834 GroupDecodingError,
835 > {
836 Ok(
837 proto::group_change::actions::DeleteMemberPendingProfileKeyAction {
838 deleted_user_id: self.encrypt_service_id(invitee)?,
839 },
840 )
841 }
842
843 pub fn create_member_presentation<const V: u8>(
865 &self,
866 server_public_params: &ServerPublicParams,
867 credential: &ExpiringProfileKeyCredential,
868 ) -> Vec<u8> {
869 let randomness: [u8; 32] = rand::random();
870 let presentation = server_public_params
871 .create_expiring_profile_key_credential_presentation::<V>(
872 randomness,
873 self.group_secret_params,
874 *credential,
875 );
876 zkgroup::serialize(&presentation)
877 }
878
879 #[allow(clippy::too_many_arguments)]
898 pub fn encrypt_group_with_credentials<R: rand::Rng + rand::CryptoRng>(
899 &self,
900 title: &str,
901 description: Option<&str>,
902 disappearing_messages_timer: Option<&Timer>,
903 access_control: Option<&AccessControl>,
904 self_credential: &ExpiringProfileKeyCredential,
905 member_candidates: &[GroupMemberCandidate],
906 server_public_params: &ServerPublicParams,
907 avatar_url: String,
908 rng: &mut R,
909 ) -> Result<proto::Group, GroupDecodingError> {
910 let mut members = Vec::new();
911 let mut members_pending_profile_key = Vec::new();
912
913 let self_presentation = self
915 .create_member_presentation::<PRESENTATION_VERSION_3>(
916 server_public_params,
917 self_credential,
918 );
919 members.push(proto::Member {
920 user_id: vec![], profile_key: vec![], presentation: self_presentation,
923 role: proto::member::Role::Administrator.into(),
924 joined_at_version: 0,
925 label_emoji: vec![],
926 label_string: vec![],
927 });
928
929 for candidate in member_candidates {
931 if let Some(credential) = &candidate.credential {
932 let presentation = self
934 .create_member_presentation::<PRESENTATION_VERSION_3>(
935 server_public_params,
936 credential,
937 );
938 members.push(proto::Member {
939 user_id: vec![],
940 profile_key: vec![],
941 presentation,
942 role: proto::member::Role::Default.into(),
943 joined_at_version: 0,
944 label_emoji: vec![],
945 label_string: vec![],
946 });
947 } else {
948 let user_id_ciphertext =
950 self.encrypt_service_id(candidate.service_id)?;
951 let self_aci = self_credential.aci();
952 members_pending_profile_key.push(
953 proto::MemberPendingProfileKey {
954 member: Some(proto::Member {
955 user_id: user_id_ciphertext,
956 profile_key: vec![],
957 presentation: vec![],
958 role: proto::member::Role::Default.into(),
959 joined_at_version: 0,
960 label_emoji: vec![],
961 label_string: vec![],
962 }),
963 added_by_user_id: self.encrypt_aci(self_aci)?,
964 timestamp: 0, },
966 );
967 }
968 }
969
970 let encrypted_title = self.encrypt_title(title, rng);
972 let encrypted_description = self.encrypt_description(description, rng);
973 let encrypted_timer = self.encrypt_disappearing_messages_timer(
974 disappearing_messages_timer,
975 rng,
976 );
977
978 let proto_access_control =
980 access_control.map(|ac| proto::AccessControl {
981 attributes: ac.attributes.into(),
982 members: ac.members.into(),
983 add_from_invite_link: ac.add_from_invite_link.into(),
984 member_label: ac.member_label.into(),
985 });
986
987 Ok(proto::Group {
988 public_key: zkgroup::serialize(
989 &self.group_secret_params.get_public_params(),
990 ),
991 title: encrypted_title,
992 avatar_url,
993 disappearing_messages_timer: encrypted_timer,
994 access_control: proto_access_control,
995 version: 0,
996 members,
997 members_pending_profile_key,
998 members_pending_admin_approval: vec![],
999 invite_link_password: vec![],
1000 description: encrypted_description,
1001 announcements_only: false,
1002 members_banned: vec![],
1003 })
1004 }
1005
1006 pub fn build_add_member_action_with_credential(
1018 &self,
1019 credential: &ExpiringProfileKeyCredential,
1020 role: super::model::Role,
1021 server_public_params: &ServerPublicParams,
1022 ) -> proto::group_change::actions::AddMemberAction {
1023 let presentation = self
1024 .create_member_presentation::<PRESENTATION_VERSION_3>(
1025 server_public_params,
1026 credential,
1027 );
1028 proto::group_change::actions::AddMemberAction {
1029 added: Some(proto::Member {
1030 user_id: vec![], profile_key: vec![], presentation,
1033 role: role.into(),
1034 joined_at_version: 0, label_emoji: vec![],
1036 label_string: vec![],
1037 }),
1038 join_from_invite_link: false,
1039 }
1040 }
1041
1042 pub fn build_add_pending_member_action(
1058 &self,
1059 invitee: ServiceId,
1060 added_by_aci: Aci,
1061 role: super::model::Role,
1062 ) -> Result<
1063 proto::group_change::actions::AddMemberPendingProfileKeyAction,
1064 GroupDecodingError,
1065 > {
1066 Ok(
1067 proto::group_change::actions::AddMemberPendingProfileKeyAction {
1068 added: Some(proto::MemberPendingProfileKey {
1069 member: Some(proto::Member {
1070 user_id: self.encrypt_service_id(invitee)?,
1071 profile_key: vec![],
1072 presentation: vec![],
1073 role: role.into(),
1074 joined_at_version: 0,
1075 label_emoji: vec![],
1076 label_string: vec![],
1077 }),
1078 added_by_user_id: self.encrypt_aci(added_by_aci)?,
1079 timestamp: 0, }),
1081 },
1082 )
1083 }
1084}
1085
1086#[cfg(test)]
1087mod tests {
1088 use super::*;
1089
1090 use rand::RngCore;
1091 use zkgroup::groups::GroupMasterKey;
1092
1093 fn create_group_operations() -> GroupOperations {
1094 let master_key_bytes = [
1096 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
1097 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
1098 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
1099 ];
1100 let group_master_key = GroupMasterKey::new(master_key_bytes);
1101 let group_secret_params =
1102 GroupSecretParams::derive_from_master_key(group_master_key);
1103 GroupOperations::new(group_secret_params)
1104 }
1105
1106 #[test]
1107 fn roundtrip_title() {
1108 let ops = create_group_operations();
1109 let mut rng = rand::rng();
1110
1111 let title = "Test Group Title";
1112 let encrypted = ops.encrypt_title(title, &mut rng);
1113 let decrypted = ops.decrypt_title(&encrypted);
1114 assert_eq!(decrypted, title);
1115 }
1116
1117 #[test]
1118 fn roundtrip_description() {
1119 let ops = create_group_operations();
1120 let mut rng = rand::rng();
1121
1122 let description = "This is a test group description";
1123 let encrypted = ops.encrypt_description(Some(description), &mut rng);
1124 let decrypted = ops.decrypt_description_text(&encrypted);
1125 assert_eq!(decrypted, Some(description.to_string()));
1126 }
1127
1128 #[test]
1129 fn roundtrip_member_label() {
1130 let ops = create_group_operations();
1131 let mut rng = rand::rng();
1132
1133 let label = "Whisperfish / rubdos";
1134 let mut randomness = [0u8; 32];
1135 rng.fill_bytes(&mut randomness);
1136 let encrypted = ops.group_secret_params.encrypt_blob_with_padding(
1137 randomness,
1138 label.as_bytes(),
1139 0,
1140 );
1141
1142 assert_eq!(
1143 ops.decrypt_member_label_text(&encrypted),
1144 Some(label.to_string())
1145 );
1146 }
1147
1148 #[test]
1149 fn roundtrip_disappearing_message_timer() {
1150 let ops = create_group_operations();
1151 let mut rng = rand::rng();
1152
1153 let timer = Timer { duration: 3600 };
1154 let encrypted =
1155 ops.encrypt_disappearing_messages_timer(Some(&timer), &mut rng);
1156 let decrypted = ops.decrypt_disappearing_messages_timer(&encrypted);
1157 assert_eq!(decrypted, Some(timer));
1158 }
1159
1160 #[test]
1161 fn roundtrip_aci_encryption() {
1162 let ops = create_group_operations();
1163
1164 let aci = Aci::parse_from_service_id_string(
1166 "550e8400-e29b-41d4-a716-446655440000",
1167 )
1168 .expect("valid ACI");
1169 let encrypted =
1170 ops.encrypt_aci(aci).expect("encrypt_aci should succeed");
1171 let decrypted = ops
1172 .decrypt_aci(&encrypted)
1173 .expect("decrypt_aci should succeed");
1174 assert_eq!(decrypted, aci);
1175 }
1176
1177 #[test]
1178 fn roundtrip_service_id_encryption() {
1179 let ops = create_group_operations();
1180
1181 let service_id: ServiceId = ServiceId::parse_from_service_id_string(
1183 "550e8400-e29b-41d4-a716-446655440000",
1184 )
1185 .expect("valid service ID");
1186 let encrypted = ops
1187 .encrypt_service_id(service_id)
1188 .expect("encrypt_service_id should succeed");
1189 let decrypted = ops
1190 .decrypt_service_id(&encrypted)
1191 .expect("decrypt_service_id should succeed");
1192 assert_eq!(decrypted, service_id);
1193 }
1194
1195 #[test]
1196 fn roundtrip_service_id_pni_encryption() {
1197 let ops = create_group_operations();
1198
1199 let service_id: ServiceId = ServiceId::parse_from_service_id_string(
1201 "PNI:550e8400-e29b-41d4-a716-446655440000",
1202 )
1203 .expect("valid service ID");
1204 let encrypted = ops
1205 .encrypt_service_id(service_id)
1206 .expect("encrypt_service_id should succeed");
1207 let decrypted = ops
1208 .decrypt_service_id(&encrypted)
1209 .expect("decrypt_service_id should succeed");
1210 assert_eq!(decrypted, service_id);
1211 }
1212
1213 #[test]
1214 fn encrypt_title_different_each_time() {
1215 let ops = create_group_operations();
1216 let mut rng = rand::rng();
1217
1218 let title = "Test Title";
1219 let encrypted1 = ops.encrypt_title(title, &mut rng);
1220 let encrypted2 = ops.encrypt_title(title, &mut rng);
1221
1222 assert_ne!(encrypted1, encrypted2);
1225 assert_eq!(ops.decrypt_title(&encrypted1), title);
1226 assert_eq!(ops.decrypt_title(&encrypted2), title);
1227 }
1228}