Support Groups v2 Change Epochs.

This commit is contained in:
Alan Evans 2020-07-16 16:51:43 -03:00 committed by Greyson Parrelli
parent 70977e5228
commit b10fc6a0b0
6 changed files with 42 additions and 25 deletions

View File

@ -466,7 +466,8 @@ final class GroupManagerV2 {
GroupsV2Operations.GroupOperations groupOperations = groupsV2Operations.forGroup(GroupSecretParams.deriveFromMasterKey(groupMasterKey)); GroupsV2Operations.GroupOperations groupOperations = groupsV2Operations.forGroup(GroupSecretParams.deriveFromMasterKey(groupMasterKey));
try { try {
return groupOperations.decryptChange(GroupChange.parseFrom(signedGroupChange), true); return groupOperations.decryptChange(GroupChange.parseFrom(signedGroupChange), true)
.orNull();
} catch (VerificationFailedException | InvalidGroupStateException | InvalidProtocolBufferException e) { } catch (VerificationFailedException | InvalidGroupStateException | InvalidProtocolBufferException e) {
Log.w(TAG, "Unable to verify supplied group change", e); Log.w(TAG, "Unable to verify supplied group change", e);
} }

View File

@ -341,7 +341,12 @@ public final class GroupsV2StateProcessor {
} }
for (DecryptedGroupHistoryEntry entry : groupStatesFromRevision) { 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; return history;

View File

@ -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.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange; 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. * Pair of a {@link DecryptedGroup} and the {@link DecryptedGroupChange} for that version.
*/ */
public final class DecryptedGroupHistoryEntry { public final class DecryptedGroupHistoryEntry {
private final DecryptedGroup group; private final Optional<DecryptedGroup> group;
private final DecryptedGroupChange change; private final Optional<DecryptedGroupChange> change;
DecryptedGroupHistoryEntry(DecryptedGroup group, DecryptedGroupChange change) { DecryptedGroupHistoryEntry(Optional<DecryptedGroup> group, Optional<DecryptedGroupChange> change)
if (group.getRevision() != change.getRevision()) { throws InvalidGroupStateException
throw new AssertionError(); {
if (group.isPresent() && change.isPresent() && group.get().getRevision() != change.get().getRevision()) {
throw new InvalidGroupStateException();
} }
this.group = group; this.group = group;
this.change = change; this.change = change;
} }
public DecryptedGroup getGroup() { public Optional<DecryptedGroup> getGroup() {
return group; return group;
} }
public DecryptedGroupChange getChange() { public Optional<DecryptedGroupChange> getChange() {
return change; return change;
} }
} }

View File

@ -17,6 +17,7 @@ import org.signal.zkgroup.auth.AuthCredentialResponse;
import org.signal.zkgroup.auth.ClientZkAuthOperations; import org.signal.zkgroup.auth.ClientZkAuthOperations;
import org.signal.zkgroup.groups.ClientZkGroupCipher; import org.signal.zkgroup.groups.ClientZkGroupCipher;
import org.signal.zkgroup.groups.GroupSecretParams; import org.signal.zkgroup.groups.GroupSecretParams;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.internal.push.PushServiceSocket; import org.whispersystems.signalservice.internal.push.PushServiceSocket;
import java.io.IOException; import java.io.IOException;
@ -100,12 +101,8 @@ public final class GroupsV2Api {
GroupsV2Operations.GroupOperations groupOperations = groupsOperations.forGroup(groupSecretParams); GroupsV2Operations.GroupOperations groupOperations = groupsOperations.forGroup(groupSecretParams);
for (GroupChanges.GroupChangeState change : changesList) { for (GroupChanges.GroupChangeState change : changesList) {
DecryptedGroup decryptedGroup = groupOperations.decryptGroup(change.getGroupState()); Optional<DecryptedGroup> decryptedGroup = change.hasGroupState () ? Optional.of(groupOperations.decryptGroup(change.getGroupState())) : Optional.absent();
DecryptedGroupChange decryptedChange = groupOperations.decryptChange(change.getGroupChange(), false); Optional<DecryptedGroupChange> decryptedChange = change.hasGroupChange() ? groupOperations.decryptChange(change.getGroupChange(), false) : Optional.absent();
if (decryptedChange.getRevision() != decryptedGroup.getRevision()) {
throw new InvalidGroupStateException();
}
result.add(new DecryptedGroupHistoryEntry(decryptedGroup, decryptedChange)); result.add(new DecryptedGroupHistoryEntry(decryptedGroup, decryptedChange));
} }

View File

@ -39,6 +39,7 @@ import java.security.SecureRandom;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@ -52,6 +53,9 @@ public final class GroupsV2Operations {
/** Used for undecryptable pending invites */ /** Used for undecryptable pending invites */
public static final UUID UNKNOWN_UUID = UuidUtil.UNKNOWN_UUID; 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 ServerPublicParams serverPublicParams;
private final ClientZkProfileOperations clientZkProfileOperations; private final ClientZkProfileOperations clientZkProfileOperations;
private final ClientZkAuthOperations clientZkAuthOperations; 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 * @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. * are not going to pass to other clients.
* <p> * <p>
* Also, if you know it's version 0, do not verify because changes for version 0 * Also, if you know it's version 0, do not verify because changes for version 0
* are not signed, but should be empty. * 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<DecryptedGroupChange> decryptChange(GroupChange groupChange, boolean verifySignature)
throws InvalidProtocolBufferException, VerificationFailedException, InvalidGroupStateException 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) public DecryptedGroupChange decryptChange(GroupChange.Actions actions)

View File

@ -132,6 +132,7 @@ message GroupChange {
bytes actions = 1; bytes actions = 1;
bytes serverSignature = 2; bytes serverSignature = 2;
uint32 changeEpoch = 3;
} }
message GroupChanges { message GroupChanges {