diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java index 38cc0f897a..ee4ac65851 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java @@ -466,7 +466,8 @@ final class GroupManagerV2 { GroupsV2Operations.GroupOperations groupOperations = groupsV2Operations.forGroup(GroupSecretParams.deriveFromMasterKey(groupMasterKey)); try { - return groupOperations.decryptChange(GroupChange.parseFrom(signedGroupChange), true); + return groupOperations.decryptChange(GroupChange.parseFrom(signedGroupChange), true) + .orNull(); } catch (VerificationFailedException | InvalidGroupStateException | InvalidProtocolBufferException e) { Log.w(TAG, "Unable to verify supplied group change", e); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessor.java index 4f296a357f..720c19cba5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessor.java @@ -341,7 +341,12 @@ public final class GroupsV2StateProcessor { } for (DecryptedGroupHistoryEntry entry : groupStatesFromRevision) { - history.add(new ServerGroupLogEntry(entry.getGroup(), ignoreServerChanges ? null : entry.getChange())); + DecryptedGroup group = entry.getGroup().orNull(); + DecryptedGroupChange change = ignoreServerChanges ? null : entry.getChange().orNull(); + + if (group != null || change != null) { + history.add(new ServerGroupLogEntry(group, change)); + } } return history; diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/DecryptedGroupHistoryEntry.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/DecryptedGroupHistoryEntry.java index 1210fd5128..9dffc87602 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/DecryptedGroupHistoryEntry.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/DecryptedGroupHistoryEntry.java @@ -2,29 +2,32 @@ package org.whispersystems.signalservice.api.groupsv2; import org.signal.storageservice.protos.groups.local.DecryptedGroup; import org.signal.storageservice.protos.groups.local.DecryptedGroupChange; +import org.whispersystems.libsignal.util.guava.Optional; /** * Pair of a {@link DecryptedGroup} and the {@link DecryptedGroupChange} for that version. */ public final class DecryptedGroupHistoryEntry { - private final DecryptedGroup group; - private final DecryptedGroupChange change; + private final Optional group; + private final Optional change; - DecryptedGroupHistoryEntry(DecryptedGroup group, DecryptedGroupChange change) { - if (group.getRevision() != change.getRevision()) { - throw new AssertionError(); + DecryptedGroupHistoryEntry(Optional group, Optional change) + throws InvalidGroupStateException + { + if (group.isPresent() && change.isPresent() && group.get().getRevision() != change.get().getRevision()) { + throw new InvalidGroupStateException(); } this.group = group; this.change = change; } - public DecryptedGroup getGroup() { + public Optional getGroup() { return group; } - public DecryptedGroupChange getChange() { + public Optional getChange() { return change; } } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Api.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Api.java index e0b7dfdfa8..4e52e29795 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Api.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Api.java @@ -17,6 +17,7 @@ import org.signal.zkgroup.auth.AuthCredentialResponse; import org.signal.zkgroup.auth.ClientZkAuthOperations; import org.signal.zkgroup.groups.ClientZkGroupCipher; import org.signal.zkgroup.groups.GroupSecretParams; +import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.internal.push.PushServiceSocket; import java.io.IOException; @@ -100,12 +101,8 @@ public final class GroupsV2Api { GroupsV2Operations.GroupOperations groupOperations = groupsOperations.forGroup(groupSecretParams); for (GroupChanges.GroupChangeState change : changesList) { - DecryptedGroup decryptedGroup = groupOperations.decryptGroup(change.getGroupState()); - DecryptedGroupChange decryptedChange = groupOperations.decryptChange(change.getGroupChange(), false); - - if (decryptedChange.getRevision() != decryptedGroup.getRevision()) { - throw new InvalidGroupStateException(); - } + Optional decryptedGroup = change.hasGroupState () ? Optional.of(groupOperations.decryptGroup(change.getGroupState())) : Optional.absent(); + Optional decryptedChange = change.hasGroupChange() ? groupOperations.decryptChange(change.getGroupChange(), false) : Optional.absent(); result.add(new DecryptedGroupHistoryEntry(decryptedGroup, decryptedChange)); } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations.java index f7e8e7848e..cd27ec0073 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations.java @@ -39,6 +39,7 @@ import java.security.SecureRandom; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Set; import java.util.UUID; @@ -52,6 +53,9 @@ public final class GroupsV2Operations { /** Used for undecryptable pending invites */ public static final UUID UNKNOWN_UUID = UuidUtil.UNKNOWN_UUID; + /** Highest change epoch this class knows now to decrypt */ + public static final int HIGHEST_KNOWN_EPOCH = 0; + private final ServerPublicParams serverPublicParams; private final ClientZkProfileOperations clientZkProfileOperations; private final ClientZkAuthOperations clientZkAuthOperations; @@ -289,18 +293,24 @@ public final class GroupsV2Operations { } /** - * @param verify You might want to avoid verification if you already know it's correct, or you - * are not going to pass to other clients. - *

- * Also, if you know it's version 0, do not verify because changes for version 0 - * are not signed, but should be empty. + * @param verifySignature You might want to avoid verification if you already know it's correct, or you + * are not going to pass to other clients. + *

+ * Also, if you know it's version 0, do not verify because changes for version 0 + * are not signed, but should be empty. + * @return {@link Optional#absent} if the epoch for the change is higher that this code can decrypt. */ - public DecryptedGroupChange decryptChange(GroupChange groupChange, boolean verify) + public Optional decryptChange(GroupChange groupChange, boolean verifySignature) throws InvalidProtocolBufferException, VerificationFailedException, InvalidGroupStateException { - GroupChange.Actions actions = verify ? getVerifiedActions(groupChange) : getActions(groupChange); + if (groupChange.getChangeEpoch() > HIGHEST_KNOWN_EPOCH) { + Log.w(TAG, String.format(Locale.US, "Ignoring change from Epoch %d. Highest known Epoch is %d", groupChange.getChangeEpoch(), HIGHEST_KNOWN_EPOCH)); + return Optional.absent(); + } - return decryptChange(actions); + GroupChange.Actions actions = verifySignature ? getVerifiedActions(groupChange) : getActions(groupChange); + + return Optional.of(decryptChange(actions)); } public DecryptedGroupChange decryptChange(GroupChange.Actions actions) diff --git a/libsignal/service/src/main/proto/Groups.proto b/libsignal/service/src/main/proto/Groups.proto index b3e11d985d..656d707984 100644 --- a/libsignal/service/src/main/proto/Groups.proto +++ b/libsignal/service/src/main/proto/Groups.proto @@ -130,8 +130,9 @@ message GroupChange { ModifyMembersAccessControlAction modifyMemberAccess = 14; } - bytes actions = 1; - bytes serverSignature = 2; + bytes actions = 1; + bytes serverSignature = 2; + uint32 changeEpoch = 3; } message GroupChanges {