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 Ok(Member {
211 aci,
212 profile_key,
213 role: member.role.try_into()?,
214 joined_at_version: member.joined_at_version,
215 })
216 }
217
218 fn decrypt_pending_member(
219 &self,
220 member: proto::MemberPendingProfileKey,
221 ) -> Result<PendingMember, GroupDecodingError> {
222 let inner_member =
223 member.member.ok_or(GroupDecodingError::WrongBlob)?;
224 let service_id = self.decrypt_service_id(&inner_member.user_id)?;
225 let added_by_aci = self.decrypt_aci(&member.added_by_user_id)?;
226
227 Ok(PendingMember {
228 address: service_id,
229 role: inner_member.role.try_into()?,
230 added_by_aci,
231 timestamp: member.timestamp,
232 })
233 }
234
235 fn decrypt_requesting_member(
236 &self,
237 member: proto::MemberPendingAdminApproval,
238 ) -> Result<RequestingMember, GroupDecodingError> {
239 let (aci, profile_key) = self.decrypt_profile_key_presentation(
240 &member.user_id,
241 &member.profile_key,
242 &member.presentation,
243 )?;
244 Ok(RequestingMember {
245 profile_key,
246 aci,
247 timestamp: member.timestamp,
248 })
249 }
250
251 fn decrypt_banned_member(
252 &self,
253 member: proto::MemberBanned,
254 ) -> Result<BannedMember, GroupDecodingError> {
255 Ok(BannedMember {
256 user_id: self.decrypt_service_id(&member.user_id)?,
257 timestamp: member.timestamp,
258 })
259 }
260
261 fn decrypt_string(
262 &self,
263 bytes: &[u8],
264 ) -> Result<String, GroupDecodingError> {
265 let bytes = self.group_secret_params.decrypt_blob(bytes)?;
266 String::from_utf8(bytes).map_err(|_| GroupDecodingError::WrongBlob)
267 }
268
269 fn decrypt_blob(&self, bytes: &[u8]) -> GroupAttributeBlob {
270 if bytes.is_empty() {
271 GroupAttributeBlob::default()
272 } else if bytes.len() < 29 {
273 tracing::warn!("bad encrypted blob length");
274 GroupAttributeBlob::default()
275 } else {
276 self.group_secret_params
277 .decrypt_blob_with_padding(bytes)
278 .map_err(GroupDecodingError::from)
279 .and_then(|plaintext| {
280 GroupAttributeBlob::decode(Bytes::from(plaintext))
281 .map_err(GroupDecodingError::ProtobufDecodeError)
282 })
283 .unwrap_or_else(|e| {
284 tracing::warn!("bad encrypted blob: {}", e);
285 GroupAttributeBlob::default()
286 })
287 }
288 }
289
290 fn encrypt_blob_content<R: rand::Rng + rand::CryptoRng>(
309 &self,
310 content: group_attribute_blob::Content,
311 rng: &mut R,
312 ) -> Vec<u8> {
313 let blob = GroupAttributeBlob {
314 content: Some(content),
315 };
316 let buf = blob.encode_to_vec();
317
318 let mut randomness = [0u8; 32];
319 rng.fill_bytes(&mut randomness);
320 self.group_secret_params
321 .encrypt_blob_with_padding(randomness, &buf, 0)
322 }
323
324 pub fn encrypt_title<R: rand::Rng + rand::CryptoRng>(
325 &self,
326 title: &str,
327 rng: &mut R,
328 ) -> Vec<u8> {
329 self.encrypt_blob_content(
330 group_attribute_blob::Content::Title(title.to_string()),
331 rng,
332 )
333 }
334
335 pub fn encrypt_description<R: rand::Rng + rand::CryptoRng>(
336 &self,
337 description: Option<&str>,
338 rng: &mut R,
339 ) -> Vec<u8> {
340 self.encrypt_blob_content(
341 group_attribute_blob::Content::DescriptionText(
342 description.unwrap_or_default().to_string(),
343 ),
344 rng,
345 )
346 }
347
348 pub fn encrypt_disappearing_messages_timer<
349 R: rand::Rng + rand::CryptoRng,
350 >(
351 &self,
352 timer: Option<&Timer>,
353 rng: &mut R,
354 ) -> Vec<u8> {
355 self.encrypt_blob_content(
356 group_attribute_blob::Content::DisappearingMessagesDuration(
357 timer.map(|t| t.duration).unwrap_or(0),
358 ),
359 rng,
360 )
361 }
362
363 fn decrypt_title(&self, ciphertext: &[u8]) -> String {
364 use group_attribute_blob::Content;
365 match self.decrypt_blob(ciphertext).content {
366 Some(Content::Title(title)) => title,
367 _ => "".into(),
368 }
369 }
370
371 fn decrypt_description_text(&self, ciphertext: &[u8]) -> Option<String> {
372 use group_attribute_blob::Content;
373 match self.decrypt_blob(ciphertext).content {
374 Some(Content::DescriptionText(d)) => {
375 Some(d).filter(|d| !d.is_empty())
376 },
377 _ => None,
378 }
379 }
380
381 fn decrypt_disappearing_messages_timer(
382 &self,
383 ciphertext: &[u8],
384 ) -> Option<Timer> {
385 use group_attribute_blob::Content;
386 match self.decrypt_blob(ciphertext).content {
387 Some(Content::DisappearingMessagesDuration(duration)) => {
388 Some(Timer { duration })
389 },
390 _ => None,
391 }
392 }
393
394 pub fn new(group_secret_params: GroupSecretParams) -> Self {
395 Self {
396 group_secret_params,
397 }
398 }
399
400 pub fn decrypt_group(
401 &self,
402 group: proto::Group,
403 ) -> Result<Group, GroupDecodingError> {
404 let proto::Group {
406 public_key: _,
407 title,
408 avatar_url,
409 disappearing_messages_timer,
410 access_control,
411 version,
412 members,
413 members_pending_profile_key,
414 members_pending_admin_approval,
415 invite_link_password,
416 description,
417 announcements_only,
418 members_banned,
419 } = group;
420
421 let title = self.decrypt_title(&title);
422
423 let description_text = self.decrypt_description_text(&description);
424
425 let disappearing_messages_timer = self
426 .decrypt_disappearing_messages_timer(&disappearing_messages_timer);
427
428 let members = members
429 .into_iter()
430 .map(|m| self.decrypt_member(m))
431 .collect::<Result<_, _>>()?;
432
433 let members_pending_profile_key = members_pending_profile_key
434 .into_iter()
435 .map(|m| self.decrypt_pending_member(m))
436 .collect::<Result<_, _>>()?;
437
438 let members_pending_admin_approval = members_pending_admin_approval
439 .into_iter()
440 .map(|m| self.decrypt_requesting_member(m))
441 .collect::<Result<_, _>>()?;
442
443 let members_banned = members_banned
444 .into_iter()
445 .map(|m| self.decrypt_banned_member(m))
446 .collect::<Result<_, _>>()?;
447
448 let access_control =
449 access_control.map(TryInto::try_into).transpose()?;
450
451 Ok(Group {
452 title,
453 avatar: avatar_url,
454 disappearing_messages_timer,
455 access_control,
456 version,
457 members,
458 members_pending_profile_key,
459 members_pending_admin_approval,
460 invite_link_password,
461 description_text,
462 announcements_only,
463 members_banned,
464 })
465 }
466
467 pub fn decrypt_group_change(
468 &self,
469 group_change: proto::GroupChange,
470 ) -> Result<GroupChanges, GroupDecodingError> {
471 let proto::GroupChange {
473 actions,
474 server_signature: _,
475 change_epoch,
476 } = group_change;
477
478 let proto::group_change::Actions {
479 group_id,
480 source_user_id,
481 version,
482 add_members,
483 delete_members,
484 modify_member_roles,
485 modify_member_profile_keys,
486 add_members_pending_profile_key,
487 delete_members_pending_profile_key,
488 promote_members_pending_profile_key,
489 modify_title,
490 modify_avatar,
491 modify_disappearing_message_timer,
492 modify_attributes_access,
493 modify_member_access,
494 modify_add_from_invite_link_access,
495 add_members_pending_admin_approval,
496 delete_members_pending_admin_approval,
497 promote_members_pending_admin_approval,
498 modify_invite_link_password,
499 modify_description,
500 modify_announcements_only,
501 add_members_banned,
502 delete_members_banned,
503 promote_members_pending_pni_aci_profile_key,
504 modify_member_labels,
505 modify_member_label_access,
506 } = Message::decode(Bytes::from(actions))?;
507
508 let source_user_id = self.decrypt_aci(&source_user_id)?;
509
510 let new_members =
511 add_members
512 .into_iter()
513 .filter_map(|m| m.added)
514 .map(|added| {
515 Ok(GroupChange::NewMember(self.decrypt_member(added)?))
516 });
517
518 let delete_members = delete_members.into_iter().map(|c| {
519 Ok(GroupChange::DeleteMember(
520 self.decrypt_aci(&c.deleted_user_id)?,
521 ))
522 });
523
524 let modify_member_roles = modify_member_roles.into_iter().map(|m| {
525 Ok(GroupChange::ModifyMemberRole {
526 aci: self.decrypt_aci(&m.user_id)?,
527 role: m.role.try_into()?,
528 })
529 });
530
531 let modify_member_profile_keys =
532 modify_member_profile_keys.into_iter().map(|m| {
533 let (aci, profile_key) = self
534 .decrypt_profile_key_presentation(
535 &m.user_id,
536 &m.profile_key,
537 &m.presentation,
538 )?;
539 Ok(GroupChange::ModifyMemberProfileKey { aci, profile_key })
540 });
541
542 let add_members_pending_profile_key = add_members_pending_profile_key
543 .into_iter()
544 .filter_map(|m| m.added)
545 .map(|added| {
546 Ok(GroupChange::NewPendingMember(
547 self.decrypt_pending_member(added)?,
548 ))
549 });
550
551 let delete_members_pending_profile_key =
552 delete_members_pending_profile_key.into_iter().map(|m| {
553 Ok(GroupChange::DeletePendingMember(
554 self.decrypt_service_id(&m.deleted_user_id)?,
555 ))
556 });
557
558 let promote_members_pending_profile_key =
559 promote_members_pending_profile_key.into_iter().map(|m| {
560 let (aci, profile_key) = self
561 .decrypt_profile_key_presentation(
562 &m.user_id,
563 &m.profile_key,
564 &m.presentation,
565 )?;
566 Ok(GroupChange::PromotePendingMember {
567 address: aci.into(),
568 profile_key,
569 })
570 });
571
572 let modify_title = modify_title
573 .into_iter()
574 .map(|m| Ok(GroupChange::Title(self.decrypt_title(&m.title))));
575
576 let modify_avatar = modify_avatar
577 .into_iter()
578 .map(|m| Ok(GroupChange::Avatar(m.avatar)));
579
580 let modify_description = modify_description.into_iter().map(|m| {
581 Ok(GroupChange::Description(
582 self.decrypt_description_text(&m.description),
583 ))
584 });
585
586 let modify_disappearing_message_timer =
587 modify_disappearing_message_timer.into_iter().map(|m| {
588 Ok(GroupChange::Timer(
589 self.decrypt_disappearing_messages_timer(&m.timer),
590 ))
591 });
592
593 let modify_attributes_access =
594 modify_attributes_access.into_iter().map(|m| {
595 Ok(GroupChange::AttributeAccess(
596 m.attributes_access.try_into()?,
597 ))
598 });
599
600 let modify_member_access = modify_member_access.into_iter().map(|m| {
601 Ok(GroupChange::MemberAccess(m.members_access.try_into()?))
602 });
603
604 let add_members_banned = add_members_banned
605 .into_iter()
606 .filter_map(|m| m.added)
607 .map(|m| {
608 Ok(GroupChange::AddBannedMember(self.decrypt_banned_member(m)?))
609 });
610
611 let delete_members_banned =
612 delete_members_banned.into_iter().map(|m| {
613 Ok(GroupChange::DeleteBannedMember(
614 self.decrypt_service_id(&m.deleted_user_id)?,
615 ))
616 });
617
618 let promote_members_pending_pni_aci_profile_key =
619 promote_members_pending_pni_aci_profile_key
620 .into_iter()
621 .map(|m| {
622 let promoted =
623 self.decrypt_pni_aci_promotion_presentation(&m)?;
624 Ok(GroupChange::PromotePendingPniAciMemberProfileKey(
625 promoted,
626 ))
627 });
628
629 let modify_add_from_invite_link_access =
630 modify_add_from_invite_link_access.into_iter().map(|m| {
631 Ok(GroupChange::InviteLinkAccess(
632 m.add_from_invite_link_access.try_into()?,
633 ))
634 });
635
636 let add_members_pending_admin_approval =
637 add_members_pending_admin_approval
638 .into_iter()
639 .filter_map(|m| m.added)
640 .map(|added| {
641 Ok(GroupChange::NewRequestingMember(
642 self.decrypt_requesting_member(added)?,
643 ))
644 });
645
646 let delete_members_pending_admin_approval =
647 delete_members_pending_admin_approval.into_iter().map(|m| {
648 Ok(GroupChange::DeleteRequestingMember(
649 self.decrypt_aci(&m.deleted_user_id)?,
650 ))
651 });
652
653 let promote_members_pending_admin_approval =
654 promote_members_pending_admin_approval.into_iter().map(|m| {
655 Ok(GroupChange::PromoteRequestingMember {
656 aci: self.decrypt_aci(&m.user_id)?,
657 role: m.role.try_into()?,
658 })
659 });
660
661 let modify_invite_link_password =
662 modify_invite_link_password.into_iter().map(|m| {
663 Ok(GroupChange::InviteLinkPassword(
664 BASE64_RELAXED.encode(m.invite_link_password),
665 ))
666 });
667
668 let modify_announcements_only = modify_announcements_only
669 .into_iter()
670 .map(|m| Ok(GroupChange::AnnouncementOnly(m.announcements_only)));
671
672 let modify_member_labels = modify_member_labels.into_iter().map(|m| {
673 Ok(GroupChange::MemberLabel {
674 user_id: self.decrypt_service_id(&m.user_id)?,
675 label_emoji: self.decrypt_string(&m.label_emoji)?,
676 label_string: self.decrypt_string(&m.label_string)?,
677 })
678 });
679
680 let modify_member_label_access =
681 modify_member_label_access.into_iter().map(|m| {
682 Ok(GroupChange::MemberLabelAccess(
683 m.member_label_access.try_into()?,
684 ))
685 });
686
687 let changes: Result<Vec<GroupChange>, GroupDecodingError> = new_members
688 .chain(delete_members)
689 .chain(modify_member_roles)
690 .chain(modify_member_profile_keys)
691 .chain(add_members_pending_profile_key)
692 .chain(delete_members_pending_profile_key)
693 .chain(promote_members_pending_profile_key)
694 .chain(modify_title)
695 .chain(modify_avatar)
696 .chain(modify_disappearing_message_timer)
697 .chain(modify_attributes_access)
698 .chain(modify_description)
699 .chain(modify_member_access)
700 .chain(add_members_banned)
701 .chain(delete_members_banned)
702 .chain(promote_members_pending_pni_aci_profile_key)
703 .chain(modify_add_from_invite_link_access)
704 .chain(add_members_pending_admin_approval)
705 .chain(delete_members_pending_admin_approval)
706 .chain(promote_members_pending_admin_approval)
707 .chain(modify_invite_link_password)
708 .chain(modify_announcements_only)
709 .chain(modify_member_labels)
710 .chain(modify_member_label_access)
711 .collect();
712
713 Ok(GroupChanges {
714 group_id: group_id
715 .try_into()
716 .map_err(|_| GroupDecodingError::WrongBlob)?,
717 editor: source_user_id,
718 version,
719 changes: changes?,
720 change_epoch,
721 })
722 }
723
724 pub fn decrypt_avatar(&self, ciphertext: &[u8]) -> Option<Vec<u8>> {
725 use group_attribute_blob::Content;
726 match self.decrypt_blob(ciphertext).content {
727 Some(Content::Avatar(d)) => Some(d).filter(|d| !d.is_empty()),
728 _ => None,
729 }
730 }
731
732 pub fn build_add_member_action(
745 &self,
746 aci: Aci,
747 profile_key: ProfileKey,
748 role: super::model::Role,
749 ) -> Result<proto::group_change::actions::AddMemberAction, GroupDecodingError>
750 {
751 Ok(proto::group_change::actions::AddMemberAction {
752 added: Some(proto::Member {
753 user_id: self.encrypt_aci(aci)?,
754 profile_key: self.encrypt_profile_key(profile_key, aci)?,
755 presentation: vec![],
756 role: role.into(),
757 joined_at_version: 0, label_emoji: vec![],
760 label_string: vec![],
761 }),
762 join_from_invite_link: false,
763 })
764 }
765
766 pub fn build_remove_member_action(
768 &self,
769 aci: Aci,
770 ) -> Result<
771 proto::group_change::actions::DeleteMemberAction,
772 GroupDecodingError,
773 > {
774 Ok(proto::group_change::actions::DeleteMemberAction {
775 deleted_user_id: self.encrypt_aci(aci)?,
776 })
777 }
778
779 pub fn build_remove_pending_member_action(
787 &self,
788 invitee: ServiceId,
789 ) -> Result<
790 proto::group_change::actions::DeleteMemberPendingProfileKeyAction,
791 GroupDecodingError,
792 > {
793 Ok(
794 proto::group_change::actions::DeleteMemberPendingProfileKeyAction {
795 deleted_user_id: self.encrypt_service_id(invitee)?,
796 },
797 )
798 }
799
800 pub fn create_member_presentation(
805 &self,
806 server_public_params: &ServerPublicParams,
807 credential: &ExpiringProfileKeyCredential,
808 ) -> Vec<u8> {
809 let randomness: [u8; 32] = rand::random();
810 let presentation = server_public_params
811 .create_expiring_profile_key_credential_presentation(
812 randomness,
813 self.group_secret_params,
814 *credential,
815 );
816 zkgroup::serialize(&presentation)
817 }
818
819 #[allow(clippy::too_many_arguments)]
838 pub fn encrypt_group_with_credentials<R: rand::Rng + rand::CryptoRng>(
839 &self,
840 title: &str,
841 description: Option<&str>,
842 disappearing_messages_timer: Option<&Timer>,
843 access_control: Option<&AccessControl>,
844 self_credential: &ExpiringProfileKeyCredential,
845 member_candidates: &[GroupMemberCandidate],
846 server_public_params: &ServerPublicParams,
847 avatar_url: String,
848 rng: &mut R,
849 ) -> Result<proto::Group, GroupDecodingError> {
850 let mut members = Vec::new();
851 let mut members_pending_profile_key = Vec::new();
852
853 let self_presentation = self
855 .create_member_presentation(server_public_params, self_credential);
856 members.push(proto::Member {
857 user_id: vec![], profile_key: vec![], presentation: self_presentation,
860 role: proto::member::Role::Administrator.into(),
861 joined_at_version: 0,
862 label_emoji: vec![],
863 label_string: vec![],
864 });
865
866 for candidate in member_candidates {
868 if let Some(credential) = &candidate.credential {
869 let presentation = self.create_member_presentation(
871 server_public_params,
872 credential,
873 );
874 members.push(proto::Member {
875 user_id: vec![],
876 profile_key: vec![],
877 presentation,
878 role: proto::member::Role::Default.into(),
879 joined_at_version: 0,
880 label_emoji: vec![],
881 label_string: vec![],
882 });
883 } else {
884 let user_id_ciphertext =
886 self.encrypt_service_id(candidate.service_id)?;
887 let self_aci = self_credential.aci();
888 members_pending_profile_key.push(
889 proto::MemberPendingProfileKey {
890 member: Some(proto::Member {
891 user_id: user_id_ciphertext,
892 profile_key: vec![],
893 presentation: vec![],
894 role: proto::member::Role::Default.into(),
895 joined_at_version: 0,
896 label_emoji: vec![],
897 label_string: vec![],
898 }),
899 added_by_user_id: self.encrypt_aci(self_aci)?,
900 timestamp: 0, },
902 );
903 }
904 }
905
906 let encrypted_title = self.encrypt_title(title, rng);
908 let encrypted_description = self.encrypt_description(description, rng);
909 let encrypted_timer = self.encrypt_disappearing_messages_timer(
910 disappearing_messages_timer,
911 rng,
912 );
913
914 let proto_access_control =
916 access_control.map(|ac| proto::AccessControl {
917 attributes: ac.attributes.into(),
918 members: ac.members.into(),
919 add_from_invite_link: ac.add_from_invite_link.into(),
920 member_label: ac.member_label.into(),
921 });
922
923 Ok(proto::Group {
924 public_key: zkgroup::serialize(
925 &self.group_secret_params.get_public_params(),
926 ),
927 title: encrypted_title,
928 avatar_url,
929 disappearing_messages_timer: encrypted_timer,
930 access_control: proto_access_control,
931 version: 0,
932 members,
933 members_pending_profile_key,
934 members_pending_admin_approval: vec![],
935 invite_link_password: vec![],
936 description: encrypted_description,
937 announcements_only: false,
938 members_banned: vec![],
939 })
940 }
941
942 pub fn build_add_member_action_with_credential(
954 &self,
955 credential: &ExpiringProfileKeyCredential,
956 role: super::model::Role,
957 server_public_params: &ServerPublicParams,
958 ) -> proto::group_change::actions::AddMemberAction {
959 let presentation =
960 self.create_member_presentation(server_public_params, credential);
961 proto::group_change::actions::AddMemberAction {
962 added: Some(proto::Member {
963 user_id: vec![], profile_key: vec![], presentation,
966 role: role.into(),
967 joined_at_version: 0, label_emoji: vec![],
969 label_string: vec![],
970 }),
971 join_from_invite_link: false,
972 }
973 }
974
975 pub fn build_add_pending_member_action(
991 &self,
992 invitee: ServiceId,
993 added_by_aci: Aci,
994 role: super::model::Role,
995 ) -> Result<
996 proto::group_change::actions::AddMemberPendingProfileKeyAction,
997 GroupDecodingError,
998 > {
999 Ok(
1000 proto::group_change::actions::AddMemberPendingProfileKeyAction {
1001 added: Some(proto::MemberPendingProfileKey {
1002 member: Some(proto::Member {
1003 user_id: self.encrypt_service_id(invitee)?,
1004 profile_key: vec![],
1005 presentation: vec![],
1006 role: role.into(),
1007 joined_at_version: 0,
1008 label_emoji: vec![],
1009 label_string: vec![],
1010 }),
1011 added_by_user_id: self.encrypt_aci(added_by_aci)?,
1012 timestamp: 0, }),
1014 },
1015 )
1016 }
1017}
1018
1019#[cfg(test)]
1020mod tests {
1021 use super::*;
1022
1023 use zkgroup::groups::GroupMasterKey;
1024
1025 fn create_group_operations() -> GroupOperations {
1026 let master_key_bytes = [
1028 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
1029 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
1030 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
1031 ];
1032 let group_master_key = GroupMasterKey::new(master_key_bytes);
1033 let group_secret_params =
1034 GroupSecretParams::derive_from_master_key(group_master_key);
1035 GroupOperations::new(group_secret_params)
1036 }
1037
1038 #[test]
1039 fn roundtrip_title() {
1040 let ops = create_group_operations();
1041 let mut rng = rand::rng();
1042
1043 let title = "Test Group Title";
1044 let encrypted = ops.encrypt_title(title, &mut rng);
1045 let decrypted = ops.decrypt_title(&encrypted);
1046 assert_eq!(decrypted, title);
1047 }
1048
1049 #[test]
1050 fn roundtrip_description() {
1051 let ops = create_group_operations();
1052 let mut rng = rand::rng();
1053
1054 let description = "This is a test group description";
1055 let encrypted = ops.encrypt_description(Some(description), &mut rng);
1056 let decrypted = ops.decrypt_description_text(&encrypted);
1057 assert_eq!(decrypted, Some(description.to_string()));
1058 }
1059
1060 #[test]
1061 fn roundtrip_disappearing_message_timer() {
1062 let ops = create_group_operations();
1063 let mut rng = rand::rng();
1064
1065 let timer = Timer { duration: 3600 };
1066 let encrypted =
1067 ops.encrypt_disappearing_messages_timer(Some(&timer), &mut rng);
1068 let decrypted = ops.decrypt_disappearing_messages_timer(&encrypted);
1069 assert_eq!(decrypted, Some(timer));
1070 }
1071
1072 #[test]
1073 fn roundtrip_aci_encryption() {
1074 let ops = create_group_operations();
1075
1076 let aci = Aci::parse_from_service_id_string(
1078 "550e8400-e29b-41d4-a716-446655440000",
1079 )
1080 .expect("valid ACI");
1081 let encrypted =
1082 ops.encrypt_aci(aci).expect("encrypt_aci should succeed");
1083 let decrypted = ops
1084 .decrypt_aci(&encrypted)
1085 .expect("decrypt_aci should succeed");
1086 assert_eq!(decrypted, aci);
1087 }
1088
1089 #[test]
1090 fn roundtrip_service_id_encryption() {
1091 let ops = create_group_operations();
1092
1093 let service_id: ServiceId = ServiceId::parse_from_service_id_string(
1095 "550e8400-e29b-41d4-a716-446655440000",
1096 )
1097 .expect("valid service ID");
1098 let encrypted = ops
1099 .encrypt_service_id(service_id)
1100 .expect("encrypt_service_id should succeed");
1101 let decrypted = ops
1102 .decrypt_service_id(&encrypted)
1103 .expect("decrypt_service_id should succeed");
1104 assert_eq!(decrypted, service_id);
1105 }
1106
1107 #[test]
1108 fn roundtrip_service_id_pni_encryption() {
1109 let ops = create_group_operations();
1110
1111 let service_id: ServiceId = ServiceId::parse_from_service_id_string(
1113 "PNI:550e8400-e29b-41d4-a716-446655440000",
1114 )
1115 .expect("valid service ID");
1116 let encrypted = ops
1117 .encrypt_service_id(service_id)
1118 .expect("encrypt_service_id should succeed");
1119 let decrypted = ops
1120 .decrypt_service_id(&encrypted)
1121 .expect("decrypt_service_id should succeed");
1122 assert_eq!(decrypted, service_id);
1123 }
1124
1125 #[test]
1126 fn encrypt_title_different_each_time() {
1127 let ops = create_group_operations();
1128 let mut rng = rand::rng();
1129
1130 let title = "Test Title";
1131 let encrypted1 = ops.encrypt_title(title, &mut rng);
1132 let encrypted2 = ops.encrypt_title(title, &mut rng);
1133
1134 assert_ne!(encrypted1, encrypted2);
1137 assert_eq!(ops.decrypt_title(&encrypted1), title);
1138 assert_eq!(ops.decrypt_title(&encrypted2), title);
1139 }
1140}