X-Git-Url: https://git.nmode.ca/signal-cli/blobdiff_plain/c49b05cd75dd8cee795859b6045fbec0040d4144..83d5d53d8a201cebbdbfb2ebe3f39d8d91a80091:/src/main/java/org/asamk/signal/manager/Manager.java diff --git a/src/main/java/org/asamk/signal/manager/Manager.java b/src/main/java/org/asamk/signal/manager/Manager.java index 26f5abbc..ab063d1b 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -45,6 +45,7 @@ import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException; import org.signal.libsignal.metadata.SelfSendException; import org.signal.storageservice.protos.groups.GroupChange; import org.signal.storageservice.protos.groups.local.DecryptedGroup; +import org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo; import org.signal.storageservice.protos.groups.local.DecryptedMember; import org.signal.zkgroup.InvalidInputException; import org.signal.zkgroup.VerificationFailedException; @@ -78,10 +79,10 @@ import org.whispersystems.signalservice.api.crypto.SignalServiceCipher; import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations; +import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException; import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api; import org.whispersystems.signalservice.api.groupsv2.GroupsV2AuthorizationString; import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations; -import org.whispersystems.signalservice.api.groupsv2.InvalidGroupStateException; import org.whispersystems.signalservice.api.messages.SendMessageResult; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer; @@ -322,6 +323,8 @@ public class Manager implements Closeable { contact.profileKey = null; account.getProfileStore().storeProfileKey(contact.getAddress(), profileKey); } + // Ensure our profile key is stored in profile store + account.getProfileStore().storeProfileKey(getSelfAddress(), account.getProfileKey()); } public void checkAccountState() throws IOException { @@ -705,6 +708,17 @@ public class Manager implements Closeable { return g; } + private GroupInfo getGroupForUpdating(byte[] groupId) throws GroupNotFoundException, NotAGroupMemberException { + GroupInfo g = account.getGroupStore().getGroup(groupId); + if (g == null) { + throw new GroupNotFoundException(groupId); + } + if (!g.isMember(account.getSelfAddress()) && !g.isPendingMember(account.getSelfAddress())) { + throw new NotAGroupMemberException(groupId, g.getTitle()); + } + return g; + } + public List getGroups() { return account.getGroupStore().getGroups(); } @@ -749,7 +763,7 @@ public class Manager implements Closeable { SignalServiceDataMessage.Builder messageBuilder; - final GroupInfo g = getGroupForSending(groupId); + final GroupInfo g = getGroupForUpdating(groupId); if (g instanceof GroupInfoV1) { GroupInfoV1 groupInfoV1 = (GroupInfoV1) g; SignalServiceGroup group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.QUIT) @@ -788,31 +802,39 @@ public class Manager implements Closeable { g = gv2; } } else { - GroupInfo group = getGroupForSending(groupId); + GroupInfo group = getGroupForUpdating(groupId); if (group instanceof GroupInfoV2) { - Pair groupGroupChangePair = null; + final GroupInfoV2 groupInfoV2 = (GroupInfoV2) group; + + Pair> result = null; + if (groupInfoV2.isPendingMember(getSelfAddress())) { + Pair groupGroupChangePair = groupHelper.acceptInvite(groupInfoV2); + result = sendUpdateGroupMessage(groupInfoV2, + groupGroupChangePair.first(), + groupGroupChangePair.second()); + } + if (members != null) { final Set newMembers = new HashSet<>(members); newMembers.removeAll(group.getMembers()); if (newMembers.size() > 0) { - groupGroupChangePair = groupHelper.updateGroupV2((GroupInfoV2) group, newMembers); + Pair groupGroupChangePair = groupHelper.updateGroupV2(groupInfoV2, + newMembers); + result = sendUpdateGroupMessage(groupInfoV2, + groupGroupChangePair.first(), + groupGroupChangePair.second()); } } - if (groupGroupChangePair == null || name != null || avatarFile != null) { - if (groupGroupChangePair != null) { - ((GroupInfoV2) group).setGroup(groupGroupChangePair.first()); - messageBuilder = getGroupUpdateMessageBuilder((GroupInfoV2) group, - groupGroupChangePair.second().toByteArray()); - sendMessage(messageBuilder, group.getMembersWithout(account.getSelfAddress())); - } - - groupGroupChangePair = groupHelper.updateGroupV2((GroupInfoV2) group, name, avatarFile); + if (result == null || name != null || avatarFile != null) { + Pair groupGroupChangePair = groupHelper.updateGroupV2(groupInfoV2, + name, + avatarFile); + result = sendUpdateGroupMessage(groupInfoV2, + groupGroupChangePair.first(), + groupGroupChangePair.second()); } - ((GroupInfoV2) group).setGroup(groupGroupChangePair.first()); - messageBuilder = getGroupUpdateMessageBuilder((GroupInfoV2) group, - groupGroupChangePair.second().toByteArray()); - g = group; + return new Pair<>(group.groupId, result.second()); } else { GroupInfoV1 gv1 = (GroupInfoV1) group; updateGroupV1(gv1, name, members, avatarFile); @@ -824,10 +846,48 @@ public class Manager implements Closeable { account.getGroupStore().updateGroup(g); final Pair> result = sendMessage(messageBuilder, - g.getMembersWithout(account.getSelfAddress())); + g.getMembersIncludingPendingWithout(account.getSelfAddress())); return new Pair<>(g.groupId, result.second()); } + public Pair> joinGroup( + GroupInviteLinkUrl inviteLinkUrl + ) throws IOException, GroupLinkNotActiveException { + return sendJoinGroupMessage(inviteLinkUrl); + } + + private Pair> sendJoinGroupMessage( + GroupInviteLinkUrl inviteLinkUrl + ) throws IOException, GroupLinkNotActiveException { + final DecryptedGroupJoinInfo groupJoinInfo = groupHelper.getDecryptedGroupJoinInfo(inviteLinkUrl.getGroupMasterKey(), + inviteLinkUrl.getPassword()); + final GroupChange groupChange = groupHelper.joinGroup(inviteLinkUrl.getGroupMasterKey(), + inviteLinkUrl.getPassword(), + groupJoinInfo); + final GroupInfoV2 group = getOrMigrateGroup(inviteLinkUrl.getGroupMasterKey(), + groupJoinInfo.getRevision() + 1, + groupChange.toByteArray()); + + if (group.getGroup() == null) { + // Only requested member, can't send update to group members + return new Pair<>(group.groupId, List.of()); + } + + final Pair> result = sendUpdateGroupMessage(group, group.getGroup(), groupChange); + + return new Pair<>(group.groupId, result.second()); + } + + private Pair> sendUpdateGroupMessage( + GroupInfoV2 group, DecryptedGroup newDecryptedGroup, GroupChange groupChange + ) throws IOException { + group.setGroup(newDecryptedGroup); + final SignalServiceDataMessage.Builder messageBuilder = getGroupUpdateMessageBuilder(group, + groupChange.toByteArray()); + account.getGroupStore().updateGroup(group); + return sendMessage(messageBuilder, group.getMembersIncludingPendingWithout(account.getSelfAddress())); + } + private void updateGroupV1( final GroupInfoV1 g, final String name, @@ -1553,45 +1613,12 @@ public class Manager implements Closeable { final SignalServiceGroupV2 groupContext = message.getGroupContext().get().getGroupV2().get(); final GroupMasterKey groupMasterKey = groupContext.getMasterKey(); - final GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey); - - byte[] groupId = groupSecretParams.getPublicParams().getGroupIdentifier().serialize(); - GroupInfo groupInfo = account.getGroupStore().getGroupByV2Id(groupId); - if (groupInfo instanceof GroupInfoV1) { - // Received a v2 group message for a v2 group, we need to locally migrate the group - account.getGroupStore().deleteGroup(groupInfo.groupId); - GroupInfoV2 groupInfoV2 = new GroupInfoV2(groupId, groupMasterKey); - groupInfoV2.setGroup(getDecryptedGroup(groupSecretParams)); - account.getGroupStore().updateGroup(groupInfoV2); - System.err.println("Locally migrated group " - + Base64.encodeBytes(groupInfo.groupId) - + " to group v2, id: " - + Base64.encodeBytes(groupInfoV2.groupId) - + " !!!"); - } else if (groupInfo == null || groupInfo instanceof GroupInfoV2) { - GroupInfoV2 groupInfoV2 = groupInfo == null - ? new GroupInfoV2(groupId, groupMasterKey) - : (GroupInfoV2) groupInfo; - - if (groupInfoV2.getGroup() == null - || groupInfoV2.getGroup().getRevision() < groupContext.getRevision()) { - DecryptedGroup group = null; - if (groupContext.hasSignedGroupChange() - && groupInfoV2.getGroup() != null - && groupInfoV2.getGroup().getRevision() + 1 == groupContext.getRevision()) { - group = groupHelper.getUpdatedDecryptedGroup(groupInfoV2.getGroup(), - groupContext.getSignedGroupChange(), - groupMasterKey); - } - if (group == null) { - group = getDecryptedGroup(groupSecretParams); - } - groupInfoV2.setGroup(group); - account.getGroupStore().updateGroup(groupInfoV2); - } - } + getOrMigrateGroup(groupMasterKey, + groupContext.getRevision(), + groupContext.hasSignedGroupChange() ? groupContext.getSignedGroupChange() : null); } } + final SignalServiceAddress conversationPartnerAddress = isSync ? destination : source; if (message.isEndSession()) { handleEndSession(conversationPartnerAddress); @@ -1674,23 +1701,58 @@ public class Manager implements Closeable { return actions; } - private DecryptedGroup getDecryptedGroup(final GroupSecretParams groupSecretParams) { - try { - final GroupsV2AuthorizationString groupsV2AuthorizationString = getGroupAuthForToday(groupSecretParams); - DecryptedGroup group = groupsV2Api.getGroup(groupSecretParams, groupsV2AuthorizationString); - for (DecryptedMember member : group.getMembersList()) { - final SignalServiceAddress address = resolveSignalServiceAddress(new SignalServiceAddress(UuidUtil.parseOrThrow( - member.getUuid().toByteArray()), null)); - try { - account.getProfileStore() - .storeProfileKey(address, new ProfileKey(member.getProfileKey().toByteArray())); - } catch (InvalidInputException ignored) { - } + private GroupInfoV2 getOrMigrateGroup( + final GroupMasterKey groupMasterKey, final int revision, final byte[] signedGroupChange + ) { + final GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey); + + byte[] groupId = groupSecretParams.getPublicParams().getGroupIdentifier().serialize(); + GroupInfo groupInfo = account.getGroupStore().getGroupByV2Id(groupId); + final GroupInfoV2 groupInfoV2; + if (groupInfo instanceof GroupInfoV1) { + // Received a v2 group message for a v1 group, we need to locally migrate the group + account.getGroupStore().deleteGroup(groupInfo.groupId); + groupInfoV2 = new GroupInfoV2(groupId, groupMasterKey); + System.err.println("Locally migrated group " + + Base64.encodeBytes(groupInfo.groupId) + + " to group v2, id: " + + Base64.encodeBytes(groupInfoV2.groupId) + + " !!!"); + } else if (groupInfo instanceof GroupInfoV2) { + groupInfoV2 = (GroupInfoV2) groupInfo; + } else { + groupInfoV2 = new GroupInfoV2(groupId, groupMasterKey); + } + + if (groupInfoV2.getGroup() == null || groupInfoV2.getGroup().getRevision() < revision) { + DecryptedGroup group = null; + if (signedGroupChange != null + && groupInfoV2.getGroup() != null + && groupInfoV2.getGroup().getRevision() + 1 == revision) { + group = groupHelper.getUpdatedDecryptedGroup(groupInfoV2.getGroup(), signedGroupChange, groupMasterKey); + } + if (group == null) { + group = groupHelper.getDecryptedGroup(groupSecretParams); + } + if (group != null) { + storeProfileKeysFromMembers(group); + } + groupInfoV2.setGroup(group); + account.getGroupStore().updateGroup(groupInfoV2); + } + + return groupInfoV2; + } + + private void storeProfileKeysFromMembers(final DecryptedGroup group) { + for (DecryptedMember member : group.getMembersList()) { + final SignalServiceAddress address = resolveSignalServiceAddress(new SignalServiceAddress(UuidUtil.parseOrThrow( + member.getUuid().toByteArray()), null)); + try { + account.getProfileStore() + .storeProfileKey(address, new ProfileKey(member.getProfileKey().toByteArray())); + } catch (InvalidInputException ignored) { } - return group; - } catch (IOException | VerificationFailedException | InvalidGroupStateException e) { - System.err.println("Failed to retrieve Group V2 info, ignoring ..."); - return null; } } @@ -1890,10 +1952,23 @@ public class Manager implements Closeable { if (content != null && content.getDataMessage().isPresent()) { SignalServiceDataMessage message = content.getDataMessage().get(); - if (message.getGroupContext().isPresent() && message.getGroupContext().get().getGroupV1().isPresent()) { - SignalServiceGroup groupInfo = message.getGroupContext().get().getGroupV1().get(); - GroupInfo group = getGroup(groupInfo.getGroupId()); - return groupInfo.getType() == SignalServiceGroup.Type.DELIVER && group != null && group.isBlocked(); + if (message.getGroupContext().isPresent()) { + GroupInfo group = null; + if (message.getGroupContext().get().getGroupV1().isPresent()) { + SignalServiceGroup groupInfo = message.getGroupContext().get().getGroupV1().get(); + if (groupInfo.getType() == SignalServiceGroup.Type.DELIVER) { + group = getGroup(groupInfo.getGroupId()); + } + } + if (message.getGroupContext().get().getGroupV2().isPresent()) { + SignalServiceGroupV2 groupContext = message.getGroupContext().get().getGroupV2().get(); + final GroupMasterKey groupMasterKey = groupContext.getMasterKey(); + byte[] groupId = GroupUtils.getGroupId(groupMasterKey); + group = account.getGroupStore().getGroupByV2Id(groupId); + } + if (group != null && group.isBlocked()) { + return true; + } } } return false;