Partially implement SSK group creation

This commit is contained in:
nielsandriesse 2020-08-07 10:17:35 +10:00
parent a5a53adc47
commit 944f85ddb9
3 changed files with 77 additions and 44 deletions

View File

@ -37,6 +37,7 @@ import org.thoughtcrime.securesms.loki.database.LokiPreKeyBundleDatabase;
import org.thoughtcrime.securesms.loki.database.LokiPreKeyRecordDatabase; import org.thoughtcrime.securesms.loki.database.LokiPreKeyRecordDatabase;
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase; import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase;
import org.thoughtcrime.securesms.loki.database.LokiUserDatabase; import org.thoughtcrime.securesms.loki.database.LokiUserDatabase;
import org.thoughtcrime.securesms.loki.database.SharedSenderKeysDatabase;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
public class DatabaseFactory { public class DatabaseFactory {
@ -73,6 +74,7 @@ public class DatabaseFactory {
private final LokiMessageDatabase lokiMessageDatabase; private final LokiMessageDatabase lokiMessageDatabase;
private final LokiThreadDatabase lokiThreadDatabase; private final LokiThreadDatabase lokiThreadDatabase;
private final LokiUserDatabase lokiUserDatabase; private final LokiUserDatabase lokiUserDatabase;
private final SharedSenderKeysDatabase sskDatabase;
public static DatabaseFactory getInstance(Context context) { public static DatabaseFactory getInstance(Context context) {
synchronized (lock) { synchronized (lock) {
@ -187,6 +189,10 @@ public class DatabaseFactory {
public static LokiUserDatabase getLokiUserDatabase(Context context) { public static LokiUserDatabase getLokiUserDatabase(Context context) {
return getInstance(context).lokiUserDatabase; return getInstance(context).lokiUserDatabase;
} }
public static SharedSenderKeysDatabase getSSKDatabase(Context context) {
return getInstance(context).sskDatabase;
}
// endregion // endregion
public static void upgradeRestored(Context context, SQLiteDatabase database){ public static void upgradeRestored(Context context, SQLiteDatabase database){
@ -200,32 +206,33 @@ public class DatabaseFactory {
DatabaseSecret databaseSecret = new DatabaseSecretProvider(context).getOrCreateDatabaseSecret(); DatabaseSecret databaseSecret = new DatabaseSecretProvider(context).getOrCreateDatabaseSecret();
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret(); AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret();
this.databaseHelper = new SQLCipherOpenHelper(context, databaseSecret); this.databaseHelper = new SQLCipherOpenHelper(context, databaseSecret);
this.sms = new SmsDatabase(context, databaseHelper); this.sms = new SmsDatabase(context, databaseHelper);
this.mms = new MmsDatabase(context, databaseHelper); this.mms = new MmsDatabase(context, databaseHelper);
this.attachments = new AttachmentDatabase(context, databaseHelper, attachmentSecret); this.attachments = new AttachmentDatabase(context, databaseHelper, attachmentSecret);
this.media = new MediaDatabase(context, databaseHelper); this.media = new MediaDatabase(context, databaseHelper);
this.thread = new ThreadDatabase(context, databaseHelper); this.thread = new ThreadDatabase(context, databaseHelper);
this.mmsSmsDatabase = new MmsSmsDatabase(context, databaseHelper); this.mmsSmsDatabase = new MmsSmsDatabase(context, databaseHelper);
this.identityDatabase = new IdentityDatabase(context, databaseHelper); this.identityDatabase = new IdentityDatabase(context, databaseHelper);
this.draftDatabase = new DraftDatabase(context, databaseHelper); this.draftDatabase = new DraftDatabase(context, databaseHelper);
this.pushDatabase = new PushDatabase(context, databaseHelper); this.pushDatabase = new PushDatabase(context, databaseHelper);
this.groupDatabase = new GroupDatabase(context, databaseHelper); this.groupDatabase = new GroupDatabase(context, databaseHelper);
this.recipientDatabase = new RecipientDatabase(context, databaseHelper); this.recipientDatabase = new RecipientDatabase(context, databaseHelper);
this.groupReceiptDatabase = new GroupReceiptDatabase(context, databaseHelper); this.groupReceiptDatabase = new GroupReceiptDatabase(context, databaseHelper);
this.contactsDatabase = new ContactsDatabase(context); this.contactsDatabase = new ContactsDatabase(context);
this.preKeyDatabase = new OneTimePreKeyDatabase(context, databaseHelper); this.preKeyDatabase = new OneTimePreKeyDatabase(context, databaseHelper);
this.signedPreKeyDatabase = new SignedPreKeyDatabase(context, databaseHelper); this.signedPreKeyDatabase = new SignedPreKeyDatabase(context, databaseHelper);
this.sessionDatabase = new SessionDatabase(context, databaseHelper); this.sessionDatabase = new SessionDatabase(context, databaseHelper);
this.searchDatabase = new SearchDatabase(context, databaseHelper); this.searchDatabase = new SearchDatabase(context, databaseHelper);
this.jobDatabase = new JobDatabase(context, databaseHelper); this.jobDatabase = new JobDatabase(context, databaseHelper);
this.stickerDatabase = new StickerDatabase(context, databaseHelper, attachmentSecret); this.stickerDatabase = new StickerDatabase(context, databaseHelper, attachmentSecret);
this.lokiAPIDatabase = new LokiAPIDatabase(context, databaseHelper); this.lokiAPIDatabase = new LokiAPIDatabase(context, databaseHelper);
this.lokiContactPreKeyDatabase = new LokiPreKeyRecordDatabase(context, databaseHelper); this.lokiContactPreKeyDatabase = new LokiPreKeyRecordDatabase(context, databaseHelper);
this.lokiPreKeyBundleDatabase = new LokiPreKeyBundleDatabase(context, databaseHelper); this.lokiPreKeyBundleDatabase = new LokiPreKeyBundleDatabase(context, databaseHelper);
this.lokiMessageDatabase = new LokiMessageDatabase(context, databaseHelper); this.lokiMessageDatabase = new LokiMessageDatabase(context, databaseHelper);
this.lokiThreadDatabase = new LokiThreadDatabase(context, databaseHelper); this.lokiThreadDatabase = new LokiThreadDatabase(context, databaseHelper);
this.lokiUserDatabase = new LokiUserDatabase(context, databaseHelper); this.lokiUserDatabase = new LokiUserDatabase(context, databaseHelper);
this.sskDatabase = new SharedSenderKeysDatabase(context, databaseHelper);
} }
public void onApplicationLevelUpgrade(@NonNull Context context, @NonNull MasterSecret masterSecret, public void onApplicationLevelUpgrade(@NonNull Context context, @NonNull MasterSecret masterSecret,

View File

@ -1,30 +1,56 @@
package org.thoughtcrime.securesms.loki.protocol package org.thoughtcrime.securesms.loki.protocol
import android.content.Context import android.content.Context
import android.util.Log
import nl.komponents.kovenant.Promise import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.functional.map
import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore
import org.thoughtcrime.securesms.database.Address import org.thoughtcrime.securesms.database.Address
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.loki.utilities.recipient import org.thoughtcrime.securesms.loki.utilities.recipient
import org.thoughtcrime.securesms.loki.utilities.timeout
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.sms.MessageSender import org.thoughtcrime.securesms.sms.MessageSender
import org.thoughtcrime.securesms.sms.OutgoingTextMessage
import org.thoughtcrime.securesms.util.GroupUtil import org.thoughtcrime.securesms.util.GroupUtil
import org.thoughtcrime.securesms.util.Hex
import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.libsignal.SignalProtocolAddress import org.whispersystems.libsignal.ecc.Curve
import org.whispersystems.signalservice.api.messages.SignalServiceContent import org.whispersystems.signalservice.loki.protocol.closedgroups.ClosedGroupSenderKey
import org.whispersystems.signalservice.api.messages.SignalServiceGroup import org.whispersystems.signalservice.loki.protocol.closedgroups.SharedSenderKeysImplementation
import org.whispersystems.signalservice.api.push.SignalServiceAddress import org.whispersystems.signalservice.loki.utilities.hexEncodedPrivateKey
import org.whispersystems.signalservice.loki.api.SnodeAPI import org.whispersystems.signalservice.loki.utilities.hexEncodedPublicKey
import org.whispersystems.signalservice.loki.api.fileserver.FileServerAPI
import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.MultiDeviceProtocol
import java.util.* import java.util.*
object ClosedGroupsProtocol { object ClosedGroupsProtocol {
public fun createClosedGroup(context: Context, name: String, members: Collection<String>): Promise<Unit, Exception> {
// 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<ClosedGroupSenderKey> = 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<Address>(members.map { Address.fromSerialized(it) }),
null, null, LinkedList<Address>(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 @JvmStatic
fun shouldIgnoreContentMessage(context: Context, address: Address, groupID: String?, senderPublicKey: String): Boolean { fun shouldIgnoreContentMessage(context: Context, address: Address, groupID: String?, senderPublicKey: String): Boolean {
if (!address.isClosedGroup || groupID == null) { return false } if (!address.isClosedGroup || groupID == null) { return false }
@ -84,7 +110,7 @@ object ClosedGroupsProtocol {
} }
@JvmStatic @JvmStatic
fun establishSessionsWithMembersIfNeeded(context: Context, members: List<String>) { fun establishSessionsWithMembersIfNeeded(context: Context, members: Collection<String>) {
@Suppress("NAME_SHADOWING") val members = members.toMutableSet() @Suppress("NAME_SHADOWING") val members = members.toMutableSet()
/* /*
val allDevices = members.flatMap { member -> val allDevices = members.flatMap { member ->

View File

@ -26,13 +26,13 @@ class PushSessionRequestMessageSendJob private constructor(parameters: Parameter
} }
constructor(publicKey: String, timestamp: Long) : this(Parameters.Builder() constructor(publicKey: String, timestamp: Long) : this(Parameters.Builder()
.addConstraint(NetworkConstraint.KEY) .addConstraint(NetworkConstraint.KEY)
.setQueue(KEY) .setQueue(KEY)
.setLifespan(TimeUnit.DAYS.toMillis(1)) .setLifespan(TimeUnit.DAYS.toMillis(1))
.setMaxAttempts(1) .setMaxAttempts(1)
.build(), .build(),
publicKey, publicKey,
timestamp) timestamp)
override fun serialize(): Data { override fun serialize(): Data {
return Data.Builder().putString("publicKey", publicKey).putLong("timestamp", timestamp).build() return Data.Builder().putString("publicKey", publicKey).putLong("timestamp", timestamp).build()