mirror of
https://github.com/oxen-io/session-android.git
synced 2025-12-30 23:26:23 +00:00
Validate incoming Group lengths and remote delete entries if wrong.
Ignore incoming messages with bad V1 group lengths.
This commit is contained in:
@@ -859,13 +859,14 @@ public class RecipientDatabase extends Database {
|
||||
for (SignalGroupV2Record insert : groupV2Inserts) {
|
||||
db.insertOrThrow(TABLE_NAME, null, getValuesForStorageGroupV2(insert));
|
||||
|
||||
GroupId.V2 groupId = GroupId.v2(insert.getMasterKey());
|
||||
Recipient recipient = Recipient.externalGroup(context, groupId);
|
||||
GroupMasterKey masterKey = insert.getMasterKeyOrThrow();
|
||||
GroupId.V2 groupId = GroupId.v2(masterKey);
|
||||
Recipient recipient = Recipient.externalGroup(context, groupId);
|
||||
|
||||
Log.i(TAG, "Creating restore placeholder for " + groupId);
|
||||
|
||||
DatabaseFactory.getGroupDatabase(context)
|
||||
.create(insert.getMasterKey(),
|
||||
.create(masterKey,
|
||||
DecryptedGroup.newBuilder()
|
||||
.setRevision(GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION)
|
||||
.build());
|
||||
@@ -886,7 +887,8 @@ public class RecipientDatabase extends Database {
|
||||
throw new AssertionError("Had an update, but it didn't match any rows!");
|
||||
}
|
||||
|
||||
Recipient recipient = Recipient.externalGroup(context, GroupId.v2(update.getOld().getMasterKey()));
|
||||
GroupMasterKey masterKey = update.getOld().getMasterKeyOrThrow();
|
||||
Recipient recipient = Recipient.externalGroup(context, GroupId.v2(masterKey));
|
||||
|
||||
threadDatabase.setArchived(recipient.getId(), update.getNew().isArchived());
|
||||
needsRefresh.add(recipient.getId());
|
||||
@@ -1031,7 +1033,7 @@ public class RecipientDatabase extends Database {
|
||||
|
||||
private static @NonNull ContentValues getValuesForStorageGroupV2(@NonNull SignalGroupV2Record groupV2) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(GROUP_ID, GroupId.v2(groupV2.getMasterKey()).toString());
|
||||
values.put(GROUP_ID, GroupId.v2(groupV2.getMasterKeyOrThrow()).toString());
|
||||
values.put(GROUP_TYPE, GroupType.SIGNAL_V2.getId());
|
||||
values.put(PROFILE_SHARING, groupV2.isProfileSharingEnabled() ? "1" : "0");
|
||||
values.put(BLOCKED, groupV2.isBlocked() ? "1" : "0");
|
||||
|
||||
@@ -19,6 +19,7 @@ public abstract class GroupId {
|
||||
private static final String ENCODED_MMS_GROUP_PREFIX = "__signal_mms_group__!";
|
||||
private static final int MMS_BYTE_LENGTH = 16;
|
||||
private static final int V1_MMS_BYTE_LENGTH = 16;
|
||||
private static final int V1_BYTE_LENGTH = 16;
|
||||
private static final int V2_BYTE_LENGTH = GroupIdentifier.SIZE;
|
||||
|
||||
private final String encodedId;
|
||||
@@ -46,6 +47,13 @@ public abstract class GroupId {
|
||||
return new GroupId.V1(gv1GroupIdBytes);
|
||||
}
|
||||
|
||||
public static @NonNull GroupId.V1 v1Exact(byte[] gv1GroupIdBytes) throws BadGroupIdException {
|
||||
if (gv1GroupIdBytes.length != V1_BYTE_LENGTH) {
|
||||
throw new BadGroupIdException();
|
||||
}
|
||||
return new GroupId.V1(gv1GroupIdBytes);
|
||||
}
|
||||
|
||||
public static GroupId.V1 createV1(@NonNull SecureRandom secureRandom) {
|
||||
return v1orThrow(Util.getSecretBytes(secureRandom, V1_MMS_BYTE_LENGTH));
|
||||
}
|
||||
@@ -54,15 +62,11 @@ public abstract class GroupId {
|
||||
return mms(Util.getSecretBytes(secureRandom, MMS_BYTE_LENGTH));
|
||||
}
|
||||
|
||||
public static GroupId.V2 v2orThrow(@NonNull byte[] bytes) {
|
||||
try {
|
||||
return v2(bytes);
|
||||
} catch (BadGroupIdException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static GroupId.V2 v2(@NonNull byte[] bytes) throws BadGroupIdException {
|
||||
/**
|
||||
* Private because it's too easy to pass the {@link GroupMasterKey} bytes directly to this as they
|
||||
* are the same length as the {@link GroupIdentifier}.
|
||||
*/
|
||||
private static GroupId.V2 v2(@NonNull byte[] bytes) throws BadGroupIdException {
|
||||
if (bytes.length != V2_BYTE_LENGTH) {
|
||||
throw new BadGroupIdException();
|
||||
}
|
||||
@@ -70,7 +74,11 @@ public abstract class GroupId {
|
||||
}
|
||||
|
||||
public static GroupId.V2 v2(@NonNull GroupIdentifier groupIdentifier) {
|
||||
return v2orThrow(groupIdentifier.serialize());
|
||||
try {
|
||||
return v2(groupIdentifier.serialize());
|
||||
} catch (BadGroupIdException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static GroupId.V2 v2(@NonNull GroupMasterKey masterKey) {
|
||||
|
||||
@@ -5,16 +5,16 @@ import androidx.annotation.NonNull;
|
||||
import com.annimon.stream.Collectors;
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.thoughtcrime.securesms.groups.BadGroupIdException;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.storage.SignalGroupV1Record;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
class GroupV1ConflictMerger implements StorageSyncHelper.ConflictMerger<SignalGroupV1Record> {
|
||||
final class GroupV1ConflictMerger implements StorageSyncHelper.ConflictMerger<SignalGroupV1Record> {
|
||||
|
||||
private final Map<GroupId, SignalGroupV1Record> localByGroupId;
|
||||
|
||||
@@ -29,7 +29,9 @@ class GroupV1ConflictMerger implements StorageSyncHelper.ConflictMerger<SignalGr
|
||||
|
||||
@Override
|
||||
public @NonNull Collection<SignalGroupV1Record> getInvalidEntries(@NonNull Collection<SignalGroupV1Record> remoteRecords) {
|
||||
return Collections.emptySet();
|
||||
return Stream.of(remoteRecords)
|
||||
.filterNot(GroupV1ConflictMerger::isValidGroupId)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -54,4 +56,13 @@ class GroupV1ConflictMerger implements StorageSyncHelper.ConflictMerger<SignalGr
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isValidGroupId(@NonNull SignalGroupV1Record record) {
|
||||
try {
|
||||
GroupId.v1Exact(record.getGroupId());
|
||||
return true;
|
||||
} catch (BadGroupIdException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import com.annimon.stream.Collectors;
|
||||
import com.annimon.stream.Stream;
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.signal.zkgroup.groups.GroupMasterKey;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
@@ -11,25 +12,26 @@ import org.whispersystems.signalservice.api.storage.SignalGroupV2Record;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
class GroupV2ConflictMerger implements StorageSyncHelper.ConflictMerger<SignalGroupV2Record> {
|
||||
final class GroupV2ConflictMerger implements StorageSyncHelper.ConflictMerger<SignalGroupV2Record> {
|
||||
|
||||
private final Map<GroupMasterKey, SignalGroupV2Record> localByGroupId;
|
||||
private final Map<ByteString, SignalGroupV2Record> localByMasterKeyBytes;
|
||||
|
||||
GroupV2ConflictMerger(@NonNull Collection<SignalGroupV2Record> localOnly) {
|
||||
localByGroupId = Stream.of(localOnly).collect(Collectors.toMap(SignalGroupV2Record::getMasterKey, g -> g));
|
||||
localByMasterKeyBytes = Stream.of(localOnly).collect(Collectors.toMap((SignalGroupV2Record signalGroupV2Record) -> ByteString.copyFrom(signalGroupV2Record.getMasterKeyBytes()), g -> g));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Optional<SignalGroupV2Record> getMatching(@NonNull SignalGroupV2Record record) {
|
||||
return Optional.fromNullable(localByGroupId.get(record.getMasterKey()));
|
||||
return Optional.fromNullable(localByMasterKeyBytes.get(ByteString.copyFrom(record.getMasterKeyBytes())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Collection<SignalGroupV2Record> getInvalidEntries(@NonNull Collection<SignalGroupV2Record> remoteRecords) {
|
||||
return Collections.emptySet();
|
||||
return Stream.of(remoteRecords)
|
||||
.filterNot(GroupV2ConflictMerger::isValidMasterKey)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -47,11 +49,15 @@ class GroupV2ConflictMerger implements StorageSyncHelper.ConflictMerger<SignalGr
|
||||
} else if (matchesLocal) {
|
||||
return local;
|
||||
} else {
|
||||
return new SignalGroupV2Record.Builder(keyGenerator.generate(), remote.getMasterKey())
|
||||
return new SignalGroupV2Record.Builder(keyGenerator.generate(), remote.getMasterKeyBytes())
|
||||
.setUnknownFields(unknownFields)
|
||||
.setBlocked(blocked)
|
||||
.setProfileSharingEnabled(blocked)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isValidMasterKey(@NonNull SignalGroupV2Record record) {
|
||||
return record.getMasterKeyBytes().length == GroupMasterKey.SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,6 +282,7 @@ public final class StorageSyncHelper {
|
||||
Set<SignalRecord> remoteDeletes = new HashSet<>();
|
||||
remoteDeletes.addAll(contactMergeResult.remoteDeletes);
|
||||
remoteDeletes.addAll(groupV1MergeResult.remoteDeletes);
|
||||
remoteDeletes.addAll(groupV2MergeResult.remoteDeletes);
|
||||
remoteDeletes.addAll(accountMergeResult.remoteDeletes);
|
||||
|
||||
return new MergeResult(contactMergeResult.localInserts,
|
||||
@@ -603,8 +604,8 @@ public final class StorageSyncHelper {
|
||||
@Override
|
||||
public @NonNull String toString() {
|
||||
return String.format(Locale.ENGLISH,
|
||||
"localContactInserts: %d, localContactUpdates: %d, localGroupV1Inserts: %d, localGroupV1Updates: %d, localGroupV2Inserts: %d, localGroupV2Updates: %d, localUnknownInserts: %d, localUnknownDeletes: %d, localAccountUpdate: %b, remoteInserts: %d, remoteUpdates: %d",
|
||||
localContactInserts.size(), localContactUpdates.size(), localGroupV1Inserts.size(), localGroupV1Updates.size(), localGroupV2Inserts.size(), localGroupV2Updates.size(), localUnknownInserts.size(), localUnknownDeletes.size(), localAccountUpdate.isPresent(), remoteInserts.size(), remoteUpdates.size());
|
||||
"localContactInserts: %d, localContactUpdates: %d, localGroupV1Inserts: %d, localGroupV1Updates: %d, localGroupV2Inserts: %d, localGroupV2Updates: %d, localUnknownInserts: %d, localUnknownDeletes: %d, localAccountUpdate: %b, remoteInserts: %d, remoteUpdates: %d, remoteDeletes: %d",
|
||||
localContactInserts.size(), localContactUpdates.size(), localGroupV1Inserts.size(), localGroupV1Updates.size(), localGroupV2Inserts.size(), localGroupV2Updates.size(), localUnknownInserts.size(), localUnknownDeletes.size(), localAccountUpdate.isPresent(), remoteInserts.size(), remoteUpdates.size(), remoteDeletes.size());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ public final class GroupUtil {
|
||||
throws BadGroupIdException
|
||||
{
|
||||
if (groupContext.getGroupV1().isPresent()) {
|
||||
return GroupId.v1(groupContext.getGroupV1().get().getGroupId());
|
||||
return GroupId.v1Exact(groupContext.getGroupV1().get().getGroupId());
|
||||
} else if (groupContext.getGroupV2().isPresent()) {
|
||||
return GroupId.v2(groupContext.getGroupV2().get().getMasterKey());
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user