From 944f85ddb94a4516ca6453ce8166ef5a0ac64d14 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Fri, 7 Aug 2020 10:17:35 +1000 Subject: [PATCH] Partially implement SSK group creation --- .../securesms/database/DatabaseFactory.java | 57 +++++++++++-------- .../loki/protocol/ClosedGroupsProtocol.kt | 50 ++++++++++++---- .../PushSessionRequestMessageSendJob.kt | 14 ++--- 3 files changed, 77 insertions(+), 44 deletions(-) diff --git a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java index 4465a5c1c0..d945967507 100644 --- a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java +++ b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java @@ -37,6 +37,7 @@ import org.thoughtcrime.securesms.loki.database.LokiPreKeyBundleDatabase; import org.thoughtcrime.securesms.loki.database.LokiPreKeyRecordDatabase; import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase; import org.thoughtcrime.securesms.loki.database.LokiUserDatabase; +import org.thoughtcrime.securesms.loki.database.SharedSenderKeysDatabase; import org.thoughtcrime.securesms.util.TextSecurePreferences; public class DatabaseFactory { @@ -73,6 +74,7 @@ public class DatabaseFactory { private final LokiMessageDatabase lokiMessageDatabase; private final LokiThreadDatabase lokiThreadDatabase; private final LokiUserDatabase lokiUserDatabase; + private final SharedSenderKeysDatabase sskDatabase; public static DatabaseFactory getInstance(Context context) { synchronized (lock) { @@ -187,6 +189,10 @@ public class DatabaseFactory { public static LokiUserDatabase getLokiUserDatabase(Context context) { return getInstance(context).lokiUserDatabase; } + + public static SharedSenderKeysDatabase getSSKDatabase(Context context) { + return getInstance(context).sskDatabase; + } // endregion public static void upgradeRestored(Context context, SQLiteDatabase database){ @@ -200,32 +206,33 @@ public class DatabaseFactory { DatabaseSecret databaseSecret = new DatabaseSecretProvider(context).getOrCreateDatabaseSecret(); AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret(); - this.databaseHelper = new SQLCipherOpenHelper(context, databaseSecret); - this.sms = new SmsDatabase(context, databaseHelper); - this.mms = new MmsDatabase(context, databaseHelper); - this.attachments = new AttachmentDatabase(context, databaseHelper, attachmentSecret); - this.media = new MediaDatabase(context, databaseHelper); - this.thread = new ThreadDatabase(context, databaseHelper); - this.mmsSmsDatabase = new MmsSmsDatabase(context, databaseHelper); - this.identityDatabase = new IdentityDatabase(context, databaseHelper); - this.draftDatabase = new DraftDatabase(context, databaseHelper); - this.pushDatabase = new PushDatabase(context, databaseHelper); - this.groupDatabase = new GroupDatabase(context, databaseHelper); - this.recipientDatabase = new RecipientDatabase(context, databaseHelper); - this.groupReceiptDatabase = new GroupReceiptDatabase(context, databaseHelper); - this.contactsDatabase = new ContactsDatabase(context); - this.preKeyDatabase = new OneTimePreKeyDatabase(context, databaseHelper); - this.signedPreKeyDatabase = new SignedPreKeyDatabase(context, databaseHelper); - this.sessionDatabase = new SessionDatabase(context, databaseHelper); - this.searchDatabase = new SearchDatabase(context, databaseHelper); - this.jobDatabase = new JobDatabase(context, databaseHelper); - this.stickerDatabase = new StickerDatabase(context, databaseHelper, attachmentSecret); - this.lokiAPIDatabase = new LokiAPIDatabase(context, databaseHelper); + this.databaseHelper = new SQLCipherOpenHelper(context, databaseSecret); + this.sms = new SmsDatabase(context, databaseHelper); + this.mms = new MmsDatabase(context, databaseHelper); + this.attachments = new AttachmentDatabase(context, databaseHelper, attachmentSecret); + this.media = new MediaDatabase(context, databaseHelper); + this.thread = new ThreadDatabase(context, databaseHelper); + this.mmsSmsDatabase = new MmsSmsDatabase(context, databaseHelper); + this.identityDatabase = new IdentityDatabase(context, databaseHelper); + this.draftDatabase = new DraftDatabase(context, databaseHelper); + this.pushDatabase = new PushDatabase(context, databaseHelper); + this.groupDatabase = new GroupDatabase(context, databaseHelper); + this.recipientDatabase = new RecipientDatabase(context, databaseHelper); + this.groupReceiptDatabase = new GroupReceiptDatabase(context, databaseHelper); + this.contactsDatabase = new ContactsDatabase(context); + this.preKeyDatabase = new OneTimePreKeyDatabase(context, databaseHelper); + this.signedPreKeyDatabase = new SignedPreKeyDatabase(context, databaseHelper); + this.sessionDatabase = new SessionDatabase(context, databaseHelper); + this.searchDatabase = new SearchDatabase(context, databaseHelper); + this.jobDatabase = new JobDatabase(context, databaseHelper); + this.stickerDatabase = new StickerDatabase(context, databaseHelper, attachmentSecret); + this.lokiAPIDatabase = new LokiAPIDatabase(context, databaseHelper); this.lokiContactPreKeyDatabase = new LokiPreKeyRecordDatabase(context, databaseHelper); - this.lokiPreKeyBundleDatabase = new LokiPreKeyBundleDatabase(context, databaseHelper); - this.lokiMessageDatabase = new LokiMessageDatabase(context, databaseHelper); - this.lokiThreadDatabase = new LokiThreadDatabase(context, databaseHelper); - this.lokiUserDatabase = new LokiUserDatabase(context, databaseHelper); + this.lokiPreKeyBundleDatabase = new LokiPreKeyBundleDatabase(context, databaseHelper); + this.lokiMessageDatabase = new LokiMessageDatabase(context, databaseHelper); + this.lokiThreadDatabase = new LokiThreadDatabase(context, databaseHelper); + this.lokiUserDatabase = new LokiUserDatabase(context, databaseHelper); + this.sskDatabase = new SharedSenderKeysDatabase(context, databaseHelper); } public void onApplicationLevelUpgrade(@NonNull Context context, @NonNull MasterSecret masterSecret, diff --git a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt index ea3f4acd7c..522bbce2f5 100644 --- a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt +++ b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt @@ -1,30 +1,56 @@ package org.thoughtcrime.securesms.loki.protocol import android.content.Context -import android.util.Log import nl.komponents.kovenant.Promise -import nl.komponents.kovenant.functional.map import org.thoughtcrime.securesms.ApplicationContext -import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore import org.thoughtcrime.securesms.database.Address import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.loki.utilities.recipient -import org.thoughtcrime.securesms.loki.utilities.timeout import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.sms.MessageSender +import org.thoughtcrime.securesms.sms.OutgoingTextMessage import org.thoughtcrime.securesms.util.GroupUtil +import org.thoughtcrime.securesms.util.Hex import org.thoughtcrime.securesms.util.TextSecurePreferences -import org.whispersystems.libsignal.SignalProtocolAddress -import org.whispersystems.signalservice.api.messages.SignalServiceContent -import org.whispersystems.signalservice.api.messages.SignalServiceGroup -import org.whispersystems.signalservice.api.push.SignalServiceAddress -import org.whispersystems.signalservice.loki.api.SnodeAPI -import org.whispersystems.signalservice.loki.api.fileserver.FileServerAPI -import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.MultiDeviceProtocol +import org.whispersystems.libsignal.ecc.Curve +import org.whispersystems.signalservice.loki.protocol.closedgroups.ClosedGroupSenderKey +import org.whispersystems.signalservice.loki.protocol.closedgroups.SharedSenderKeysImplementation +import org.whispersystems.signalservice.loki.utilities.hexEncodedPrivateKey +import org.whispersystems.signalservice.loki.utilities.hexEncodedPublicKey import java.util.* object ClosedGroupsProtocol { + public fun createClosedGroup(context: Context, name: String, members: Collection): Promise { + // Prepare + val userPublicKey = TextSecurePreferences.getLocalNumber(context) + // Generate a key pair for the group + val groupKeyPair = Curve.generateKeyPair() + val groupPublicKey = groupKeyPair.hexEncodedPublicKey // Includes the "05" prefix + val membersAsData = members.map { Hex.fromStringCondensed(it) } + // Create ratchets for all members + val senderKeys: List = members.map { publicKey -> + val ratchet = SharedSenderKeysImplementation.shared.generateRatchet(groupPublicKey, publicKey) + ClosedGroupSenderKey(Hex.fromStringCondensed(ratchet.chainKey), ratchet.keyIndex, Hex.fromStringCondensed(publicKey)) + } + // Create the group + val groupID = GroupUtil.getEncodedId(Hex.fromStringCondensed(groupPublicKey), false); + val admins = setOf( Address.fromSerialized(userPublicKey) ) + DatabaseFactory.getGroupDatabase(context).create(groupID, name, LinkedList
(members.map { Address.fromSerialized(it) }), + null, null, LinkedList
(admins)) + DatabaseFactory.getRecipientDatabase(context).setProfileSharing(Recipient.from(context, Address.fromSerialized(groupID), false), true) + // Establish sessions if needed + establishSessionsWithMembersIfNeeded(context, members) + // Send a closed group update message to all members using established channels + // TODO + // Add the group to the user's set of public keys to poll for + DatabaseFactory.getSSKDatabase(context).setClosedGroupPrivateKey(groupPublicKey, groupKeyPair.hexEncodedPrivateKey) + // Notify the user + // TODO + // Return + return Promise.of(Unit) + } + @JvmStatic fun shouldIgnoreContentMessage(context: Context, address: Address, groupID: String?, senderPublicKey: String): Boolean { if (!address.isClosedGroup || groupID == null) { return false } @@ -84,7 +110,7 @@ object ClosedGroupsProtocol { } @JvmStatic - fun establishSessionsWithMembersIfNeeded(context: Context, members: List) { + fun establishSessionsWithMembersIfNeeded(context: Context, members: Collection) { @Suppress("NAME_SHADOWING") val members = members.toMutableSet() /* val allDevices = members.flatMap { member -> diff --git a/src/org/thoughtcrime/securesms/loki/protocol/PushSessionRequestMessageSendJob.kt b/src/org/thoughtcrime/securesms/loki/protocol/PushSessionRequestMessageSendJob.kt index f530468235..01b2668f71 100644 --- a/src/org/thoughtcrime/securesms/loki/protocol/PushSessionRequestMessageSendJob.kt +++ b/src/org/thoughtcrime/securesms/loki/protocol/PushSessionRequestMessageSendJob.kt @@ -26,13 +26,13 @@ class PushSessionRequestMessageSendJob private constructor(parameters: Parameter } constructor(publicKey: String, timestamp: Long) : this(Parameters.Builder() - .addConstraint(NetworkConstraint.KEY) - .setQueue(KEY) - .setLifespan(TimeUnit.DAYS.toMillis(1)) - .setMaxAttempts(1) - .build(), - publicKey, - timestamp) + .addConstraint(NetworkConstraint.KEY) + .setQueue(KEY) + .setLifespan(TimeUnit.DAYS.toMillis(1)) + .setMaxAttempts(1) + .build(), + publicKey, + timestamp) override fun serialize(): Data { return Data.Builder().putString("publicKey", publicKey).putLong("timestamp", timestamp).build()