Server signed group v2 changes sent and received P2P.

This commit is contained in:
Alan Evans 2020-05-19 16:02:24 -03:00 committed by Greyson Parrelli
parent ec8d5defd4
commit 2f9320989a
8 changed files with 98 additions and 49 deletions

View File

@ -148,11 +148,12 @@ public final class GroupManager {
public static void updateGroupFromServer(@NonNull Context context,
@NonNull GroupMasterKey groupMasterKey,
int version,
long timestamp)
long timestamp,
@Nullable byte[] signedGroupChange)
throws GroupChangeBusyException, IOException, GroupNotAMemberException
{
try (GroupManagerV2.GroupUpdater updater = new GroupManagerV2(context).updater(groupMasterKey)) {
updater.updateLocalToServerVersion(version, timestamp);
updater.updateLocalToServerVersion(version, timestamp, signedGroupChange);
}
}

View File

@ -6,6 +6,8 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.google.protobuf.InvalidProtocolBufferException;
import org.signal.storageservice.protos.groups.AccessControl;
import org.signal.storageservice.protos.groups.GroupChange;
import org.signal.storageservice.protos.groups.Member;
@ -144,7 +146,7 @@ final class GroupManagerV2 {
groupDatabase.onAvatarUpdated(groupId, avatar != null);
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(groupRecipient.getId(), true);
return sendGroupUpdate(masterKey, decryptedGroup, null);
return sendGroupUpdate(masterKey, decryptedGroup, null, null);
} catch (VerificationFailedException | InvalidGroupStateException e) {
throw new GroupChangeFailedException(e);
}
@ -354,7 +356,7 @@ final class GroupManagerV2 {
throws IOException, GroupNotAMemberException, GroupChangeFailedException
{
GroupsV2StateProcessor.GroupUpdateResult groupUpdateResult = groupsV2StateProcessor.forGroup(groupMasterKey)
.updateLocalGroupToRevision(GroupsV2StateProcessor.LATEST, System.currentTimeMillis());
.updateLocalGroupToRevision(GroupsV2StateProcessor.LATEST, System.currentTimeMillis(), null);
if (groupUpdateResult.getGroupState() != GroupsV2StateProcessor.GroupState.GROUP_UPDATED || groupUpdateResult.getLatestServer() == null) {
throw new GroupChangeFailedException();
@ -390,24 +392,24 @@ final class GroupManagerV2 {
throw new IOException(e);
}
commitToServer(changeActions);
GroupChange signedGroupChange = commitToServer(changeActions);
groupDatabase.update(groupId, decryptedGroupState);
return sendGroupUpdate(groupMasterKey, decryptedGroupState, decryptedChange);
return sendGroupUpdate(groupMasterKey, decryptedGroupState, decryptedChange, signedGroupChange);
}
private void commitToServer(GroupChange.Actions change)
private GroupChange commitToServer(GroupChange.Actions change)
throws GroupNotAMemberException, GroupChangeFailedException, IOException, GroupInsufficientRightsException
{
try {
groupsV2Api.patchGroup(change, groupSecretParams, authorization.getAuthorizationForToday(selfUuid, groupSecretParams));
return groupsV2Api.patchGroup(change, authorization.getAuthorizationForToday(selfUuid, groupSecretParams));
} catch (NotInGroupException e) {
Log.w(TAG, e);
throw new GroupNotAMemberException(e);
} catch (AuthorizationFailedException e) {
Log.w(TAG, e);
throw new GroupInsufficientRightsException(e);
} catch (VerificationFailedException | InvalidGroupStateException e) {
} catch (VerificationFailedException e) {
Log.w(TAG, e);
throw new GroupChangeFailedException(e);
}
@ -430,11 +432,25 @@ final class GroupManagerV2 {
}
@WorkerThread
void updateLocalToServerVersion(int version, long timestamp)
void updateLocalToServerVersion(int version, long timestamp, @Nullable byte[] signedGroupChange)
throws IOException, GroupNotAMemberException
{
new GroupsV2StateProcessor(context).forGroup(groupMasterKey)
.updateLocalGroupToRevision(version, timestamp);
.updateLocalGroupToRevision(version, timestamp, getDecryptedGroupChange(signedGroupChange));
}
private DecryptedGroupChange getDecryptedGroupChange(@Nullable byte[] signedGroupChange) {
if (signedGroupChange != null) {
GroupsV2Operations.GroupOperations groupOperations = groupsV2Operations.forGroup(GroupSecretParams.deriveFromMasterKey(groupMasterKey));
try {
return groupOperations.decryptChange(GroupChange.parseFrom(signedGroupChange), true);
} catch (VerificationFailedException | InvalidGroupStateException | InvalidProtocolBufferException e) {
Log.w(TAG, "Unable to verify supplied group change", e);
}
}
return null;
}
@Override
@ -445,11 +461,12 @@ final class GroupManagerV2 {
private @NonNull GroupManager.GroupActionResult sendGroupUpdate(@NonNull GroupMasterKey masterKey,
@NonNull DecryptedGroup decryptedGroup,
@Nullable DecryptedGroupChange plainGroupChange)
@Nullable DecryptedGroupChange plainGroupChange,
@Nullable GroupChange signedGroupChange)
{
GroupId.V2 groupId = GroupId.v2(masterKey);
Recipient groupRecipient = Recipient.externalGroup(context, groupId);
DecryptedGroupV2Context decryptedGroupV2Context = GroupProtoUtil.createDecryptedGroupV2Context(masterKey, decryptedGroup, plainGroupChange);
DecryptedGroupV2Context decryptedGroupV2Context = GroupProtoUtil.createDecryptedGroupV2Context(masterKey, decryptedGroup, plainGroupChange, signedGroupChange);
OutgoingGroupUpdateMessage outgoingMessage = new OutgoingGroupUpdateMessage(groupRecipient,
decryptedGroupV2Context,
null,

View File

@ -8,6 +8,7 @@ import androidx.annotation.WorkerThread;
import com.google.protobuf.ByteString;
import org.signal.storageservice.protos.groups.GroupChange;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
import org.signal.storageservice.protos.groups.local.DecryptedMember;
@ -48,16 +49,20 @@ public final class GroupProtoUtil {
public static DecryptedGroupV2Context createDecryptedGroupV2Context(@NonNull GroupMasterKey masterKey,
@NonNull DecryptedGroup decryptedGroup,
@Nullable DecryptedGroupChange plainGroupChange)
@Nullable DecryptedGroupChange plainGroupChange,
@Nullable GroupChange signedServerChange)
{
int version = plainGroupChange != null ? plainGroupChange.getVersion() : decryptedGroup.getVersion();
SignalServiceProtos.GroupContextV2 groupContext = SignalServiceProtos.GroupContextV2.newBuilder()
SignalServiceProtos.GroupContextV2.Builder contextBuilder = SignalServiceProtos.GroupContextV2.newBuilder()
.setMasterKey(ByteString.copyFrom(masterKey.serialize()))
.setRevision(version)
.build();
.setRevision(version);
if (signedServerChange != null) {
contextBuilder.setGroupChange(signedServerChange.toByteString());
}
DecryptedGroupV2Context.Builder builder = DecryptedGroupV2Context.newBuilder()
.setContext(groupContext)
.setContext(contextBuilder.build())
.setGroupState(decryptedGroup);
if (plainGroupChange != null) {

View File

@ -132,21 +132,47 @@ public final class GroupsV2StateProcessor {
*/
@WorkerThread
public GroupUpdateResult updateLocalGroupToRevision(final int revision,
final long timestamp)
final long timestamp,
@Nullable DecryptedGroupChange signedGroupChange)
throws IOException, GroupNotAMemberException
{
if (localIsAtLeast(revision)) {
return new GroupUpdateResult(GroupState.GROUP_CONSISTENT_OR_AHEAD, null);
}
GlobalGroupState inputGroupState;
GlobalGroupState inputGroupState = null;
DecryptedGroup localState = groupDatabase.getGroup(groupId)
.transform(g -> g.requireV2GroupProperties().getDecryptedGroup())
.orNull();
if (signedGroupChange != null &&
localState != null &&
localState.getVersion() + 1 == signedGroupChange.getVersion() &&
revision == signedGroupChange.getVersion())
{
try {
inputGroupState = queryServer();
Log.i(TAG, "Applying P2P group change");
DecryptedGroup newState = DecryptedGroupUtil.apply(localState, signedGroupChange);
inputGroupState = new GlobalGroupState(localState, Collections.singletonList(new GroupLogEntry(newState, signedGroupChange)));
} catch (DecryptedGroupUtil.NotAbleToApplyChangeException e) {
Log.w(TAG, "Unable to apply P2P group change", e);
}
}
if (inputGroupState == null) {
try {
inputGroupState = queryServer(localState);
} catch (GroupNotAMemberException e) {
Log.w(TAG, "Unable to query server for group " + groupId + " server says we're not in group, inserting leave message");
insertGroupLeave();
throw e;
}
} else {
Log.i(TAG, "Saved server query for group change");
}
AdvanceGroupStateResult advanceGroupStateResult = GroupStateMapper.partiallyAdvanceGroupState(inputGroupState, revision);
DecryptedGroup newLocalState = advanceGroupStateResult.getNewGlobalGroupState().getLocalState();
@ -185,7 +211,7 @@ public final class GroupsV2StateProcessor {
.addDeleteMembers(UuidUtil.toByteString(selfUuid))
.build();
DecryptedGroupV2Context decryptedGroupV2Context = GroupProtoUtil.createDecryptedGroupV2Context(masterKey, simulatedGroupState, simulatedGroupChange);
DecryptedGroupV2Context decryptedGroupV2Context = GroupProtoUtil.createDecryptedGroupV2Context(masterKey, simulatedGroupState, simulatedGroupChange, null);
OutgoingGroupUpdateMessage leaveMessage = new OutgoingGroupUpdateMessage(groupRecipient,
decryptedGroupV2Context,
null,
@ -245,7 +271,7 @@ public final class GroupsV2StateProcessor {
private void insertUpdateMessages(long timestamp, Collection<GroupLogEntry> processedLogEntries) {
for (GroupLogEntry entry : processedLogEntries) {
storeMessage(GroupProtoUtil.createDecryptedGroupV2Context(masterKey, entry.getGroup(), entry.getChange()), timestamp);
storeMessage(GroupProtoUtil.createDecryptedGroupV2Context(masterKey, entry.getGroup(), entry.getChange(), null), timestamp);
}
}
@ -266,15 +292,12 @@ public final class GroupsV2StateProcessor {
}
}
private GlobalGroupState queryServer()
private @NonNull GlobalGroupState queryServer(@Nullable DecryptedGroup localState)
throws IOException, GroupNotAMemberException
{
DecryptedGroup latestServerGroup;
List<GroupLogEntry> history;
UUID selfUuid = Recipient.self().getUuid().get();
DecryptedGroup localState = groupDatabase.getGroup(groupId)
.transform(g -> g.requireV2GroupProperties().getDecryptedGroup())
.orNull();
try {
latestServerGroup = groupsV2Api.getGroup(groupSecretParams, groupsV2Authorization.getAuthorizationForToday(selfUuid, groupSecretParams));

View File

@ -445,7 +445,7 @@ public final class PushProcessMessageJob extends BaseJob {
throws IOException, GroupChangeBusyException
{
try {
GroupManager.updateGroupFromServer(context, groupMasterKey, groupV2.getRevision(), content.getTimestamp());
GroupManager.updateGroupFromServer(context, groupMasterKey, groupV2.getRevision(), content.getTimestamp(), groupV2.getSignedGroupChange());
return true;
} catch (GroupNotAMemberException e) {
Log.w(TAG, "Ignoring message for a group we're not in");

View File

@ -87,7 +87,7 @@ public final class RequestGroupV2InfoJob extends BaseJob {
return;
}
GroupManager.updateGroupFromServer(context, group.get().requireV2GroupProperties().getGroupMasterKey(), toRevision, System.currentTimeMillis());
GroupManager.updateGroupFromServer(context, group.get().requireV2GroupProperties().getGroupMasterKey(), toRevision, System.currentTimeMillis(), null);
}
@Override

View File

@ -1072,10 +1072,17 @@ public class SignalServiceMessageSender {
}
private static GroupContextV2 createGroupContent(SignalServiceGroupV2 group) {
return GroupContextV2.newBuilder()
GroupContextV2.Builder builder = GroupContextV2.newBuilder()
.setMasterKey(ByteString.copyFrom(group.getMasterKey().serialize()))
.setRevision(group.getRevision())
.build();
.setRevision(group.getRevision());
byte[] signedGroupChange = group.getSignedGroupChange();
if (signedGroupChange != null && signedGroupChange.length <= 2048) {
builder.setGroupChange(ByteString.copyFrom(signedGroupChange));
}
return builder.build();
}
private List<DataMessage.Contact> createSharedContactContent(List<SharedContact> contacts) throws IOException {

View File

@ -132,15 +132,11 @@ public final class GroupsV2Api {
return form.getKey();
}
public DecryptedGroupChange patchGroup(GroupChange.Actions groupChange,
GroupSecretParams groupSecretParams,
public GroupChange patchGroup(GroupChange.Actions groupChange,
GroupsV2AuthorizationString authorization)
throws IOException, VerificationFailedException, InvalidGroupStateException
throws IOException
{
GroupChange groupChanges = socket.patchGroupsV2Group(groupChange, authorization.toString());
return groupsOperations.forGroup(groupSecretParams)
.decryptChange(groupChanges, true);
return socket.patchGroupsV2Group(groupChange, authorization.toString());
}
private static HashMap<Integer, AuthCredentialResponse> parseCredentialResponse(CredentialResponse credentialResponse)