Migrate local group to v2 if another member has migrated it

This commit is contained in:
AsamK 2020-12-12 11:14:36 +01:00
parent f6061f95de
commit c10910e466
7 changed files with 101 additions and 34 deletions

View file

@ -3,6 +3,10 @@ package org.asamk.signal.manager;
import org.asamk.signal.storage.groups.GroupInfo;
import org.asamk.signal.storage.groups.GroupInfoV1;
import org.asamk.signal.storage.groups.GroupInfoV2;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.groups.GroupMasterKey;
import org.signal.zkgroup.groups.GroupSecretParams;
import org.whispersystems.libsignal.kdf.HKDFv3;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2;
@ -25,4 +29,19 @@ public class GroupUtils {
messageBuilder.asGroupMessage(group);
}
}
public static byte[] getGroupId(GroupMasterKey groupMasterKey) {
final GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey);
return groupSecretParams.getPublicParams().getGroupIdentifier().serialize();
}
public static GroupMasterKey deriveV2MigrationMasterKey(byte[] groupId) {
try {
return new GroupMasterKey(new HKDFv3().deriveSecrets(groupId,
"GV2 Migration".getBytes(),
GroupMasterKey.SIZE));
} catch (InvalidInputException e) {
throw new AssertionError(e);
}
}
}

View file

@ -865,7 +865,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;
@ -1450,7 +1450,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 +1505,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 +1515,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)
@ -1526,26 +1535,7 @@ 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 ...");
}
groupInfoV2.setGroup(getDecryptedGroup(groupSecretParams));
account.getGroupStore().updateGroup(groupInfoV2);
}
}
@ -1633,6 +1623,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 +2324,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<JsonIdentityKeyStore.Identity> getIdentities() {
return account.getSignalProtocolStore().getIdentities();
}

View file

@ -57,7 +57,7 @@ public class ServiceConfig {
} catch (Throwable ignored) {
zkGroupAvailable = false;
}
capabilities = new AccountAttributes.Capabilities(false, zkGroupAvailable, false, false);
capabilities = new AccountAttributes.Capabilities(false, zkGroupAvailable, false, zkGroupAvailable);
}
public static SignalServiceConfiguration createDefaultServiceConfiguration(String userAgent) {