From 05da743ea273ae776640ef59bee210ba43c1a384 Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Mon, 8 Feb 2021 16:44:26 +1100 Subject: [PATCH] configuration message handling --- .../securesms/ApplicationContext.java | 1 + .../securesms/database/GroupDatabase.java | 1 + .../securesms/database/Storage.kt | 9 +++++ .../securesms/jobs/PushDecryptJob.java | 6 +-- .../loki/protocol/MultiDeviceProtocol.kt | 40 ++++++++++++++++++- .../SingleRecipientNotificationBuilder.java | 2 +- .../libsession/messaging/StorageProtocol.kt | 3 ++ .../messages/control/ConfigurationMessage.kt | 9 +++-- .../api/SignalServiceMessageSender.java | 9 +---- .../api/crypto/SignalServiceCipher.java | 13 +++++- .../api/messages/SignalServiceContent.java | 27 +++++++++++-- 11 files changed, 97 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index e1563ebc89..a5c5697b3d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -206,6 +206,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc SessionMetaProtocol.Companion.configureIfNeeded(apiDB, userPublicKey); SyncMessagesProtocol.Companion.configureIfNeeded(apiDB, userPublicKey); } + org.session.libsignal.service.loki.protocol.shelved.multidevice.MultiDeviceProtocol.Companion.configureIfNeeded(apiDB); SessionManagementProtocol.Companion.configureIfNeeded(sessionResetImpl, sskDatabase, this); setUpP2PAPIIfNeeded(); PushNotificationAPI.Companion.configureIfNeeded(BuildConfig.DEBUG); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java index 22f2a6c301..3603b68f51 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java @@ -157,6 +157,7 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt while ((record = reader.getNext()) != null) { if (record.isActive()) { groups.add(record); } } + reader.close(); return groups; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index c8eb646915..3cc08abf81 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -27,6 +27,7 @@ import org.session.libsignal.service.api.messages.SignalServiceAttachment import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer import org.session.libsignal.service.api.messages.SignalServiceGroup import org.session.libsignal.service.internal.push.SignalServiceProtos +import org.session.libsignal.service.loki.api.opengroups.PublicChat import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase @@ -371,6 +372,14 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, return DatabaseFactory.getLokiAPIDatabase(context).getLatestClosedGroupEncryptionKeyPair(groupPublicKey) } + override fun getAllClosedGroupPublicKeys(): Set { + return DatabaseFactory.getSSKDatabase(context).getAllClosedGroupPublicKeys() + } + + override fun getAllOpenGroups(): Map { + return DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChats() + } + override fun getAllGroups(): List { return DatabaseFactory.getGroupDatabase(context).allGroups } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 0d23b6af7f..e7f3c02a54 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -80,6 +80,7 @@ import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase; import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase; import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol; import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocolV2; +import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol; import org.thoughtcrime.securesms.loki.protocol.SessionManagementProtocol; import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol; import org.thoughtcrime.securesms.loki.protocol.SessionResetImplementation; @@ -264,8 +265,8 @@ public class PushDecryptJob extends BaseJob implements InjectableType { SessionMetaProtocol.handleProfileUpdateIfNeeded(context, content); - if (content.getDeviceLink().isPresent()) { - throw new UnsupportedOperationException("Device link operations are not supported!"); + if (content.configurationMessageProto.isPresent()) { + MultiDeviceProtocol.handleConfigurationMessage(context, content.configurationMessageProto.get(), content.getSender()); } else if (content.getDataMessage().isPresent()) { SignalServiceDataMessage message = content.getDataMessage().get(); boolean isMediaMessage = message.getAttachments().isPresent() || message.getQuote().isPresent() || message.getSharedContacts().isPresent() || message.getPreviews().isPresent() || message.getSticker().isPresent(); @@ -277,7 +278,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType { if (message.getClosedGroupUpdateV2().isPresent()) { ClosedGroupsProtocolV2.handleMessage(context, message.getClosedGroupUpdateV2().get(), message.getTimestamp(), envelope.getSource(), content.getSender()); } - if (message.isEndSession()) { handleEndSessionMessage(content, smsMessageId); } else if (message.isGroupUpdate()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt index e3cefce922..bb1ce53fca 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt @@ -1,12 +1,18 @@ package org.thoughtcrime.securesms.loki.protocol import android.content.Context +import com.google.protobuf.ByteString +import org.session.libsession.messaging.MessagingConfiguration import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.service.api.push.SignalServiceAddress +import org.session.libsignal.service.internal.push.SignalServiceProtos +import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded +import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.logging.Log import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil +import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities import org.thoughtcrime.securesms.loki.utilities.recipient import java.util.* @@ -14,7 +20,7 @@ object MultiDeviceProtocol { @JvmStatic fun syncConfigurationIfNeeded(context: Context) { - val userPublicKey = TextSecurePreferences.getLocalNumber(context)!! + val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return val lastSyncTime = TextSecurePreferences.getLastConfigurationSyncTime(context) val now = System.currentTimeMillis() if (now - lastSyncTime < 2 * 24 * 60 * 60 * 1000) return @@ -35,7 +41,7 @@ object MultiDeviceProtocol { } fun forceSyncConfigurationNowIfNeeded(context: Context) { - val userPublicKey = TextSecurePreferences.getLocalNumber(context)!! + val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return val configurationMessage = ConfigurationMessage.getCurrent() val serializedMessage = configurationMessage.toProto()!!.toByteArray() val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender() @@ -50,4 +56,34 @@ object MultiDeviceProtocol { Log.d("Loki", "Failed to send configuration message due to error: $e.") } } + + @JvmStatic + fun handleConfigurationMessage(context: Context, content: SignalServiceProtos.Content, senderPublicKey: String) { + val configurationMessage = ConfigurationMessage.fromProto(content) ?: return + val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return + if (senderPublicKey != userPublicKey) return + val storage = MessagingConfiguration.shared.storage + val allClosedGroupPublicKeys = storage.getAllClosedGroupPublicKeys() + for (closedGroup in configurationMessage.closedGroups) { + if (allClosedGroupPublicKeys.contains(closedGroup.publicKey)) continue + + val closedGroupUpdate = SignalServiceProtos.ClosedGroupUpdateV2.newBuilder() + closedGroupUpdate.type = SignalServiceProtos.ClosedGroupUpdateV2.Type.NEW + closedGroupUpdate.publicKey = ByteString.copyFrom(Hex.fromStringCondensed(closedGroup.publicKey)) + closedGroupUpdate.name = closedGroup.name + val encryptionKeyPair = SignalServiceProtos.KeyPair.newBuilder() + encryptionKeyPair.publicKey = ByteString.copyFrom(closedGroup.encryptionKeyPair.publicKey.serialize().removing05PrefixIfNeeded()) + encryptionKeyPair.privateKey = ByteString.copyFrom(closedGroup.encryptionKeyPair.privateKey.serialize()) + closedGroupUpdate.encryptionKeyPair = encryptionKeyPair.build() + closedGroupUpdate.addAllMembers(closedGroup.members.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) }) + closedGroupUpdate.addAllAdmins(closedGroup.admins.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) }) + + ClosedGroupsProtocolV2.handleNewClosedGroup(context, closedGroupUpdate.build(), userPublicKey) + } + val allOpenGroups = storage.getAllOpenGroups().map { it.value.server } + for (openGroup in configurationMessage.openGroups) { + if (allOpenGroups.contains(openGroup)) continue + OpenGroupUtilities.addGroup(context, openGroup, 1) + } + } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java index 62bfcc8b5e..680c9ac684 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java @@ -327,7 +327,7 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil private static Drawable getPlaceholderDrawable(Context context, Recipient recipient) { String publicKey = recipient.getAddress().serialize(); String hepk = (recipient.isLocalNumber() && publicKey != null) - ? TextSecurePreferences.getMasterHexEncodedPublicKey(context) + ? TextSecurePreferences.getLocalNumber(context) : publicKey; String displayName = recipient.getName(); return AvatarPlaceholderGenerator.generate(context, 128, hepk, displayName); diff --git a/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt b/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt index 3749c731f3..2aa82f4971 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt @@ -21,6 +21,7 @@ import org.session.libsignal.libsignal.ecc.ECPrivateKey import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer import org.session.libsignal.service.api.messages.SignalServiceGroup import org.session.libsignal.service.internal.push.SignalServiceProtos +import org.session.libsignal.service.loki.api.opengroups.PublicChat interface StorageProtocol { @@ -109,6 +110,8 @@ interface StorageProtocol { fun getClosedGroupEncryptionKeyPairs(groupPublicKey: String): MutableList fun getLatestClosedGroupEncryptionKeyPair(groupPublicKey: String): ECKeyPair? // Groups + fun getAllClosedGroupPublicKeys(): Set + fun getAllOpenGroups(): Map fun getAllGroups(): List // Settings diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ConfigurationMessage.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ConfigurationMessage.kt index 818bbed9bd..aee6356740 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ConfigurationMessage.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ConfigurationMessage.kt @@ -10,6 +10,7 @@ import org.session.libsignal.libsignal.ecc.ECKeyPair import org.session.libsignal.service.internal.push.SignalServiceProtos import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded import org.session.libsignal.service.loki.utilities.toHexString +import org.session.libsignal.utilities.Hex class ConfigurationMessage(val closedGroups: List, val openGroups: List): ControlMessage() { @@ -36,14 +37,14 @@ class ConfigurationMessage(val closedGroups: List, val openGroups: fun toProto(): SignalServiceProtos.ConfigurationMessage.ClosedGroup? { val result = SignalServiceProtos.ConfigurationMessage.ClosedGroup.newBuilder() - result.publicKey = ByteString.copyFrom(publicKey.toByteArray()) + result.publicKey = ByteString.copyFrom(Hex.fromStringCondensed(publicKey)) result.name = name val encryptionKeyPairAsProto = SignalServiceProtos.KeyPair.newBuilder() encryptionKeyPairAsProto.publicKey = ByteString.copyFrom(encryptionKeyPair.publicKey.serialize().removing05PrefixIfNeeded()) encryptionKeyPairAsProto.privateKey = ByteString.copyFrom(encryptionKeyPair.privateKey.serialize()) result.encryptionKeyPair = encryptionKeyPairAsProto.build() - result.addAllMembers(members.map { ByteString.copyFrom(it.toByteArray()) }) - result.addAllAdmins(admins.map { ByteString.copyFrom(it.toByteArray()) }) + result.addAllMembers(members.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) }) + result.addAllAdmins(admins.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) }) return result.build() } } @@ -61,7 +62,7 @@ class ConfigurationMessage(val closedGroups: List, val openGroups: for (groupRecord in groups) { if (groupRecord.isClosedGroup) { if (!groupRecord.members.contains(Address.fromSerialized(storage.getUserPublicKey()!!))) continue - val groupPublicKey = GroupUtil.getDecodedGroupID(groupRecord.encodedId) // TODO: Check if this is correct. Does it need to be double decoded? + val groupPublicKey = GroupUtil.getDecodedGroupIDAsData(GroupUtil.getDecodedGroupID(groupRecord.encodedId)).toHexString() // Double decoded if (!storage.isClosedGroup(groupPublicKey)) continue val encryptionKeyPair = storage.getLatestClosedGroupEncryptionKeyPair(groupPublicKey) ?: continue val closedGroup = ClosedGroup(groupPublicKey, groupRecord.title, encryptionKeyPair, groupRecord.members.map { it.serialize() }, groupRecord.admins.map { it.serialize() }) diff --git a/libsignal/src/main/java/org/session/libsignal/service/api/SignalServiceMessageSender.java b/libsignal/src/main/java/org/session/libsignal/service/api/SignalServiceMessageSender.java index f0a9d2d0ec..bd23c1f91f 100644 --- a/libsignal/src/main/java/org/session/libsignal/service/api/SignalServiceMessageSender.java +++ b/libsignal/src/main/java/org/session/libsignal/service/api/SignalServiceMessageSender.java @@ -381,7 +381,6 @@ public class SignalServiceMessageSender { } else if (message.getStickerPackOperations().isPresent()) { content = createMultiDeviceStickerPackOperationContent(message.getStickerPackOperations().get()); } else if (message.getVerified().isPresent()) { - sendMessage(message.getVerified().get(), unidentifiedAccess); return; } else { throw new IOException("Unsupported sync message!"); @@ -452,12 +451,6 @@ public class SignalServiceMessageSender { result.getUrl()); } - private void sendMessage(VerifiedMessage message, Optional unidentifiedAccess) - throws IOException, UntrustedIdentityException - { - - } - private byte[] createTypingContent(SignalServiceTypingMessage message) { Content.Builder container = Content.newBuilder(); TypingMessage.Builder builder = TypingMessage.newBuilder(); @@ -1155,7 +1148,7 @@ public class SignalServiceMessageSender { final boolean notifyPNServer) throws IOException, UntrustedIdentityException { - if (recipient.getNumber().equals(userPublicKey)) { return SendMessageResult.success(recipient, false, false); } +// if (recipient.getNumber().equals(userPublicKey)) { return SendMessageResult.success(recipient, false, false); } final SettableFuture[] future = { new SettableFuture() }; OutgoingPushMessageList messages = getSessionProtocolEncryptedMessage(recipient, timestamp, content); // Loki - Remove this when we have shared sender keys diff --git a/libsignal/src/main/java/org/session/libsignal/service/api/crypto/SignalServiceCipher.java b/libsignal/src/main/java/org/session/libsignal/service/api/crypto/SignalServiceCipher.java index 8f9521793b..7c7f575b63 100644 --- a/libsignal/src/main/java/org/session/libsignal/service/api/crypto/SignalServiceCipher.java +++ b/libsignal/src/main/java/org/session/libsignal/service/api/crypto/SignalServiceCipher.java @@ -216,7 +216,18 @@ public class SignalServiceCipher { ); } - if (message.hasDeviceLinkMessage()) { + if (message.hasConfigurationMessage()) { + SignalServiceCipher.Metadata metadata = plaintext.getMetadata(); + SignalServiceContent content = new SignalServiceContent(message, metadata.getSender(), metadata.getSenderDevice(), metadata.getTimestamp()); + + if (message.hasDataMessage()) { + setProfile(message.getDataMessage(), content); + SignalServiceDataMessage signalServiceDataMessage = createSignalServiceMessage(metadata, message.getDataMessage()); + content.setDataMessage(signalServiceDataMessage); + } + + return content; + } else if (message.hasDeviceLinkMessage()) { SignalServiceProtos.DeviceLinkMessage protoDeviceLinkMessage = message.getDeviceLinkMessage(); String masterPublicKey = protoDeviceLinkMessage.getPrimaryPublicKey(); String slavePublicKey = protoDeviceLinkMessage.getSecondaryPublicKey(); diff --git a/libsignal/src/main/java/org/session/libsignal/service/api/messages/SignalServiceContent.java b/libsignal/src/main/java/org/session/libsignal/service/api/messages/SignalServiceContent.java index 5d5e02a9f9..0e978e5954 100644 --- a/libsignal/src/main/java/org/session/libsignal/service/api/messages/SignalServiceContent.java +++ b/libsignal/src/main/java/org/session/libsignal/service/api/messages/SignalServiceContent.java @@ -12,7 +12,9 @@ import org.session.libsignal.service.api.messages.SignalServiceNullMessage; import org.session.libsignal.service.api.messages.SignalServiceReceiptMessage; import org.session.libsignal.service.api.messages.SignalServiceTypingMessage; import org.session.libsignal.service.api.messages.calls.SignalServiceCallMessage; +import org.session.libsignal.service.api.messages.multidevice.ConfigurationMessage; import org.session.libsignal.service.api.messages.multidevice.SignalServiceSyncMessage; +import org.session.libsignal.service.internal.push.SignalServiceProtos; import org.session.libsignal.service.loki.protocol.shelved.multidevice.DeviceLink; import org.session.libsignal.service.loki.protocol.sessionmanagement.PreKeyBundleMessage; @@ -33,10 +35,11 @@ public class SignalServiceContent { private final Optional typingMessage; // Loki - private final Optional deviceLink; - public Optional preKeyBundleMessage = Optional.absent(); - public Optional senderDisplayName = Optional.absent(); - public Optional senderProfilePictureURL = Optional.absent(); + private final Optional deviceLink; + public Optional configurationMessageProto = Optional.absent(); + public Optional preKeyBundleMessage = Optional.absent(); + public Optional senderDisplayName = Optional.absent(); + public Optional senderProfilePictureURL = Optional.absent(); public SignalServiceContent(SignalServiceDataMessage message, String sender, int senderDevice, long timestamp, boolean needsReceipt, boolean isDeviceUnlinkingRequest) { this.sender = sender; @@ -128,6 +131,22 @@ public class SignalServiceContent { this.isDeviceUnlinkingRequest = false; } + public SignalServiceContent(SignalServiceProtos.Content configurationMessageProto, String sender, int senderDevice, long timestamp) { + this.sender = sender; + this.senderDevice = senderDevice; + this.timestamp = timestamp; + this.needsReceipt = false; + this.message = Optional.absent(); + this.synchronizeMessage = Optional.absent(); + this.callMessage = Optional.absent(); + this.nullMessage = Optional.absent(); + this.readMessage = Optional.absent(); + this.typingMessage = Optional.absent(); + this.deviceLink = Optional.absent(); + this.configurationMessageProto = Optional.fromNullable(configurationMessageProto); + this.isDeviceUnlinkingRequest = false; + } + public SignalServiceContent(SignalServiceNullMessage nullMessage, String sender, int senderDevice, long timestamp) { this.sender = sender; this.senderDevice = senderDevice;