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,
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(
848 &self,
849 server_public_params: &ServerPublicParams,
850 credential: &ExpiringProfileKeyCredential,
851 ) -> Vec<u8> {
852 let randomness: [u8; 32] = rand::random();
853 let presentation = server_public_params
854 .create_expiring_profile_key_credential_presentation(
855 randomness,
856 self.group_secret_params,
857 *credential,
858 );
859 zkgroup::serialize(&presentation)
860 }
861
862 #[allow(clippy::too_many_arguments)]
881 pub fn encrypt_group_with_credentials<R: rand::Rng + rand::CryptoRng>(
882 &self,
883 title: &str,
884 description: Option<&str>,
885 disappearing_messages_timer: Option<&Timer>,
886 access_control: Option<&AccessControl>,
887 self_credential: &ExpiringProfileKeyCredential,
888 member_candidates: &[GroupMemberCandidate],
889 server_public_params: &ServerPublicParams,
890 avatar_url: String,
891 rng: &mut R,
892 ) -> Result<proto::Group, GroupDecodingError> {
893 let mut members = Vec::new();
894 let mut members_pending_profile_key = Vec::new();
895
896 let self_presentation = self
898 .create_member_presentation(server_public_params, self_credential);
899 members.push(proto::Member {
900 user_id: vec![], profile_key: vec![], presentation: self_presentation,
903 role: proto::member::Role::Administrator.into(),
904 joined_at_version: 0,
905 label_emoji: vec![],
906 label_string: vec![],
907 });
908
909 for candidate in member_candidates {
911 if let Some(credential) = &candidate.credential {
912 let presentation = self.create_member_presentation(
914 server_public_params,
915 credential,
916 );
917 members.push(proto::Member {
918 user_id: vec![],
919 profile_key: vec![],
920 presentation,
921 role: proto::member::Role::Default.into(),
922 joined_at_version: 0,
923 label_emoji: vec![],
924 label_string: vec![],
925 });
926 } else {
927 let user_id_ciphertext =
929 self.encrypt_service_id(candidate.service_id)?;
930 let self_aci = self_credential.aci();
931 members_pending_profile_key.push(
932 proto::MemberPendingProfileKey {
933 member: Some(proto::Member {
934 user_id: user_id_ciphertext,
935 profile_key: vec![],
936 presentation: vec![],
937 role: proto::member::Role::Default.into(),
938 joined_at_version: 0,
939 label_emoji: vec![],
940 label_string: vec![],
941 }),
942 added_by_user_id: self.encrypt_aci(self_aci)?,
943 timestamp: 0, },
945 );
946 }
947 }
948
949 let encrypted_title = self.encrypt_title(title, rng);
951 let encrypted_description = self.encrypt_description(description, rng);
952 let encrypted_timer = self.encrypt_disappearing_messages_timer(
953 disappearing_messages_timer,
954 rng,
955 );
956
957 let proto_access_control =
959 access_control.map(|ac| proto::AccessControl {
960 attributes: ac.attributes.into(),
961 members: ac.members.into(),
962 add_from_invite_link: ac.add_from_invite_link.into(),
963 member_label: ac.member_label.into(),
964 });
965
966 Ok(proto::Group {
967 public_key: zkgroup::serialize(
968 &self.group_secret_params.get_public_params(),
969 ),
970 title: encrypted_title,
971 avatar_url,
972 disappearing_messages_timer: encrypted_timer,
973 access_control: proto_access_control,
974 version: 0,
975 members,
976 members_pending_profile_key,
977 members_pending_admin_approval: vec![],
978 invite_link_password: vec![],
979 description: encrypted_description,
980 announcements_only: false,
981 members_banned: vec![],
982 })
983 }
984
985 pub fn build_add_member_action_with_credential(
997 &self,
998 credential: &ExpiringProfileKeyCredential,
999 role: super::model::Role,
1000 server_public_params: &ServerPublicParams,
1001 ) -> proto::group_change::actions::AddMemberAction {
1002 let presentation =
1003 self.create_member_presentation(server_public_params, credential);
1004 proto::group_change::actions::AddMemberAction {
1005 added: Some(proto::Member {
1006 user_id: vec![], profile_key: vec![], presentation,
1009 role: role.into(),
1010 joined_at_version: 0, label_emoji: vec![],
1012 label_string: vec![],
1013 }),
1014 join_from_invite_link: false,
1015 }
1016 }
1017
1018 pub fn build_add_pending_member_action(
1034 &self,
1035 invitee: ServiceId,
1036 added_by_aci: Aci,
1037 role: super::model::Role,
1038 ) -> Result<
1039 proto::group_change::actions::AddMemberPendingProfileKeyAction,
1040 GroupDecodingError,
1041 > {
1042 Ok(
1043 proto::group_change::actions::AddMemberPendingProfileKeyAction {
1044 added: Some(proto::MemberPendingProfileKey {
1045 member: Some(proto::Member {
1046 user_id: self.encrypt_service_id(invitee)?,
1047 profile_key: vec![],
1048 presentation: vec![],
1049 role: role.into(),
1050 joined_at_version: 0,
1051 label_emoji: vec![],
1052 label_string: vec![],
1053 }),
1054 added_by_user_id: self.encrypt_aci(added_by_aci)?,
1055 timestamp: 0, }),
1057 },
1058 )
1059 }
1060}
1061
1062#[cfg(test)]
1063mod tests {
1064 use super::*;
1065
1066 use rand::RngCore;
1067 use zkgroup::groups::GroupMasterKey;
1068
1069 fn create_group_operations() -> GroupOperations {
1070 let master_key_bytes = [
1072 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
1073 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
1074 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
1075 ];
1076 let group_master_key = GroupMasterKey::new(master_key_bytes);
1077 let group_secret_params =
1078 GroupSecretParams::derive_from_master_key(group_master_key);
1079 GroupOperations::new(group_secret_params)
1080 }
1081
1082 #[test]
1083 fn roundtrip_title() {
1084 let ops = create_group_operations();
1085 let mut rng = rand::rng();
1086
1087 let title = "Test Group Title";
1088 let encrypted = ops.encrypt_title(title, &mut rng);
1089 let decrypted = ops.decrypt_title(&encrypted);
1090 assert_eq!(decrypted, title);
1091 }
1092
1093 #[test]
1094 fn roundtrip_description() {
1095 let ops = create_group_operations();
1096 let mut rng = rand::rng();
1097
1098 let description = "This is a test group description";
1099 let encrypted = ops.encrypt_description(Some(description), &mut rng);
1100 let decrypted = ops.decrypt_description_text(&encrypted);
1101 assert_eq!(decrypted, Some(description.to_string()));
1102 }
1103
1104 #[test]
1105 fn roundtrip_member_label() {
1106 let ops = create_group_operations();
1107 let mut rng = rand::rng();
1108
1109 let label = "Whisperfish / rubdos";
1110 let mut randomness = [0u8; 32];
1111 rng.fill_bytes(&mut randomness);
1112 let encrypted = ops.group_secret_params.encrypt_blob_with_padding(
1113 randomness,
1114 label.as_bytes(),
1115 0,
1116 );
1117
1118 assert_eq!(
1119 ops.decrypt_member_label_text(&encrypted),
1120 Some(label.to_string())
1121 );
1122 }
1123
1124 #[test]
1125 fn roundtrip_disappearing_message_timer() {
1126 let ops = create_group_operations();
1127 let mut rng = rand::rng();
1128
1129 let timer = Timer { duration: 3600 };
1130 let encrypted =
1131 ops.encrypt_disappearing_messages_timer(Some(&timer), &mut rng);
1132 let decrypted = ops.decrypt_disappearing_messages_timer(&encrypted);
1133 assert_eq!(decrypted, Some(timer));
1134 }
1135
1136 #[test]
1137 fn roundtrip_aci_encryption() {
1138 let ops = create_group_operations();
1139
1140 let aci = Aci::parse_from_service_id_string(
1142 "550e8400-e29b-41d4-a716-446655440000",
1143 )
1144 .expect("valid ACI");
1145 let encrypted =
1146 ops.encrypt_aci(aci).expect("encrypt_aci should succeed");
1147 let decrypted = ops
1148 .decrypt_aci(&encrypted)
1149 .expect("decrypt_aci should succeed");
1150 assert_eq!(decrypted, aci);
1151 }
1152
1153 #[test]
1154 fn roundtrip_service_id_encryption() {
1155 let ops = create_group_operations();
1156
1157 let service_id: ServiceId = ServiceId::parse_from_service_id_string(
1159 "550e8400-e29b-41d4-a716-446655440000",
1160 )
1161 .expect("valid service ID");
1162 let encrypted = ops
1163 .encrypt_service_id(service_id)
1164 .expect("encrypt_service_id should succeed");
1165 let decrypted = ops
1166 .decrypt_service_id(&encrypted)
1167 .expect("decrypt_service_id should succeed");
1168 assert_eq!(decrypted, service_id);
1169 }
1170
1171 #[test]
1172 fn roundtrip_service_id_pni_encryption() {
1173 let ops = create_group_operations();
1174
1175 let service_id: ServiceId = ServiceId::parse_from_service_id_string(
1177 "PNI:550e8400-e29b-41d4-a716-446655440000",
1178 )
1179 .expect("valid service ID");
1180 let encrypted = ops
1181 .encrypt_service_id(service_id)
1182 .expect("encrypt_service_id should succeed");
1183 let decrypted = ops
1184 .decrypt_service_id(&encrypted)
1185 .expect("decrypt_service_id should succeed");
1186 assert_eq!(decrypted, service_id);
1187 }
1188
1189 #[test]
1190 fn encrypt_title_different_each_time() {
1191 let ops = create_group_operations();
1192 let mut rng = rand::rng();
1193
1194 let title = "Test Title";
1195 let encrypted1 = ops.encrypt_title(title, &mut rng);
1196 let encrypted2 = ops.encrypt_title(title, &mut rng);
1197
1198 assert_ne!(encrypted1, encrypted2);
1201 assert_eq!(ops.decrypt_title(&encrypted1), title);
1202 assert_eq!(ops.decrypt_title(&encrypted2), title);
1203 }
1204}