]> nmode's Git Repositories - signal-cli/blob - src/main/java/org/asamk/signal/manager/helper/GroupHelper.java
Apply decrypted group change when receiving signed change
[signal-cli] / src / main / java / org / asamk / signal / manager / helper / GroupHelper.java
1 package org.asamk.signal.manager.helper;
2
3 import com.google.protobuf.InvalidProtocolBufferException;
4
5 import org.signal.storageservice.protos.groups.GroupChange;
6 import org.signal.storageservice.protos.groups.Member;
7 import org.signal.storageservice.protos.groups.local.DecryptedGroup;
8 import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
9 import org.signal.zkgroup.VerificationFailedException;
10 import org.signal.zkgroup.groups.GroupMasterKey;
11 import org.signal.zkgroup.groups.GroupSecretParams;
12 import org.signal.zkgroup.profiles.ProfileKeyCredential;
13 import org.whispersystems.libsignal.util.guava.Optional;
14 import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
15 import org.whispersystems.signalservice.api.groupsv2.GroupCandidate;
16 import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
17 import org.whispersystems.signalservice.api.groupsv2.InvalidGroupStateException;
18 import org.whispersystems.signalservice.api.groupsv2.NotAbleToApplyGroupV2ChangeException;
19 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
20
21 import java.util.Collection;
22 import java.util.Set;
23 import java.util.stream.Collectors;
24
25 public class GroupHelper {
26
27 private final ProfileKeyCredentialProvider profileKeyCredentialProvider;
28
29 private final ProfileProvider profileProvider;
30
31 private final SelfAddressProvider selfAddressProvider;
32
33 private final GroupsV2Operations groupsV2Operations;
34
35 public GroupHelper(
36 final ProfileKeyCredentialProvider profileKeyCredentialProvider,
37 final ProfileProvider profileProvider,
38 final SelfAddressProvider selfAddressProvider,
39 final GroupsV2Operations groupsV2Operations
40 ) {
41 this.profileKeyCredentialProvider = profileKeyCredentialProvider;
42 this.profileProvider = profileProvider;
43 this.selfAddressProvider = selfAddressProvider;
44 this.groupsV2Operations = groupsV2Operations;
45 }
46
47 public GroupsV2Operations.NewGroup createGroupV2(
48 String name, Collection<SignalServiceAddress> members, byte[] avatar
49 ) {
50 final ProfileKeyCredential profileKeyCredential = profileKeyCredentialProvider.getProfileKeyCredential(
51 selfAddressProvider.getSelfAddress());
52 if (profileKeyCredential == null) {
53 System.err.println("Cannot create a V2 group as self does not have a versioned profile");
54 return null;
55 }
56
57 final int noUuidCapability = members.stream()
58 .filter(address -> !address.getUuid().isPresent())
59 .collect(Collectors.toUnmodifiableSet())
60 .size();
61 if (noUuidCapability > 0) {
62 System.err.println("Cannot create a V2 group as " + noUuidCapability + " members don't have a UUID.");
63 return null;
64 }
65
66 final int noGv2Capability = members.stream()
67 .map(profileProvider::getProfile)
68 .filter(profile -> !profile.getCapabilities().gv2)
69 .collect(Collectors.toUnmodifiableSet())
70 .size();
71 if (noGv2Capability > 0) {
72 System.err.println("Cannot create a V2 group as " + noGv2Capability + " members don't support Groups V2.");
73 return null;
74 }
75
76 GroupCandidate self = new GroupCandidate(selfAddressProvider.getSelfAddress().getUuid().orNull(),
77 Optional.fromNullable(profileKeyCredential));
78 Set<GroupCandidate> candidates = members.stream()
79 .map(member -> new GroupCandidate(member.getUuid().get(),
80 Optional.fromNullable(profileKeyCredentialProvider.getProfileKeyCredential(member))))
81 .collect(Collectors.toSet());
82
83 final GroupSecretParams groupSecretParams = GroupSecretParams.generate();
84 return groupsV2Operations.createNewGroup(groupSecretParams,
85 name,
86 Optional.fromNullable(avatar),
87 self,
88 candidates,
89 Member.Role.DEFAULT,
90 0);
91 }
92
93 public DecryptedGroup getUpdatedDecryptedGroup(
94 DecryptedGroup group, byte[] signedGroupChange, GroupMasterKey groupMasterKey
95 ) {
96 try {
97 final DecryptedGroupChange decryptedGroupChange = getDecryptedGroupChange(signedGroupChange,
98 groupMasterKey);
99 if (decryptedGroupChange == null) {
100 return null;
101 }
102 return DecryptedGroupUtil.apply(group, decryptedGroupChange);
103 } catch (NotAbleToApplyGroupV2ChangeException e) {
104 return null;
105 }
106 }
107
108 private DecryptedGroupChange getDecryptedGroupChange(byte[] signedGroupChange, GroupMasterKey groupMasterKey) {
109 if (signedGroupChange != null) {
110 GroupsV2Operations.GroupOperations groupOperations = groupsV2Operations.forGroup(GroupSecretParams.deriveFromMasterKey(
111 groupMasterKey));
112
113 try {
114 return groupOperations.decryptChange(GroupChange.parseFrom(signedGroupChange), true).orNull();
115 } catch (VerificationFailedException | InvalidGroupStateException | InvalidProtocolBufferException e) {
116 return null;
117 }
118 }
119
120 return null;
121 }
122 }