X-Git-Url: https://git.nmode.ca/signal-cli/blobdiff_plain/4f2261e86f493a9c8954c02c52ddaa9a46490d20..1fd62ee342eb224017e044def60d5ffbf157be43:/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 c556ed41..887f9e42 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -43,6 +43,7 @@ import org.signal.libsignal.metadata.ProtocolLegacyMessageException; import org.signal.libsignal.metadata.ProtocolNoSessionException; 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.DecryptedMember; import org.signal.zkgroup.InvalidInputException; @@ -213,7 +214,9 @@ public class Manager implements Closeable { this.groupHelper = new GroupHelper(this::getRecipientProfileKeyCredential, this::getRecipientProfile, account::getSelfAddress, - groupsV2Operations); + groupsV2Operations, + groupsV2Api, + this::getGroupAuthForToday); } public String getUsername() { @@ -704,7 +707,7 @@ public class Manager implements Closeable { ) throws IOException, GroupNotFoundException, NotAGroupMemberException { final GroupInfo g = getGroupForSending(groupId); - GroupHelper.setGroupContext(messageBuilder, g); + GroupUtils.setGroupContext(messageBuilder, g); messageBuilder.withExpiration(g.getMessageExpirationTime()); return sendMessage(messageBuilder, g.getMembersWithout(account.getSelfAddress())); @@ -752,36 +755,6 @@ public class Manager implements Closeable { return sendMessage(messageBuilder, g.getMembersWithout(account.getSelfAddress())); } - private GroupInfoV2 createGroupV2( - String name, Collection members, InputStream avatar - ) throws IOException { - byte[] avatarBytes = avatar == null ? null : IOUtils.readFully(avatar); - final GroupsV2Operations.NewGroup newGroup = groupHelper.createGroupV2(name, members, avatarBytes); - final GroupSecretParams groupSecretParams = newGroup.getGroupSecretParams(); - - final GroupsV2AuthorizationString groupAuthForToday; - final DecryptedGroup decryptedGroup; - try { - groupAuthForToday = getGroupAuthForToday(groupSecretParams); - groupsV2Api.putNewGroup(newGroup, groupAuthForToday); - decryptedGroup = groupsV2Api.getGroup(groupSecretParams, groupAuthForToday); - } catch (IOException | VerificationFailedException | InvalidGroupStateException e) { - System.err.println("Failed to create V2 group: " + e.getMessage()); - return null; - } - if (decryptedGroup == null) { - System.err.println("Failed to create V2 group!"); - return null; - } - - final byte[] groupId = groupSecretParams.getPublicParams().getGroupIdentifier().serialize(); - final GroupMasterKey masterKey = groupSecretParams.getMasterKey(); - GroupInfoV2 g = new GroupInfoV2(groupId, masterKey); - g.setGroup(decryptedGroup); - - return g; - } - private Pair> sendUpdateGroupMessage( byte[] groupId, String name, Collection members, String avatarFile ) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException { @@ -789,8 +762,7 @@ public class Manager implements Closeable { SignalServiceDataMessage.Builder messageBuilder; if (groupId == null) { // Create new group - InputStream avatar = avatarFile == null ? null : new FileInputStream(avatarFile); - GroupInfoV2 gv2 = createGroupV2(name, members, avatar); + GroupInfoV2 gv2 = groupHelper.createGroupV2(name, members, avatarFile); if (gv2 == null) { GroupInfoV1 gv1 = new GroupInfoV1(KeyUtils.createGroupId()); gv1.addMembers(Collections.singleton(account.getSelfAddress())); @@ -798,18 +770,41 @@ public class Manager implements Closeable { messageBuilder = getGroupUpdateMessageBuilder(gv1); g = gv1; } else { - messageBuilder = getGroupUpdateMessageBuilder(gv2); + messageBuilder = getGroupUpdateMessageBuilder(gv2, null); g = gv2; } } else { GroupInfo group = getGroupForSending(groupId); - if (!(group instanceof GroupInfoV1)) { - throw new RuntimeException("TODO Not implemented!"); + if (group instanceof GroupInfoV2) { + Pair groupGroupChangePair = null; + if (members != null) { + final Set newMembers = new HashSet<>(members); + newMembers.removeAll(group.getMembers()); + if (newMembers.size() > 0) { + groupGroupChangePair = groupHelper.updateGroupV2((GroupInfoV2) group, newMembers); + } + } + 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); + } + + ((GroupInfoV2) group).setGroup(groupGroupChangePair.first()); + messageBuilder = getGroupUpdateMessageBuilder((GroupInfoV2) group, + groupGroupChangePair.second().toByteArray()); + g = group; + } else { + GroupInfoV1 gv1 = (GroupInfoV1) group; + updateGroupV1(gv1, name, members, avatarFile); + messageBuilder = getGroupUpdateMessageBuilder(gv1); + g = gv1; } - GroupInfoV1 gv1 = (GroupInfoV1) group; - updateGroupV1(gv1, name, members, avatarFile); - messageBuilder = getGroupUpdateMessageBuilder(gv1); - g = gv1; } account.getGroupStore().updateGroup(g); @@ -865,7 +860,7 @@ public class Manager implements Closeable { GroupInfoV1 g; GroupInfo group = getGroupForSending(groupId); if (!(group instanceof GroupInfoV1)) { - throw new RuntimeException("TODO Not implemented!"); + throw new RuntimeException("Received an invalid group request for a v2 group!"); } g = (GroupInfoV1) group; @@ -899,11 +894,10 @@ public class Manager implements Closeable { .withExpiration(g.getMessageExpirationTime()); } - private SignalServiceDataMessage.Builder getGroupUpdateMessageBuilder(GroupInfoV2 g) { + private SignalServiceDataMessage.Builder getGroupUpdateMessageBuilder(GroupInfoV2 g, byte[] signedGroupChange) { SignalServiceGroupV2.Builder group = SignalServiceGroupV2.newBuilder(g.getMasterKey()) .withRevision(g.getGroup().getRevision()) -// .withSignedGroupChange() // TODO - ; + .withSignedGroupChange(signedGroupChange); return SignalServiceDataMessage.newBuilder() .asGroupMessage(group.build()) .withExpiration(g.getMessageExpirationTime()); @@ -1427,16 +1421,20 @@ public class Manager implements Closeable { private GroupsV2AuthorizationString getGroupAuthForToday( final GroupSecretParams groupSecretParams - ) throws IOException, VerificationFailedException { + ) throws IOException { final int today = currentTimeDays(); // Returns credentials for the next 7 days final HashMap credentials = groupsV2Api.getCredentials(today); // TODO cache credentials until they expire AuthCredentialResponse authCredentialResponse = credentials.get(today); - return groupsV2Api.getGroupsV2AuthorizationString(account.getUuid(), - today, - groupSecretParams, - authCredentialResponse); + try { + return groupsV2Api.getGroupsV2AuthorizationString(account.getUuid(), + today, + groupSecretParams, + authCredentialResponse); + } catch (VerificationFailedException e) { + throw new IOException(e); + } } private List handleSignalServiceDataMessage( @@ -1450,7 +1448,7 @@ public class Manager implements Closeable { if (message.getGroupContext().isPresent()) { if (message.getGroupContext().get().getGroupV1().isPresent()) { SignalServiceGroup groupInfo = message.getGroupContext().get().getGroupV1().get(); - GroupInfo group = account.getGroupStore().getGroup(groupInfo.getGroupId()); + GroupInfo group = account.getGroupStore().getGroupByV1Id(groupInfo.getGroupId()); if (group == null || group instanceof GroupInfoV1) { GroupInfoV1 groupV1 = (GroupInfoV1) group; switch (groupInfo.getType()) { @@ -1505,7 +1503,7 @@ public class Manager implements Closeable { break; } } else { - System.err.println("Received a group v1 message for a v2 group: " + group.getTitle()); + // Received a group v1 message for a v2 group } } if (message.getGroupContext().get().getGroupV2().isPresent()) { @@ -1515,9 +1513,18 @@ public class Manager implements Closeable { final GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey); byte[] groupId = groupSecretParams.getPublicParams().getGroupIdentifier().serialize(); - GroupInfo groupInfo = account.getGroupStore().getGroup(groupId); + GroupInfo groupInfo = account.getGroupStore().getGroupByV2Id(groupId); if (groupInfo instanceof GroupInfoV1) { - // TODO upgrade group + // 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) @@ -1525,27 +1532,18 @@ public class Manager implements Closeable { if (groupInfoV2.getGroup() == null || groupInfoV2.getGroup().getRevision() < groupContext.getRevision()) { - // TODO check if revision is only 1 behind and a signedGroupChange is available - try { - final GroupsV2AuthorizationString groupsV2AuthorizationString = getGroupAuthForToday( - groupSecretParams); - final DecryptedGroup group = groupsV2Api.getGroup(groupSecretParams, - groupsV2AuthorizationString); - groupInfoV2.setGroup(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) { - } - } - } catch (IOException | VerificationFailedException | InvalidGroupStateException e) { - System.err.println("Failed to retrieve Group V2 info, ignoring ..."); + 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); } } @@ -1633,6 +1631,26 @@ 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) { + } + } + return group; + } catch (IOException | VerificationFailedException | InvalidGroupStateException e) { + System.err.println("Failed to retrieve Group V2 info, ignoring ..."); + return null; + } + } + private void retryFailedReceivedMessages( ReceiveMessageHandler handler, boolean ignoreAttachments ) { @@ -2314,11 +2332,6 @@ public class Manager implements Closeable { return account.getGroupStore().getGroup(groupId); } - public byte[] getGroupId(GroupMasterKey groupMasterKey) { - final GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey); - return groupSecretParams.getPublicParams().getGroupIdentifier().serialize(); - } - public List getIdentities() { return account.getSignalProtocolStore().getIdentities(); }