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 terminated,
463 } = group;
464
465 let title = self.decrypt_title(&title);
466
467 let description_text = self.decrypt_description_text(&description);
468
469 let disappearing_messages_timer = self
470 .decrypt_disappearing_messages_timer(&disappearing_messages_timer);
471
472 let members = members
473 .into_iter()
474 .map(|m| self.decrypt_member(m))
475 .collect::<Result<_, _>>()?;
476
477 let members_pending_profile_key = members_pending_profile_key
478 .into_iter()
479 .map(|m| self.decrypt_pending_member(m))
480 .collect::<Result<_, _>>()?;
481
482 let members_pending_admin_approval = members_pending_admin_approval
483 .into_iter()
484 .map(|m| self.decrypt_requesting_member(m))
485 .collect::<Result<_, _>>()?;
486
487 let members_banned = members_banned
488 .into_iter()
489 .map(|m| self.decrypt_banned_member(m))
490 .collect::<Result<_, _>>()?;
491
492 let access_control =
493 access_control.map(TryInto::try_into).transpose()?;
494
495 Ok(Group {
496 title,
497 avatar: avatar_url,
498 disappearing_messages_timer,
499 access_control,
500 version,
501 members,
502 members_pending_profile_key,
503 members_pending_admin_approval,
504 invite_link_password,
505 description_text,
506 announcements_only,
507 members_banned,
508 terminated,
509 })
510 }
511
512 pub fn decrypt_group_change(
513 &self,
514 group_change: proto::GroupChange,
515 ) -> Result<GroupChanges, GroupDecodingError> {
516 let proto::GroupChange {
518 actions,
519 server_signature: _,
520 change_epoch,
521 } = group_change;
522
523 let proto::group_change::Actions {
524 group_id,
525 source_user_id,
526 version,
527 add_members,
528 delete_members,
529 modify_member_roles,
530 modify_member_profile_keys,
531 add_members_pending_profile_key,
532 delete_members_pending_profile_key,
533 promote_members_pending_profile_key,
534 modify_title,
535 modify_avatar,
536 modify_disappearing_message_timer,
537 modify_attributes_access,
538 modify_member_access,
539 modify_add_from_invite_link_access,
540 add_members_pending_admin_approval,
541 delete_members_pending_admin_approval,
542 promote_members_pending_admin_approval,
543 modify_invite_link_password,
544 modify_description,
545 modify_announcements_only,
546 add_members_banned,
547 delete_members_banned,
548 promote_members_pending_pni_aci_profile_key,
549 modify_member_labels,
550 modify_member_label_access,
551 terminate_group,
552 } = Message::decode(Bytes::from(actions))?;
553
554 let source_user_id = self.decrypt_aci(&source_user_id)?;
555
556 let new_members =
557 add_members
558 .into_iter()
559 .filter_map(|m| m.added)
560 .map(|added| {
561 Ok(GroupChange::NewMember(self.decrypt_member(added)?))
562 });
563
564 let delete_members = delete_members.into_iter().map(|c| {
565 Ok(GroupChange::DeleteMember(
566 self.decrypt_aci(&c.deleted_user_id)?,
567 ))
568 });
569
570 let modify_member_roles = modify_member_roles.into_iter().map(|m| {
571 Ok(GroupChange::ModifyMemberRole {
572 aci: self.decrypt_aci(&m.user_id)?,
573 role: m.role.try_into()?,
574 })
575 });
576
577 let modify_member_profile_keys =
578 modify_member_profile_keys.into_iter().map(|m| {
579 let (aci, profile_key) = self
580 .decrypt_profile_key_presentation(
581 &m.user_id,
582 &m.profile_key,
583 &m.presentation,
584 )?;
585 Ok(GroupChange::ModifyMemberProfileKey { aci, profile_key })
586 });
587
588 let add_members_pending_profile_key = add_members_pending_profile_key
589 .into_iter()
590 .filter_map(|m| m.added)
591 .map(|added| {
592 Ok(GroupChange::NewPendingMember(
593 self.decrypt_pending_member(added)?,
594 ))
595 });
596
597 let delete_members_pending_profile_key =
598 delete_members_pending_profile_key.into_iter().map(|m| {
599 Ok(GroupChange::DeletePendingMember(
600 self.decrypt_service_id(&m.deleted_user_id)?,
601 ))
602 });
603
604 let promote_members_pending_profile_key =
605 promote_members_pending_profile_key.into_iter().map(|m| {
606 let (aci, profile_key) = self
607 .decrypt_profile_key_presentation(
608 &m.user_id,
609 &m.profile_key,
610 &m.presentation,
611 )?;
612 Ok(GroupChange::PromotePendingMember {
613 address: aci.into(),
614 profile_key,
615 })
616 });
617
618 let modify_title = modify_title
619 .into_iter()
620 .map(|m| Ok(GroupChange::Title(self.decrypt_title(&m.title))));
621
622 let modify_avatar = modify_avatar
623 .into_iter()
624 .map(|m| Ok(GroupChange::Avatar(m.avatar)));
625
626 let modify_description = modify_description.into_iter().map(|m| {
627 Ok(GroupChange::Description(
628 self.decrypt_description_text(&m.description),
629 ))
630 });
631
632 let modify_disappearing_message_timer =
633 modify_disappearing_message_timer.into_iter().map(|m| {
634 Ok(GroupChange::Timer(
635 self.decrypt_disappearing_messages_timer(&m.timer),
636 ))
637 });
638
639 let modify_attributes_access =
640 modify_attributes_access.into_iter().map(|m| {
641 Ok(GroupChange::AttributeAccess(
642 m.attributes_access.try_into()?,
643 ))
644 });
645
646 let modify_member_access = modify_member_access.into_iter().map(|m| {
647 Ok(GroupChange::MemberAccess(m.members_access.try_into()?))
648 });
649
650 let add_members_banned = add_members_banned
651 .into_iter()
652 .filter_map(|m| m.added)
653 .map(|m| {
654 Ok(GroupChange::AddBannedMember(self.decrypt_banned_member(m)?))
655 });
656
657 let delete_members_banned =
658 delete_members_banned.into_iter().map(|m| {
659 Ok(GroupChange::DeleteBannedMember(
660 self.decrypt_service_id(&m.deleted_user_id)?,
661 ))
662 });
663
664 let promote_members_pending_pni_aci_profile_key =
665 promote_members_pending_pni_aci_profile_key
666 .into_iter()
667 .map(|m| {
668 let promoted =
669 self.decrypt_pni_aci_promotion_presentation(&m)?;
670 Ok(GroupChange::PromotePendingPniAciMemberProfileKey(
671 promoted,
672 ))
673 });
674
675 let modify_add_from_invite_link_access =
676 modify_add_from_invite_link_access.into_iter().map(|m| {
677 Ok(GroupChange::InviteLinkAccess(
678 m.add_from_invite_link_access.try_into()?,
679 ))
680 });
681
682 let add_members_pending_admin_approval =
683 add_members_pending_admin_approval
684 .into_iter()
685 .filter_map(|m| m.added)
686 .map(|added| {
687 Ok(GroupChange::NewRequestingMember(
688 self.decrypt_requesting_member(added)?,
689 ))
690 });
691
692 let delete_members_pending_admin_approval =
693 delete_members_pending_admin_approval.into_iter().map(|m| {
694 Ok(GroupChange::DeleteRequestingMember(
695 self.decrypt_aci(&m.deleted_user_id)?,
696 ))
697 });
698
699 let promote_members_pending_admin_approval =
700 promote_members_pending_admin_approval.into_iter().map(|m| {
701 Ok(GroupChange::PromoteRequestingMember {
702 aci: self.decrypt_aci(&m.user_id)?,
703 role: m.role.try_into()?,
704 })
705 });
706
707 let modify_invite_link_password =
708 modify_invite_link_password.into_iter().map(|m| {
709 Ok(GroupChange::InviteLinkPassword(
710 BASE64_RELAXED.encode(m.invite_link_password),
711 ))
712 });
713
714 let modify_announcements_only = modify_announcements_only
715 .into_iter()
716 .map(|m| Ok(GroupChange::AnnouncementOnly(m.announcements_only)));
717
718 let modify_member_labels = modify_member_labels.into_iter().map(|m| {
719 Ok(GroupChange::MemberLabel {
720 user_id: self.decrypt_service_id(&m.user_id)?,
721 label_emoji: self.decrypt_member_label_emoji(&m.label_emoji),
722 label_string: self.decrypt_member_label_text(&m.label_string),
723 })
724 });
725
726 let modify_member_label_access =
727 modify_member_label_access.into_iter().map(|m| {
728 Ok(GroupChange::MemberLabelAccess(
729 m.member_label_access.try_into()?,
730 ))
731 });
732
733 let terminate_group = terminate_group.into_iter().map(|_m| {
734 Ok(GroupChange::TerminateGroup)
737 });
738
739 let changes: Result<Vec<GroupChange>, GroupDecodingError> = new_members
740 .chain(delete_members)
741 .chain(modify_member_roles)
742 .chain(modify_member_profile_keys)
743 .chain(add_members_pending_profile_key)
744 .chain(delete_members_pending_profile_key)
745 .chain(promote_members_pending_profile_key)
746 .chain(modify_title)
747 .chain(modify_avatar)
748 .chain(modify_disappearing_message_timer)
749 .chain(modify_attributes_access)
750 .chain(modify_description)
751 .chain(modify_member_access)
752 .chain(add_members_banned)
753 .chain(delete_members_banned)
754 .chain(promote_members_pending_pni_aci_profile_key)
755 .chain(modify_add_from_invite_link_access)
756 .chain(add_members_pending_admin_approval)
757 .chain(delete_members_pending_admin_approval)
758 .chain(promote_members_pending_admin_approval)
759 .chain(modify_invite_link_password)
760 .chain(modify_announcements_only)
761 .chain(modify_member_labels)
762 .chain(modify_member_label_access)
763 .chain(terminate_group)
764 .collect();
765
766 Ok(GroupChanges {
767 group_id: group_id
768 .try_into()
769 .map_err(|_| GroupDecodingError::WrongBlob)?,
770 editor: source_user_id,
771 version,
772 changes: changes?,
773 change_epoch,
774 })
775 }
776
777 pub fn decrypt_avatar(&self, ciphertext: &[u8]) -> Option<Vec<u8>> {
778 use group_attribute_blob::Content;
779 match self.decrypt_blob(ciphertext).content {
780 Some(Content::Avatar(d)) => Some(d).filter(|d| !d.is_empty()),
781 _ => None,
782 }
783 }
784
785 pub fn build_add_member_action(
798 &self,
799 aci: Aci,
800 profile_key: ProfileKey,
801 role: super::model::Role,
802 ) -> Result<proto::group_change::actions::AddMemberAction, GroupDecodingError>
803 {
804 Ok(proto::group_change::actions::AddMemberAction {
805 added: Some(proto::Member {
806 user_id: self.encrypt_aci(aci)?,
807 profile_key: self.encrypt_profile_key(profile_key, aci)?,
808 presentation: vec![],
809 role: role.into(),
810 joined_at_version: 0, label_emoji: vec![],
813 label_string: vec![],
814 }),
815 join_from_invite_link: false,
816 })
817 }
818
819 pub fn build_remove_member_action(
821 &self,
822 aci: Aci,
823 ) -> Result<
824 proto::group_change::actions::DeleteMemberAction,
825 GroupDecodingError,
826 > {
827 Ok(proto::group_change::actions::DeleteMemberAction {
828 deleted_user_id: self.encrypt_aci(aci)?,
829 })
830 }
831
832 pub fn build_remove_pending_member_action(
840 &self,
841 invitee: ServiceId,
842 ) -> Result<
843 proto::group_change::actions::DeleteMemberPendingProfileKeyAction,
844 GroupDecodingError,
845 > {
846 Ok(
847 proto::group_change::actions::DeleteMemberPendingProfileKeyAction {
848 deleted_user_id: self.encrypt_service_id(invitee)?,
849 },
850 )
851 }
852
853 pub fn create_member_presentation(
858 &self,
859 server_public_params: &ServerPublicParams,
860 credential: &ExpiringProfileKeyCredential,
861 ) -> Vec<u8> {
862 let randomness: [u8; 32] = rand::random();
863 let presentation = server_public_params
864 .create_expiring_profile_key_credential_presentation(
865 randomness,
866 self.group_secret_params,
867 *credential,
868 );
869 zkgroup::serialize(&presentation)
870 }
871
872 #[allow(clippy::too_many_arguments)]
891 pub fn encrypt_group_with_credentials<R: rand::Rng + rand::CryptoRng>(
892 &self,
893 title: &str,
894 description: Option<&str>,
895 disappearing_messages_timer: Option<&Timer>,
896 access_control: Option<&AccessControl>,
897 self_credential: &ExpiringProfileKeyCredential,
898 member_candidates: &[GroupMemberCandidate],
899 server_public_params: &ServerPublicParams,
900 avatar_url: String,
901 rng: &mut R,
902 ) -> Result<proto::Group, GroupDecodingError> {
903 let mut members = Vec::new();
904 let mut members_pending_profile_key = Vec::new();
905
906 let self_presentation = self
908 .create_member_presentation(server_public_params, self_credential);
909 members.push(proto::Member {
910 user_id: vec![], profile_key: vec![], presentation: self_presentation,
913 role: proto::member::Role::Administrator.into(),
914 joined_at_version: 0,
915 label_emoji: vec![],
916 label_string: vec![],
917 });
918
919 for candidate in member_candidates {
921 if let Some(credential) = &candidate.credential {
922 let presentation = self.create_member_presentation(
924 server_public_params,
925 credential,
926 );
927 members.push(proto::Member {
928 user_id: vec![],
929 profile_key: vec![],
930 presentation,
931 role: proto::member::Role::Default.into(),
932 joined_at_version: 0,
933 label_emoji: vec![],
934 label_string: vec![],
935 });
936 } else {
937 let user_id_ciphertext =
939 self.encrypt_service_id(candidate.service_id)?;
940 let self_aci = self_credential.aci();
941 members_pending_profile_key.push(
942 proto::MemberPendingProfileKey {
943 member: Some(proto::Member {
944 user_id: user_id_ciphertext,
945 profile_key: vec![],
946 presentation: vec![],
947 role: proto::member::Role::Default.into(),
948 joined_at_version: 0,
949 label_emoji: vec![],
950 label_string: vec![],
951 }),
952 added_by_user_id: self.encrypt_aci(self_aci)?,
953 timestamp: 0, },
955 );
956 }
957 }
958
959 let encrypted_title = self.encrypt_title(title, rng);
961 let encrypted_description = self.encrypt_description(description, rng);
962 let encrypted_timer = self.encrypt_disappearing_messages_timer(
963 disappearing_messages_timer,
964 rng,
965 );
966
967 let proto_access_control =
969 access_control.map(|ac| proto::AccessControl {
970 attributes: ac.attributes.into(),
971 members: ac.members.into(),
972 add_from_invite_link: ac.add_from_invite_link.into(),
973 member_label: ac.member_label.into(),
974 });
975
976 Ok(proto::Group {
977 public_key: zkgroup::serialize(
978 &self.group_secret_params.get_public_params(),
979 ),
980 title: encrypted_title,
981 avatar_url,
982 disappearing_messages_timer: encrypted_timer,
983 access_control: proto_access_control,
984 version: 0,
985 members,
986 members_pending_profile_key,
987 members_pending_admin_approval: vec![],
988 invite_link_password: vec![],
989 description: encrypted_description,
990 announcements_only: false,
991 members_banned: vec![],
992 terminated: false,
993 })
994 }
995
996 pub fn build_add_member_action_with_credential(
1008 &self,
1009 credential: &ExpiringProfileKeyCredential,
1010 role: super::model::Role,
1011 server_public_params: &ServerPublicParams,
1012 ) -> proto::group_change::actions::AddMemberAction {
1013 let presentation =
1014 self.create_member_presentation(server_public_params, credential);
1015 proto::group_change::actions::AddMemberAction {
1016 added: Some(proto::Member {
1017 user_id: vec![], profile_key: vec![], presentation,
1020 role: role.into(),
1021 joined_at_version: 0, label_emoji: vec![],
1023 label_string: vec![],
1024 }),
1025 join_from_invite_link: false,
1026 }
1027 }
1028
1029 pub fn build_add_pending_member_action(
1045 &self,
1046 invitee: ServiceId,
1047 added_by_aci: Aci,
1048 role: super::model::Role,
1049 ) -> Result<
1050 proto::group_change::actions::AddMemberPendingProfileKeyAction,
1051 GroupDecodingError,
1052 > {
1053 Ok(
1054 proto::group_change::actions::AddMemberPendingProfileKeyAction {
1055 added: Some(proto::MemberPendingProfileKey {
1056 member: Some(proto::Member {
1057 user_id: self.encrypt_service_id(invitee)?,
1058 profile_key: vec![],
1059 presentation: vec![],
1060 role: role.into(),
1061 joined_at_version: 0,
1062 label_emoji: vec![],
1063 label_string: vec![],
1064 }),
1065 added_by_user_id: self.encrypt_aci(added_by_aci)?,
1066 timestamp: 0, }),
1068 },
1069 )
1070 }
1071}
1072
1073#[cfg(test)]
1074mod tests {
1075 use super::*;
1076
1077 use rand::RngCore;
1078 use zkgroup::groups::GroupMasterKey;
1079
1080 fn create_group_operations() -> GroupOperations {
1081 let master_key_bytes = [
1083 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
1084 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
1085 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
1086 ];
1087 let group_master_key = GroupMasterKey::new(master_key_bytes);
1088 let group_secret_params =
1089 GroupSecretParams::derive_from_master_key(group_master_key);
1090 GroupOperations::new(group_secret_params)
1091 }
1092
1093 #[test]
1094 fn roundtrip_title() {
1095 let ops = create_group_operations();
1096 let mut rng = rand::rng();
1097
1098 let title = "Test Group Title";
1099 let encrypted = ops.encrypt_title(title, &mut rng);
1100 let decrypted = ops.decrypt_title(&encrypted);
1101 assert_eq!(decrypted, title);
1102 }
1103
1104 #[test]
1105 fn roundtrip_description() {
1106 let ops = create_group_operations();
1107 let mut rng = rand::rng();
1108
1109 let description = "This is a test group description";
1110 let encrypted = ops.encrypt_description(Some(description), &mut rng);
1111 let decrypted = ops.decrypt_description_text(&encrypted);
1112 assert_eq!(decrypted, Some(description.to_string()));
1113 }
1114
1115 #[test]
1116 fn roundtrip_member_label() {
1117 let ops = create_group_operations();
1118 let mut rng = rand::rng();
1119
1120 let label = "Whisperfish / rubdos";
1121 let mut randomness = [0u8; 32];
1122 rng.fill_bytes(&mut randomness);
1123 let encrypted = ops.group_secret_params.encrypt_blob_with_padding(
1124 randomness,
1125 label.as_bytes(),
1126 0,
1127 );
1128
1129 assert_eq!(
1130 ops.decrypt_member_label_text(&encrypted),
1131 Some(label.to_string())
1132 );
1133 }
1134
1135 #[test]
1136 fn roundtrip_disappearing_message_timer() {
1137 let ops = create_group_operations();
1138 let mut rng = rand::rng();
1139
1140 let timer = Timer { duration: 3600 };
1141 let encrypted =
1142 ops.encrypt_disappearing_messages_timer(Some(&timer), &mut rng);
1143 let decrypted = ops.decrypt_disappearing_messages_timer(&encrypted);
1144 assert_eq!(decrypted, Some(timer));
1145 }
1146
1147 #[test]
1148 fn roundtrip_aci_encryption() {
1149 let ops = create_group_operations();
1150
1151 let aci = Aci::parse_from_service_id_string(
1153 "550e8400-e29b-41d4-a716-446655440000",
1154 )
1155 .expect("valid ACI");
1156 let encrypted =
1157 ops.encrypt_aci(aci).expect("encrypt_aci should succeed");
1158 let decrypted = ops
1159 .decrypt_aci(&encrypted)
1160 .expect("decrypt_aci should succeed");
1161 assert_eq!(decrypted, aci);
1162 }
1163
1164 #[test]
1165 fn roundtrip_service_id_encryption() {
1166 let ops = create_group_operations();
1167
1168 let service_id: ServiceId = ServiceId::parse_from_service_id_string(
1170 "550e8400-e29b-41d4-a716-446655440000",
1171 )
1172 .expect("valid service ID");
1173 let encrypted = ops
1174 .encrypt_service_id(service_id)
1175 .expect("encrypt_service_id should succeed");
1176 let decrypted = ops
1177 .decrypt_service_id(&encrypted)
1178 .expect("decrypt_service_id should succeed");
1179 assert_eq!(decrypted, service_id);
1180 }
1181
1182 #[test]
1183 fn roundtrip_service_id_pni_encryption() {
1184 let ops = create_group_operations();
1185
1186 let service_id: ServiceId = ServiceId::parse_from_service_id_string(
1188 "PNI:550e8400-e29b-41d4-a716-446655440000",
1189 )
1190 .expect("valid service ID");
1191 let encrypted = ops
1192 .encrypt_service_id(service_id)
1193 .expect("encrypt_service_id should succeed");
1194 let decrypted = ops
1195 .decrypt_service_id(&encrypted)
1196 .expect("decrypt_service_id should succeed");
1197 assert_eq!(decrypted, service_id);
1198 }
1199
1200 #[test]
1201 fn encrypt_title_different_each_time() {
1202 let ops = create_group_operations();
1203 let mut rng = rand::rng();
1204
1205 let title = "Test Title";
1206 let encrypted1 = ops.encrypt_title(title, &mut rng);
1207 let encrypted2 = ops.encrypt_title(title, &mut rng);
1208
1209 assert_ne!(encrypted1, encrypted2);
1212 assert_eq!(ops.decrypt_title(&encrypted1), title);
1213 assert_eq!(ops.decrypt_title(&encrypted2), title);
1214 }
1215}