diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index eb655878af..e8e7aaea7f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -75,7 +75,6 @@ import org.thoughtcrime.securesms.loki.api.PublicChatManager; import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase; 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.loki.protocol.MultiDeviceProtocol; import org.thoughtcrime.securesms.loki.protocol.SessionResetImplementation; import org.thoughtcrime.securesms.loki.utilities.Broadcaster; @@ -182,12 +181,10 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this); LokiThreadDatabase threadDB = DatabaseFactory.getLokiThreadDatabase(this); LokiUserDatabase userDB = DatabaseFactory.getLokiUserDatabase(this); - SharedSenderKeysDatabase sskDatabase = DatabaseFactory.getSSKDatabase(this); String userPublicKey = TextSecurePreferences.getLocalNumber(this); SessionResetImplementation sessionResetImpl = new SessionResetImplementation(this); MessagingConfiguration.Companion.configure(this, DatabaseFactory.getStorage(this), - sskDatabase, DatabaseFactory.getAttachmentProvider(this), new SessionProtocolImpl(this)); if (userPublicKey != null) { @@ -507,8 +504,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc } return Unit.INSTANCE; }); - SharedSenderKeysDatabase sskDatabase = DatabaseFactory.getSSKDatabase(this); - ClosedGroupPoller.Companion.configureIfNeeded(this, sskDatabase); + ClosedGroupPoller.Companion.configureIfNeeded(this); closedGroupPoller = ClosedGroupPoller.Companion.getShared(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java index d266f94977..8df2752fbf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -1098,16 +1098,16 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity builder.setPositiveButton(R.string.yes, (dialog, which) -> { Recipient groupRecipient = getRecipient(); String groupPublicKey; - boolean isSSKBasedClosedGroup; + boolean isClosedGroup; try { groupPublicKey = HexEncodingKt.toHexString(GroupUtil.doubleDecodeGroupID(groupRecipient.getAddress().toString())); - isSSKBasedClosedGroup = DatabaseFactory.getSSKDatabase(this).isSSKBasedClosedGroup(groupPublicKey); + isClosedGroup = DatabaseFactory.getLokiAPIDatabase(this).isClosedGroup(groupPublicKey); } catch (IOException e) { groupPublicKey = null; - isSSKBasedClosedGroup = false; + isClosedGroup = false; } try { - if (isSSKBasedClosedGroup) { + if (isClosedGroup) { ClosedGroupsProtocolV2.explicitLeave(this, groupPublicKey); initializeEnabledCheck(); } else { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java index 6a69b71079..8a50e06f0b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java @@ -34,7 +34,6 @@ import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase; import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase; import org.thoughtcrime.securesms.loki.database.LokiUserDatabase; import org.thoughtcrime.securesms.loki.database.SessionJobDatabase; -import org.thoughtcrime.securesms.loki.database.SharedSenderKeysDatabase; public class DatabaseFactory { @@ -67,7 +66,6 @@ public class DatabaseFactory { private final LokiThreadDatabase lokiThreadDatabase; private final LokiUserDatabase lokiUserDatabase; private final LokiBackupFilesDatabase lokiBackupFilesDatabase; - private final SharedSenderKeysDatabase sskDatabase; private final SessionJobDatabase sessionJobDatabase; // Refactor @@ -176,10 +174,6 @@ public class DatabaseFactory { return getInstance(context).lokiBackupFilesDatabase; } - public static SharedSenderKeysDatabase getSSKDatabase(Context context) { - return getInstance(context).sskDatabase; - } - public static SessionJobDatabase getSessionJobDatabase(Context context) { return getInstance(context).sessionJobDatabase; } @@ -229,7 +223,6 @@ public class DatabaseFactory { this.lokiThreadDatabase = new LokiThreadDatabase(context, databaseHelper); this.lokiUserDatabase = new LokiUserDatabase(context, databaseHelper); this.lokiBackupFilesDatabase = new LokiBackupFilesDatabase(context, databaseHelper); - this.sskDatabase = new SharedSenderKeysDatabase(context, databaseHelper); this.storage = new Storage(context, databaseHelper); this.attachmentProvider = new DatabaseAttachmentProvider(context, databaseHelper); this.sessionJobDatabase = new SessionJobDatabase(context, databaseHelper); 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 7d39d5032f..3a5fe5147f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -417,9 +417,9 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, } override fun isClosedGroup(publicKey: String): Boolean { - val isSSKBasedClosedGroup = DatabaseFactory.getSSKDatabase(context).isSSKBasedClosedGroup(publicKey) + val isClosedGroup = DatabaseFactory.getLokiAPIDatabase(context).isClosedGroup(publicKey) val address = Address.fromSerialized(publicKey) - return address.isClosedGroup || isSSKBasedClosedGroup + return address.isClosedGroup || isClosedGroup } override fun getClosedGroupEncryptionKeyPairs(groupPublicKey: String): MutableList { @@ -431,7 +431,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, } override fun getAllClosedGroupPublicKeys(): Set { - return DatabaseFactory.getSSKDatabase(context).getAllClosedGroupPublicKeys() + return DatabaseFactory.getLokiAPIDatabase(context).getAllClosedGroupPublicKeys() } override fun addClosedGroupPublicKey(groupPublicKey: String) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index 91d3263ac8..a69f260991 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -32,7 +32,6 @@ import org.thoughtcrime.securesms.loki.database.LokiBackupFilesDatabase; import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase; 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.loki.protocol.ClosedGroupsMigration; public class SQLCipherOpenHelper extends SQLiteOpenHelper { @@ -131,9 +130,6 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { db.execSQL(LokiUserDatabase.getCreateDisplayNameTableCommand()); db.execSQL(LokiUserDatabase.getCreateServerDisplayNameTableCommand()); db.execSQL(LokiBackupFilesDatabase.getCreateTableCommand()); - db.execSQL(SharedSenderKeysDatabase.getCreateOldClosedGroupRatchetTableCommand()); - db.execSQL(SharedSenderKeysDatabase.getCreateCurrentClosedGroupRatchetTableCommand()); - db.execSQL(SharedSenderKeysDatabase.getCreateClosedGroupPrivateKeyTableCommand()); executeStatements(db, SmsDatabase.CREATE_INDEXS); executeStatements(db, MmsDatabase.CREATE_INDEXS); @@ -186,8 +182,8 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { if (oldVersion < lokiV12) { db.execSQL(LokiAPIDatabase.getCreateLastMessageHashValueTable2Command()); - db.execSQL(SharedSenderKeysDatabase.getCreateCurrentClosedGroupRatchetTableCommand()); - db.execSQL(SharedSenderKeysDatabase.getCreateClosedGroupPrivateKeyTableCommand()); + db.execSQL(ClosedGroupsMigration.getCreateCurrentClosedGroupRatchetTableCommand()); + db.execSQL(ClosedGroupsMigration.getCreateClosedGroupPrivateKeyTableCommand()); } if (oldVersion < lokiV13) { @@ -199,7 +195,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { } if (oldVersion < lokiV15) { - db.execSQL(SharedSenderKeysDatabase.getCreateOldClosedGroupRatchetTableCommand()); + db.execSQL(ClosedGroupsMigration.getCreateOldClosedGroupRatchetTableCommand()); } if (oldVersion < lokiV16) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java b/app/src/main/java/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java index 2f1ebed5a2..dac248c830 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java @@ -98,7 +98,6 @@ public class SignalCommunicationModule { Optional.of(new MessageSenderEventListener(context)), TextSecurePreferences.getLocalNumber(context), DatabaseFactory.getLokiAPIDatabase(context), - DatabaseFactory.getSSKDatabase(context), DatabaseFactory.getLokiThreadDatabase(context), DatabaseFactory.getLokiMessageDatabase(context), null, // DatabaseFactory.getLokiPreKeyBundleDatabase(context) 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 f83803f68a..7cbfedbeae 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -248,7 +248,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { SessionResetProtocol sessionResetProtocol = new SessionResetImplementation(context); SignalServiceAddress localAddress = new SignalServiceAddress(TextSecurePreferences.getLocalNumber(context)); LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(context); - LokiServiceCipher cipher = new LokiServiceCipher(localAddress, axolotlStore, DatabaseFactory.getSSKDatabase(context), new SessionProtocolImpl(context), sessionResetProtocol, apiDB, UnidentifiedAccessUtil.getCertificateValidator()); + LokiServiceCipher cipher = new LokiServiceCipher(localAddress, axolotlStore, new SessionProtocolImpl(context), sessionResetProtocol, apiDB, UnidentifiedAccessUtil.getCertificateValidator()); SignalServiceContent content = cipher.decrypt(envelope); diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt index 489aa354fe..83fea40046 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt @@ -246,21 +246,21 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() { val admins = members.toSet() //TODO For now, consider all the users to be admins. - var isSSKBasedClosedGroup: Boolean + var isClosedGroup: Boolean var groupPublicKey: String? try { groupPublicKey = GroupUtil.doubleDecodeGroupID(groupID).toHexString() - isSSKBasedClosedGroup = DatabaseFactory.getSSKDatabase(this).isSSKBasedClosedGroup(groupPublicKey) + isClosedGroup = DatabaseFactory.getLokiAPIDatabase(this).isClosedGroup(groupPublicKey) } catch (e: IOException) { groupPublicKey = null - isSSKBasedClosedGroup = false + isClosedGroup = false } if (members.isEmpty()) { return Toast.makeText(this, R.string.activity_edit_closed_group_not_enough_group_members_error, Toast.LENGTH_LONG).show() } - val maxGroupMembers = if (isSSKBasedClosedGroup) ClosedGroupsProtocolV2.groupSizeLimit else legacyGroupSizeLimit + val maxGroupMembers = if (isClosedGroup) ClosedGroupsProtocolV2.groupSizeLimit else legacyGroupSizeLimit if (members.size >= maxGroupMembers) { return Toast.makeText(this, R.string.activity_create_closed_group_too_many_group_members_error, Toast.LENGTH_LONG).show() } @@ -273,7 +273,7 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() { return Toast.makeText(this@EditClosedGroupActivity, message, Toast.LENGTH_LONG).show() } - if (isSSKBasedClosedGroup) { + if (isClosedGroup) { isLoading = true loaderContainer.fadeIn() val promise: Promise = if (!members.contains(Recipient.from(this, Address.fromSerialized(userPublicKey), false))) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt index 9beabed9f6..739b04178d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt @@ -167,7 +167,6 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe val apiDB = DatabaseFactory.getLokiAPIDatabase(this) val threadDB = DatabaseFactory.getLokiThreadDatabase(this) val userDB = DatabaseFactory.getLokiUserDatabase(this) - val sskDatabase = DatabaseFactory.getSSKDatabase(this) val userPublicKey = TextSecurePreferences.getLocalNumber(this) val sessionResetImpl = SessionResetImplementation(this) if (userPublicKey != null) { @@ -315,7 +314,6 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe val threadID = thread.threadId val recipient = thread.recipient val threadDB = DatabaseFactory.getThreadDatabase(this) - val isClosedGroup = recipient.address.isClosedGroup val dialogMessage: String if (recipient.isGroupRecipient) { val group = DatabaseFactory.getGroupDatabase(this).getGroup(recipient.address.toString()).orNull() @@ -333,17 +331,17 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe val context = this@HomeActivity as Context // Send a leave group message if this is an active closed group - if (isClosedGroup && DatabaseFactory.getGroupDatabase(context).isActive(recipient.address.toGroupString())) { - var isSSKBasedClosedGroup: Boolean + if (recipient.address.isClosedGroup && DatabaseFactory.getGroupDatabase(context).isActive(recipient.address.toGroupString())) { + var isClosedGroup: Boolean var groupPublicKey: String? try { groupPublicKey = GroupUtil.doubleDecodeGroupID(recipient.address.toString()).toHexString() - isSSKBasedClosedGroup = DatabaseFactory.getSSKDatabase(context).isSSKBasedClosedGroup(groupPublicKey) + isClosedGroup = DatabaseFactory.getLokiAPIDatabase(context).isClosedGroup(groupPublicKey) } catch (e: IOException) { groupPublicKey = null - isSSKBasedClosedGroup = false + isClosedGroup = false } - if (isSSKBasedClosedGroup) { + if (isClosedGroup) { ClosedGroupsProtocolV2.explicitLeave(context, groupPublicKey!!) } else { Toast.makeText(context, R.string.activity_home_leaving_group_failed_message, Toast.LENGTH_LONG).show() diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/api/BackgroundPollWorker.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/api/BackgroundPollWorker.kt index 7fc1f5bee9..422afcee58 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/api/BackgroundPollWorker.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/api/BackgroundPollWorker.kt @@ -76,8 +76,7 @@ class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Wor promises.add(privateChatsPromise) // Closed groups - val sskDatabase = DatabaseFactory.getSSKDatabase(context) - ClosedGroupPoller.configureIfNeeded(context, sskDatabase) + ClosedGroupPoller.configureIfNeeded(context) promises.addAll(ClosedGroupPoller.shared.pollOnce()) // Open Groups diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/api/ClosedGroupPoller.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/api/ClosedGroupPoller.kt index 2076bee417..75c973c27d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/api/ClosedGroupPoller.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/api/ClosedGroupPoller.kt @@ -7,14 +7,14 @@ import nl.komponents.kovenant.functional.bind import nl.komponents.kovenant.functional.map import org.thoughtcrime.securesms.jobs.PushContentReceiveJob import org.session.libsignal.utilities.logging.Log -import org.thoughtcrime.securesms.loki.database.SharedSenderKeysDatabase import org.session.libsignal.utilities.successBackground import org.session.libsignal.service.api.messages.SignalServiceEnvelope import org.session.libsignal.service.loki.api.SnodeAPI import org.session.libsignal.service.loki.api.SwarmAPI import org.session.libsignal.service.loki.utilities.getRandomElementOrNull +import org.thoughtcrime.securesms.database.DatabaseFactory -class ClosedGroupPoller private constructor(private val context: Context, private val database: SharedSenderKeysDatabase) { +class ClosedGroupPoller private constructor(private val context: Context) { private var isPolling = false private val handler: Handler by lazy { Handler() } @@ -32,9 +32,9 @@ class ClosedGroupPoller private constructor(private val context: Context, privat public lateinit var shared: ClosedGroupPoller - public fun configureIfNeeded(context: Context, sskDatabase: SharedSenderKeysDatabase) { + public fun configureIfNeeded(context: Context) { if (::shared.isInitialized) { return; } - shared = ClosedGroupPoller(context, sskDatabase) + shared = ClosedGroupPoller(context) } } // endregion @@ -66,7 +66,7 @@ class ClosedGroupPoller private constructor(private val context: Context, privat // region Private API private fun poll(): List> { if (!isPolling) { return listOf() } - val publicKeys = database.getAllClosedGroupPublicKeys() + val publicKeys = DatabaseFactory.getLokiAPIDatabase(context).getAllClosedGroupPublicKeys() return publicKeys.map { publicKey -> val promise = SwarmAPI.shared.getSwarm(publicKey).bind { swarm -> val snode = swarm.getRandomElementOrNull() ?: throw InsufficientSnodesException() // Should be cryptographically secure diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/api/LokiPushNotificationManager.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/api/LokiPushNotificationManager.kt index 9822f0525a..db2789985f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/api/LokiPushNotificationManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/api/LokiPushNotificationManager.kt @@ -53,7 +53,7 @@ object LokiPushNotificationManager { } } // Unsubscribe from all closed groups - val allClosedGroupPublicKeys = DatabaseFactory.getSSKDatabase(context).getAllClosedGroupPublicKeys() + val allClosedGroupPublicKeys = DatabaseFactory.getLokiAPIDatabase(context).getAllClosedGroupPublicKeys() val userPublicKey = TextSecurePreferences.getLocalNumber(context)!! allClosedGroupPublicKeys.forEach { closedGroup -> performOperation(context, ClosedGroupOperation.Unsubscribe, closedGroup, userPublicKey) @@ -84,7 +84,7 @@ object LokiPushNotificationManager { } } // Subscribe to all closed groups - val allClosedGroupPublicKeys = DatabaseFactory.getSSKDatabase(context).getAllClosedGroupPublicKeys() + val allClosedGroupPublicKeys = DatabaseFactory.getLokiAPIDatabase(context).getAllClosedGroupPublicKeys() allClosedGroupPublicKeys.forEach { closedGroup -> performOperation(context, ClosedGroupOperation.Subscribe, closedGroup, publicKey) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt index d426d200f0..077715f7e6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt @@ -16,6 +16,7 @@ import org.session.libsignal.service.loki.utilities.toHexString import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.session.libsignal.utilities.Hex import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsignal.service.loki.utilities.PublicKeyValidation import java.util.* class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiAPIDatabaseProtocol { @@ -454,6 +455,11 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( }.toSet() } + override fun isClosedGroup(groupPublicKey: String): Boolean { + if (!PublicKeyValidation.isValid(groupPublicKey)) { return false } + return getAllClosedGroupPublicKeys().contains(groupPublicKey) + } + fun removeClosedGroupPublicKey(groupPublicKey: String) { val database = databaseHelper.writableDatabase database.delete(closedGroupPublicKeysTable, "${Companion.groupPublicKey} = ?", wrap(groupPublicKey)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/database/SharedSenderKeysDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/database/SharedSenderKeysDatabase.kt deleted file mode 100644 index c8907dbdbe..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/database/SharedSenderKeysDatabase.kt +++ /dev/null @@ -1,143 +0,0 @@ -package org.thoughtcrime.securesms.loki.database - -import android.content.ContentValues -import android.content.Context -import org.thoughtcrime.securesms.database.Database -import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper -import org.thoughtcrime.securesms.loki.utilities.* -import org.session.libsignal.utilities.Hex -import org.session.libsignal.service.loki.protocol.closedgroups.ClosedGroupRatchet -import org.session.libsignal.service.loki.protocol.closedgroups.ClosedGroupRatchetCollectionType -import org.session.libsignal.service.loki.protocol.closedgroups.ClosedGroupSenderKey -import org.session.libsignal.service.loki.protocol.closedgroups.SharedSenderKeysDatabaseProtocol -import org.session.libsignal.service.loki.utilities.PublicKeyValidation -import org.thoughtcrime.securesms.database.DatabaseFactory - -class SharedSenderKeysDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), SharedSenderKeysDatabaseProtocol { - - companion object { - // Shared - public val closedGroupPublicKey = "closed_group_public_key" - // Ratchets - private val oldClosedGroupRatchetTable = "old_closed_group_ratchet_table" - private val currentClosedGroupRatchetTable = "closed_group_ratchet_table" - private val senderPublicKey = "sender_public_key" - private val chainKey = "chain_key" - private val keyIndex = "key_index" - private val messageKeys = "message_keys" - @JvmStatic val createOldClosedGroupRatchetTableCommand - = "CREATE TABLE $oldClosedGroupRatchetTable ($closedGroupPublicKey STRING, $senderPublicKey STRING, $chainKey STRING, " + - "$keyIndex INTEGER DEFAULT 0, $messageKeys TEXT, PRIMARY KEY ($closedGroupPublicKey, $senderPublicKey));" - // Private keys - @JvmStatic val createCurrentClosedGroupRatchetTableCommand - = "CREATE TABLE $currentClosedGroupRatchetTable ($closedGroupPublicKey STRING, $senderPublicKey STRING, $chainKey STRING, " + - "$keyIndex INTEGER DEFAULT 0, $messageKeys TEXT, PRIMARY KEY ($closedGroupPublicKey, $senderPublicKey));" - // Private keys - public val closedGroupPrivateKeyTable = "closed_group_private_key_table" - public val closedGroupPrivateKey = "closed_group_private_key" - @JvmStatic val createClosedGroupPrivateKeyTableCommand - = "CREATE TABLE $closedGroupPrivateKeyTable ($closedGroupPublicKey STRING PRIMARY KEY, $closedGroupPrivateKey STRING);" - } - - private fun getTable(collection: ClosedGroupRatchetCollectionType): String { - return when (collection) { - ClosedGroupRatchetCollectionType.Old -> oldClosedGroupRatchetTable - ClosedGroupRatchetCollectionType.Current -> currentClosedGroupRatchetTable - } - } - - // region Ratchets & Sender Keys - override fun getClosedGroupRatchet(groupPublicKey: String, senderPublicKey: String, collection: ClosedGroupRatchetCollectionType): ClosedGroupRatchet? { - val database = databaseHelper.readableDatabase - val query = "${Companion.closedGroupPublicKey} = ? AND ${Companion.senderPublicKey} = ?" - return database.get(getTable(collection), query, arrayOf( groupPublicKey, senderPublicKey )) { cursor -> - val chainKey = cursor.getString(Companion.chainKey) - val keyIndex = cursor.getInt(Companion.keyIndex) - val messageKeys = cursor.getString(Companion.messageKeys).split("-") - ClosedGroupRatchet(chainKey, keyIndex, messageKeys) - } - } - - override fun setClosedGroupRatchet(groupPublicKey: String, senderPublicKey: String, ratchet: ClosedGroupRatchet, collection: ClosedGroupRatchetCollectionType) { - val database = databaseHelper.writableDatabase - val values = ContentValues() - values.put(Companion.closedGroupPublicKey, groupPublicKey) - values.put(Companion.senderPublicKey, senderPublicKey) - values.put(Companion.chainKey, ratchet.chainKey) - values.put(Companion.keyIndex, ratchet.keyIndex) - values.put(Companion.messageKeys, ratchet.messageKeys.joinToString("-")) - val query = "${Companion.closedGroupPublicKey} = ? AND ${Companion.senderPublicKey} = ?" - database.insertOrUpdate(getTable(collection), values, query, arrayOf( groupPublicKey, senderPublicKey )) - } - - override fun removeAllClosedGroupRatchets(groupPublicKey: String, collection: ClosedGroupRatchetCollectionType) { - val database = databaseHelper.writableDatabase - val query = "${Companion.closedGroupPublicKey} = ?" - database.delete(getTable(collection), query, arrayOf( groupPublicKey )) - } - - override fun getAllClosedGroupRatchets(groupPublicKey: String, collection: ClosedGroupRatchetCollectionType): Set> { - val database = databaseHelper.readableDatabase - val query = "${Companion.closedGroupPublicKey} = ?" - return database.getAll(getTable(collection), query, arrayOf( groupPublicKey )) { cursor -> - val chainKey = cursor.getString(Companion.chainKey) - val keyIndex = cursor.getInt(Companion.keyIndex) - val messageKeys = cursor.getString(Companion.messageKeys).split("-") - val senderPublicKey = cursor.getString(Companion.senderPublicKey) - val ratchet = ClosedGroupRatchet(chainKey, keyIndex, messageKeys) - Pair(senderPublicKey, ratchet) - }.toSet() - } - - override fun getAllClosedGroupSenderKeys(groupPublicKey: String, collection: ClosedGroupRatchetCollectionType): Set { - return getAllClosedGroupRatchets(groupPublicKey, collection).map { pair -> - val senderPublicKey = pair.first - val ratchet = pair.second - ClosedGroupSenderKey(Hex.fromStringCondensed(ratchet.chainKey), ratchet.keyIndex, Hex.fromStringCondensed(senderPublicKey)) - }.toSet() - } - // endregion - - // region Public & Private Keys - override fun getClosedGroupPrivateKey(groupPublicKey: String): String? { - val database = databaseHelper.readableDatabase - val query = "${Companion.closedGroupPublicKey} = ?" - return database.get(closedGroupPrivateKeyTable, query, arrayOf( groupPublicKey )) { cursor -> - cursor.getString(Companion.closedGroupPrivateKey) - } - } - - override fun setClosedGroupPrivateKey(groupPublicKey: String, groupPrivateKey: String) { - val database = databaseHelper.writableDatabase - val values = ContentValues() - values.put(Companion.closedGroupPublicKey, groupPublicKey) - values.put(Companion.closedGroupPrivateKey, groupPrivateKey) - val query = "${Companion.closedGroupPublicKey} = ?" - database.insertOrUpdate(closedGroupPrivateKeyTable, values, query, arrayOf( groupPublicKey )) - } - - override fun removeClosedGroupPrivateKey(groupPublicKey: String) { - val database = databaseHelper.writableDatabase - val query = "${Companion.closedGroupPublicKey} = ?" - database.delete(closedGroupPrivateKeyTable, query, arrayOf( groupPublicKey )) - } - - override fun getAllClosedGroupPublicKeys(): Set { - val database = databaseHelper.readableDatabase - val result = mutableSetOf() - result.addAll(database.getAll(closedGroupPrivateKeyTable, null, null) { cursor -> - cursor.getString(Companion.closedGroupPublicKey) - }.filter { - PublicKeyValidation.isValid(it) - }) - result.addAll(DatabaseFactory.getLokiAPIDatabase(context).getAllClosedGroupPublicKeys()) - return result - } - // endregion - - override fun isSSKBasedClosedGroup(groupPublicKey: String): Boolean { - if (!PublicKeyValidation.isValid(groupPublicKey)) { return false } - return getAllClosedGroupPublicKeys().contains(groupPublicKey) - } - // endregion -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsMigration.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsMigration.kt index dd104d510a..2883c985f2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsMigration.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsMigration.kt @@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.loki.protocol import android.content.ContentValues import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase -import org.thoughtcrime.securesms.loki.database.SharedSenderKeysDatabase import org.thoughtcrime.securesms.loki.utilities.get import org.thoughtcrime.securesms.loki.utilities.getAll import org.thoughtcrime.securesms.loki.utilities.getString @@ -18,17 +17,39 @@ import java.util.* object ClosedGroupsMigration { - fun perform(database: net.sqlcipher.database.SQLiteDatabase) { - val publicKeys = database.getAll(SharedSenderKeysDatabase.closedGroupPrivateKeyTable, null, null) { cursor -> - cursor.getString(SharedSenderKeysDatabase.closedGroupPublicKey) + public val closedGroupPublicKey = "closed_group_public_key" + // Ratchets + private val oldClosedGroupRatchetTable = "old_closed_group_ratchet_table" + private val currentClosedGroupRatchetTable = "closed_group_ratchet_table" + private val senderPublicKey = "sender_public_key" + private val chainKey = "chain_key" + private val keyIndex = "key_index" + private val messageKeys = "message_keys" + @JvmStatic val createOldClosedGroupRatchetTableCommand + = "CREATE TABLE $oldClosedGroupRatchetTable ($closedGroupPublicKey STRING, $senderPublicKey STRING, $chainKey STRING, " + + "$keyIndex INTEGER DEFAULT 0, $messageKeys TEXT, PRIMARY KEY ($closedGroupPublicKey, $senderPublicKey));" + // Private keys + @JvmStatic val createCurrentClosedGroupRatchetTableCommand + = "CREATE TABLE $currentClosedGroupRatchetTable ($closedGroupPublicKey STRING, $senderPublicKey STRING, $chainKey STRING, " + + "$keyIndex INTEGER DEFAULT 0, $messageKeys TEXT, PRIMARY KEY ($closedGroupPublicKey, $senderPublicKey));" + // Private keys + public val closedGroupPrivateKeyTable = "closed_group_private_key_table" + public val closedGroupPrivateKey = "closed_group_private_key" + @JvmStatic val createClosedGroupPrivateKeyTableCommand + = "CREATE TABLE $closedGroupPrivateKeyTable ($closedGroupPublicKey STRING PRIMARY KEY, $closedGroupPrivateKey STRING);" + + +fun perform(database: net.sqlcipher.database.SQLiteDatabase) { + val publicKeys = database.getAll(closedGroupPrivateKeyTable, null, null) { cursor -> + cursor.getString(closedGroupPublicKey) }.filter { PublicKeyValidation.isValid(it) } val keyPairs = mutableListOf() for (publicKey in publicKeys) { - val query = "${SharedSenderKeysDatabase.closedGroupPublicKey} = ?" - val privateKey = database.get(SharedSenderKeysDatabase.closedGroupPrivateKeyTable, query, arrayOf( publicKey )) { cursor -> - cursor.getString(SharedSenderKeysDatabase.closedGroupPrivateKey) + val query = "${closedGroupPublicKey} = ?" + val privateKey = database.get(closedGroupPrivateKeyTable, query, arrayOf( publicKey )) { cursor -> + cursor.getString(closedGroupPrivateKey) } val keyPair = ECKeyPair(DjbECPublicKey(Hex.fromStringCondensed(publicKey.removing05PrefixIfNeeded())), DjbECPrivateKey(Hex.fromStringCondensed(privateKey))) keyPairs.add(keyPair) diff --git a/libsession/src/main/java/org/session/libsession/messaging/MessagingConfiguration.kt b/libsession/src/main/java/org/session/libsession/messaging/MessagingConfiguration.kt index f16843a163..ae1daec5a6 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/MessagingConfiguration.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/MessagingConfiguration.kt @@ -2,16 +2,11 @@ package org.session.libsession.messaging import android.content.Context import org.session.libsession.database.MessageDataProvider -import org.session.libsignal.libsignal.loki.SessionResetProtocol -import org.session.libsignal.libsignal.state.* -import org.session.libsignal.metadata.certificate.CertificateValidator import org.session.libsignal.service.loki.api.crypto.SessionProtocol -import org.session.libsignal.service.loki.protocol.closedgroups.SharedSenderKeysDatabaseProtocol class MessagingConfiguration( val context: Context, val storage: StorageProtocol, - val sskDatabase: SharedSenderKeysDatabaseProtocol, val messageDataProvider: MessageDataProvider, val sessionProtocol: SessionProtocol) { @@ -20,12 +15,11 @@ class MessagingConfiguration( fun configure(context: Context, storage: StorageProtocol, - sskDatabase: SharedSenderKeysDatabaseProtocol, messageDataProvider: MessageDataProvider, sessionProtocol: SessionProtocol ) { if (Companion::shared.isInitialized) { return } - shared = MessagingConfiguration(context, storage, sskDatabase, messageDataProvider, sessionProtocol) + shared = MessagingConfiguration(context, storage, messageDataProvider, sessionProtocol) } } } \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt index 601bec9975..c30ed32181 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt @@ -47,7 +47,7 @@ object PushNotificationAPI { } } // Unsubscribe from all closed groups - val allClosedGroupPublicKeys = MessagingConfiguration.shared.sskDatabase.getAllClosedGroupPublicKeys() + val allClosedGroupPublicKeys = MessagingConfiguration.shared.storage.getAllClosedGroupPublicKeys() val userPublicKey = MessagingConfiguration.shared.storage.getUserPublicKey()!! allClosedGroupPublicKeys.forEach { closedGroup -> performOperation(ClosedGroupOperation.Unsubscribe, closedGroup, userPublicKey) @@ -77,7 +77,7 @@ object PushNotificationAPI { } } // Subscribe to all closed groups - val allClosedGroupPublicKeys = MessagingConfiguration.shared.sskDatabase.getAllClosedGroupPublicKeys() + val allClosedGroupPublicKeys = MessagingConfiguration.shared.storage.getAllClosedGroupPublicKeys() allClosedGroupPublicKeys.forEach { closedGroup -> performOperation(ClosedGroupOperation.Subscribe, closedGroup, publicKey) } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/ClosedGroupPoller.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/ClosedGroupPoller.kt index 69af74ecd8..e2ac3c3aee 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/ClosedGroupPoller.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/ClosedGroupPoller.kt @@ -61,7 +61,7 @@ class ClosedGroupPoller { // region Private API private fun poll(): List> { if (!isPolling) { return listOf() } - val publicKeys = MessagingConfiguration.shared.sskDatabase.getAllClosedGroupPublicKeys() + val publicKeys = MessagingConfiguration.shared.storage.getAllClosedGroupPublicKeys() return publicKeys.map { publicKey -> val promise = SnodeAPI.getSwarm(publicKey).bind { swarm -> val snode = swarm.getRandomElementOrNull() ?: throw InsufficientSnodesException() // Should be cryptographically secure 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 4ab17f3469..caefdca330 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 @@ -75,7 +75,6 @@ import org.session.libsignal.service.loki.database.LokiOpenGroupDatabaseProtocol import org.session.libsignal.service.loki.database.LokiPreKeyBundleDatabaseProtocol; import org.session.libsignal.service.loki.database.LokiThreadDatabaseProtocol; import org.session.libsignal.service.loki.database.LokiUserDatabaseProtocol; -import org.session.libsignal.service.loki.protocol.closedgroups.SharedSenderKeysDatabaseProtocol; import org.session.libsignal.service.loki.protocol.meta.TTLUtilities; import org.session.libsignal.service.loki.protocol.sessionmanagement.SessionManagementProtocol; import org.session.libsignal.service.loki.utilities.Broadcaster; @@ -121,7 +120,6 @@ public class SignalServiceMessageSender { // Loki private final String userPublicKey; private final LokiAPIDatabaseProtocol apiDatabase; - private final SharedSenderKeysDatabaseProtocol sskDatabase; private final LokiThreadDatabaseProtocol threadDatabase; private final LokiMessageDatabaseProtocol messageDatabase; private final LokiPreKeyBundleDatabaseProtocol preKeyBundleDatabase; @@ -151,7 +149,6 @@ public class SignalServiceMessageSender { Optional eventListener, String userPublicKey, LokiAPIDatabaseProtocol apiDatabase, - SharedSenderKeysDatabaseProtocol sskDatabase, LokiThreadDatabaseProtocol threadDatabase, LokiMessageDatabaseProtocol messageDatabase, LokiPreKeyBundleDatabaseProtocol preKeyBundleDatabase, @@ -161,7 +158,7 @@ public class SignalServiceMessageSender { LokiOpenGroupDatabaseProtocol openGroupDatabase, Broadcaster broadcaster) { - this(urls, new StaticCredentialsProvider(user, password, null), store, userAgent, isMultiDevice, pipe, unidentifiedPipe, eventListener, userPublicKey, apiDatabase, sskDatabase, threadDatabase, messageDatabase, preKeyBundleDatabase, sessionProtocolImpl, sessionResetImpl, userDatabase, openGroupDatabase, broadcaster); + this(urls, new StaticCredentialsProvider(user, password, null), store, userAgent, isMultiDevice, pipe, unidentifiedPipe, eventListener, userPublicKey, apiDatabase, threadDatabase, messageDatabase, preKeyBundleDatabase, sessionProtocolImpl, sessionResetImpl, userDatabase, openGroupDatabase, broadcaster); } public SignalServiceMessageSender(SignalServiceConfiguration urls, @@ -174,7 +171,6 @@ public class SignalServiceMessageSender { Optional eventListener, String userPublicKey, LokiAPIDatabaseProtocol apiDatabase, - SharedSenderKeysDatabaseProtocol sskDatabase, LokiThreadDatabaseProtocol threadDatabase, LokiMessageDatabaseProtocol messageDatabase, LokiPreKeyBundleDatabaseProtocol preKeyBundleDatabase, @@ -193,7 +189,6 @@ public class SignalServiceMessageSender { this.eventListener = eventListener; this.userPublicKey = userPublicKey; this.apiDatabase = apiDatabase; - this.sskDatabase = sskDatabase; this.threadDatabase = threadDatabase; this.messageDatabase = messageDatabase; this.preKeyBundleDatabase = preKeyBundleDatabase; @@ -1181,9 +1176,9 @@ public class SignalServiceMessageSender { PushTransportDetails transportDetails = new PushTransportDetails(3); String publicKey = recipient.getNumber(); // Could be a contact's public key or the public key of a SSK group - boolean isSSKBasedClosedGroup = sskDatabase.isSSKBasedClosedGroup(publicKey); + boolean isClosedGroup = apiDatabase.isClosedGroup(publicKey); String encryptionPublicKey; - if (isSSKBasedClosedGroup) { + if (isClosedGroup) { ECKeyPair encryptionKeyPair = apiDatabase.getLatestClosedGroupEncryptionKeyPair(publicKey); encryptionPublicKey = HexEncodingKt.getHexEncodedPublicKey(encryptionKeyPair); } else { @@ -1191,7 +1186,7 @@ public class SignalServiceMessageSender { } byte[] ciphertext = sessionProtocolImpl.encrypt(transportDetails.getPaddedMessageBody(plaintext), encryptionPublicKey); String body = Base64.encodeBytes(ciphertext); - int type = isSSKBasedClosedGroup ? SignalServiceProtos.Envelope.Type.CLOSED_GROUP_CIPHERTEXT_VALUE : + int type = isClosedGroup ? SignalServiceProtos.Envelope.Type.CLOSED_GROUP_CIPHERTEXT_VALUE : SignalServiceProtos.Envelope.Type.UNIDENTIFIED_SENDER_VALUE; OutgoingPushMessage message = new OutgoingPushMessage(type, 1, 0, body); messages.add(message); 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 153353c2c0..196b22d25d 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 @@ -88,8 +88,6 @@ import org.session.libsignal.service.loki.api.crypto.SessionProtocol; import org.session.libsignal.service.loki.api.crypto.SessionProtocolUtilities; import org.session.libsignal.service.loki.api.opengroups.PublicChat; import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol; -import org.session.libsignal.service.loki.protocol.closedgroups.ClosedGroupUtilities; -import org.session.libsignal.service.loki.protocol.closedgroups.SharedSenderKeysDatabaseProtocol; import org.session.libsignal.service.loki.protocol.sessionmanagement.PreKeyBundleMessage; import java.io.ByteArrayInputStream; @@ -115,7 +113,6 @@ public class SignalServiceCipher { private final SignalProtocolStore signalProtocolStore; private final SessionResetProtocol sessionResetProtocol; - private final SharedSenderKeysDatabaseProtocol sskDatabase; private final SignalServiceAddress localAddress; private final SessionProtocol sessionProtocolImpl; private final LokiAPIDatabaseProtocol apiDB; @@ -123,7 +120,6 @@ public class SignalServiceCipher { public SignalServiceCipher(SignalServiceAddress localAddress, SignalProtocolStore signalProtocolStore, - SharedSenderKeysDatabaseProtocol sskDatabase, SessionResetProtocol sessionResetProtocol, SessionProtocol sessionProtocolImpl, LokiAPIDatabaseProtocol apiDB, @@ -131,56 +127,55 @@ public class SignalServiceCipher { { this.signalProtocolStore = signalProtocolStore; this.sessionResetProtocol = sessionResetProtocol; - this.sskDatabase = sskDatabase; this.localAddress = localAddress; this.sessionProtocolImpl = sessionProtocolImpl; this.apiDB = apiDB; this.certificateValidator = certificateValidator; } - public OutgoingPushMessage encrypt(SignalProtocolAddress destination, - Optional unidentifiedAccess, - byte[] unpaddedMessage) - throws UntrustedIdentityException, InvalidKeyException, IOException - { - if (unidentifiedAccess.isPresent() && sskDatabase.isSSKBasedClosedGroup(destination.getName())) { - String userPublicKey = localAddress.getNumber(); - SignalProtocolAddress signalProtocolAddress = new SignalProtocolAddress(userPublicKey, 1); - SealedSessionCipher sessionCipher = new SealedSessionCipher(signalProtocolStore, sessionResetProtocol, signalProtocolAddress); - PushTransportDetails transportDetails = new PushTransportDetails(sessionCipher.getSessionVersion(destination)); - byte[] plaintext = transportDetails.getPaddedMessageBody(unpaddedMessage); - byte[] ciphertext = ClosedGroupUtilities.encrypt(plaintext, destination.getName(), userPublicKey); - String body = Base64.encodeBytes(ciphertext); - int remoteRegistrationId = sessionCipher.getRemoteRegistrationId(destination); - return new OutgoingPushMessage(Type.CLOSED_GROUP_CIPHERTEXT_VALUE, destination.getDeviceId(), remoteRegistrationId, body); - } else if (unidentifiedAccess.isPresent()) { - SealedSessionCipher sessionCipher = new SealedSessionCipher(signalProtocolStore, sessionResetProtocol, new SignalProtocolAddress(localAddress.getNumber(), 1)); - PushTransportDetails transportDetails = new PushTransportDetails(sessionCipher.getSessionVersion(destination)); - byte[] ciphertext = sessionCipher.encrypt(destination, unidentifiedAccess.get().getUnidentifiedCertificate(), transportDetails.getPaddedMessageBody(unpaddedMessage)); - String body = Base64.encodeBytes(ciphertext); - int remoteRegistrationId = sessionCipher.getRemoteRegistrationId(destination); - - return new OutgoingPushMessage(Type.UNIDENTIFIED_SENDER_VALUE, destination.getDeviceId(), remoteRegistrationId, body); - } else { - SessionCipher sessionCipher = new SessionCipher(signalProtocolStore, destination); - PushTransportDetails transportDetails = new PushTransportDetails(sessionCipher.getSessionVersion()); - CiphertextMessage message = sessionCipher.encrypt(transportDetails.getPaddedMessageBody(unpaddedMessage)); - int remoteRegistrationId = sessionCipher.getRemoteRegistrationId(); - String body = Base64.encodeBytes(message.serialize()); - - int type; - - switch (message.getType()) { - case CiphertextMessage.PREKEY_TYPE: type = Type.PREKEY_BUNDLE_VALUE; break; - case CiphertextMessage.WHISPER_TYPE: type = Type.CIPHERTEXT_VALUE; break; - case CiphertextMessage.FALLBACK_MESSAGE_TYPE: type = Type.FALLBACK_MESSAGE_VALUE; break; - case CiphertextMessage.CLOSED_GROUP_CIPHERTEXT: type = Type.CLOSED_GROUP_CIPHERTEXT_VALUE; break; - default: throw new AssertionError("Bad type: " + message.getType()); - } - - return new OutgoingPushMessage(type, destination.getDeviceId(), remoteRegistrationId, body); - } - } +// public OutgoingPushMessage encrypt(SignalProtocolAddress destination, +// Optional unidentifiedAccess, +// byte[] unpaddedMessage) +// throws UntrustedIdentityException, InvalidKeyException, IOException +// { +// if (unidentifiedAccess.isPresent() && sskDatabase.isSSKBasedClosedGroup(destination.getName())) { +// String userPublicKey = localAddress.getNumber(); +// SignalProtocolAddress signalProtocolAddress = new SignalProtocolAddress(userPublicKey, 1); +// SealedSessionCipher sessionCipher = new SealedSessionCipher(signalProtocolStore, sessionResetProtocol, signalProtocolAddress); +// PushTransportDetails transportDetails = new PushTransportDetails(sessionCipher.getSessionVersion(destination)); +// byte[] plaintext = transportDetails.getPaddedMessageBody(unpaddedMessage); +// byte[] ciphertext = ClosedGroupUtilities.encrypt(plaintext, destination.getName(), userPublicKey); +// String body = Base64.encodeBytes(ciphertext); +// int remoteRegistrationId = sessionCipher.getRemoteRegistrationId(destination); +// return new OutgoingPushMessage(Type.CLOSED_GROUP_CIPHERTEXT_VALUE, destination.getDeviceId(), remoteRegistrationId, body); +// } else if (unidentifiedAccess.isPresent()) { +// SealedSessionCipher sessionCipher = new SealedSessionCipher(signalProtocolStore, sessionResetProtocol, new SignalProtocolAddress(localAddress.getNumber(), 1)); +// PushTransportDetails transportDetails = new PushTransportDetails(sessionCipher.getSessionVersion(destination)); +// byte[] ciphertext = sessionCipher.encrypt(destination, unidentifiedAccess.get().getUnidentifiedCertificate(), transportDetails.getPaddedMessageBody(unpaddedMessage)); +// String body = Base64.encodeBytes(ciphertext); +// int remoteRegistrationId = sessionCipher.getRemoteRegistrationId(destination); +// +// return new OutgoingPushMessage(Type.UNIDENTIFIED_SENDER_VALUE, destination.getDeviceId(), remoteRegistrationId, body); +// } else { +// SessionCipher sessionCipher = new SessionCipher(signalProtocolStore, destination); +// PushTransportDetails transportDetails = new PushTransportDetails(sessionCipher.getSessionVersion()); +// CiphertextMessage message = sessionCipher.encrypt(transportDetails.getPaddedMessageBody(unpaddedMessage)); +// int remoteRegistrationId = sessionCipher.getRemoteRegistrationId(); +// String body = Base64.encodeBytes(message.serialize()); +// +// int type; +// +// switch (message.getType()) { +// case CiphertextMessage.PREKEY_TYPE: type = Type.PREKEY_BUNDLE_VALUE; break; +// case CiphertextMessage.WHISPER_TYPE: type = Type.CIPHERTEXT_VALUE; break; +// case CiphertextMessage.FALLBACK_MESSAGE_TYPE: type = Type.FALLBACK_MESSAGE_VALUE; break; +// case CiphertextMessage.CLOSED_GROUP_CIPHERTEXT: type = Type.CLOSED_GROUP_CIPHERTEXT_VALUE; break; +// default: throw new AssertionError("Bad type: " + message.getType()); +// } +// +// return new OutgoingPushMessage(type, destination.getDeviceId(), remoteRegistrationId, body); +// } +// } /** * Decrypt a received {@link SignalServiceEnvelope} diff --git a/libsignal/src/main/java/org/session/libsignal/service/loki/crypto/LokiServiceCipher.kt b/libsignal/src/main/java/org/session/libsignal/service/loki/crypto/LokiServiceCipher.kt index 43c6810bfe..150a9debec 100644 --- a/libsignal/src/main/java/org/session/libsignal/service/loki/crypto/LokiServiceCipher.kt +++ b/libsignal/src/main/java/org/session/libsignal/service/loki/crypto/LokiServiceCipher.kt @@ -11,9 +11,8 @@ import org.session.libsignal.service.api.push.SignalServiceAddress import org.session.libsignal.service.internal.push.PushTransportDetails import org.session.libsignal.service.loki.api.crypto.SessionProtocol import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol -import org.session.libsignal.service.loki.protocol.closedgroups.SharedSenderKeysDatabaseProtocol -class LokiServiceCipher(localAddress: SignalServiceAddress, private val signalProtocolStore: SignalProtocolStore, private val sskDatabase: SharedSenderKeysDatabaseProtocol, sessionProtocolImpl: SessionProtocol, sessionResetProtocol: SessionResetProtocol, apiDB: LokiAPIDatabaseProtocol, certificateValidator: CertificateValidator?) : SignalServiceCipher(localAddress, signalProtocolStore, sskDatabase, sessionResetProtocol, sessionProtocolImpl, apiDB, certificateValidator) { +class LokiServiceCipher(localAddress: SignalServiceAddress, private val signalProtocolStore: SignalProtocolStore, sessionProtocolImpl: SessionProtocol, sessionResetProtocol: SessionResetProtocol, apiDB: LokiAPIDatabaseProtocol, certificateValidator: CertificateValidator?) : SignalServiceCipher(localAddress, signalProtocolStore, sessionResetProtocol, sessionProtocolImpl, apiDB, certificateValidator) { private val userPrivateKey get() = signalProtocolStore.identityKeyPair.privateKey.serialize() diff --git a/libsignal/src/main/java/org/session/libsignal/service/loki/database/LokiAPIDatabaseProtocol.kt b/libsignal/src/main/java/org/session/libsignal/service/loki/database/LokiAPIDatabaseProtocol.kt index 07caf9d0d9..0d24f218ba 100644 --- a/libsignal/src/main/java/org/session/libsignal/service/loki/database/LokiAPIDatabaseProtocol.kt +++ b/libsignal/src/main/java/org/session/libsignal/service/loki/database/LokiAPIDatabaseProtocol.kt @@ -37,4 +37,5 @@ interface LokiAPIDatabaseProtocol { fun getUserX25519KeyPair(): ECKeyPair fun getClosedGroupEncryptionKeyPairs(groupPublicKey: String): List fun getLatestClosedGroupEncryptionKeyPair(groupPublicKey: String): ECKeyPair? + fun isClosedGroup(groupPublicKey: String): Boolean } diff --git a/libsignal/src/main/java/org/session/libsignal/service/loki/protocol/closedgroups/ClosedGroupRatchet.kt b/libsignal/src/main/java/org/session/libsignal/service/loki/protocol/closedgroups/ClosedGroupRatchet.kt deleted file mode 100644 index a803152a26..0000000000 --- a/libsignal/src/main/java/org/session/libsignal/service/loki/protocol/closedgroups/ClosedGroupRatchet.kt +++ /dev/null @@ -1,22 +0,0 @@ -package org.session.libsignal.service.loki.protocol.closedgroups - -import org.session.libsignal.service.loki.utilities.prettifiedDescription - -public class ClosedGroupRatchet(public val chainKey: String, public val keyIndex: Int, public val messageKeys: List) { - - override fun equals(other: Any?): Boolean { - return if (other is ClosedGroupRatchet) { - chainKey == other.chainKey && keyIndex == other.keyIndex && messageKeys == other.messageKeys - } else { - false - } - } - - override fun hashCode(): Int { - return chainKey.hashCode() xor keyIndex.hashCode() xor messageKeys.hashCode() - } - - override fun toString(): String { - return "[ chainKey : $chainKey, keyIndex : $keyIndex, messageKeys : ${messageKeys.prettifiedDescription()} ]" - } -} diff --git a/libsignal/src/main/java/org/session/libsignal/service/loki/protocol/closedgroups/ClosedGroupSenderKey.kt b/libsignal/src/main/java/org/session/libsignal/service/loki/protocol/closedgroups/ClosedGroupSenderKey.kt deleted file mode 100644 index bfbebf8c5b..0000000000 --- a/libsignal/src/main/java/org/session/libsignal/service/loki/protocol/closedgroups/ClosedGroupSenderKey.kt +++ /dev/null @@ -1,56 +0,0 @@ -package org.session.libsignal.service.loki.protocol.closedgroups - -import com.google.protobuf.ByteString -import org.session.libsignal.utilities.logging.Log -import org.session.libsignal.utilities.Hex -import org.session.libsignal.service.internal.push.SignalServiceProtos -import org.session.libsignal.utilities.JsonUtil -import org.session.libsignal.service.loki.utilities.toHexString - -public class ClosedGroupSenderKey(public val chainKey: ByteArray, public val keyIndex: Int, public val publicKey: ByteArray) { - - companion object { - - public fun fromJSON(jsonAsString: String): ClosedGroupSenderKey? { - try { - val json = JsonUtil.fromJson(jsonAsString, Map::class.java) - val chainKey = Hex.fromStringCondensed(json["chainKey"] as String) - val keyIndex = json["keyIndex"] as Int - val publicKey = Hex.fromStringCondensed(json["publicKey"] as String) - return ClosedGroupSenderKey(chainKey, keyIndex, publicKey) - } catch (exception: Exception) { - Log.d("Loki", "Couldn't parse closed group sender key from: $jsonAsString.") - return null - } - } - } - - public fun toJSON(): String { - val json = mapOf( "chainKey" to chainKey.toHexString(), "keyIndex" to keyIndex, "publicKey" to publicKey.toHexString() ) - return JsonUtil.toJson(json) - } - - public fun toProto(): SignalServiceProtos.ClosedGroupUpdate.SenderKey { - val builder = SignalServiceProtos.ClosedGroupUpdate.SenderKey.newBuilder() - builder.chainKey = ByteString.copyFrom(chainKey) - builder.keyIndex = keyIndex - builder.publicKey = ByteString.copyFrom(publicKey) - return builder.build() - } - - override fun equals(other: Any?): Boolean { - return if (other is ClosedGroupSenderKey) { - chainKey.contentEquals(other.chainKey) && keyIndex == other.keyIndex && publicKey.contentEquals(other.publicKey) - } else { - false - } - } - - override fun hashCode(): Int { - return chainKey.hashCode() xor keyIndex.hashCode() xor publicKey.hashCode() - } - - override fun toString(): String { - return "[ chainKey : ${chainKey.toHexString()}, keyIndex : $keyIndex, messageKeys : ${publicKey.toHexString()} ]" - } -} diff --git a/libsignal/src/main/java/org/session/libsignal/service/loki/protocol/closedgroups/ClosedGroupUtilities.kt b/libsignal/src/main/java/org/session/libsignal/service/loki/protocol/closedgroups/ClosedGroupUtilities.kt deleted file mode 100644 index 75bcca22b6..0000000000 --- a/libsignal/src/main/java/org/session/libsignal/service/loki/protocol/closedgroups/ClosedGroupUtilities.kt +++ /dev/null @@ -1,78 +0,0 @@ -package org.session.libsignal.service.loki.protocol.closedgroups - -import com.google.protobuf.ByteString -import org.whispersystems.curve25519.Curve25519 -import org.session.libsignal.libsignal.loki.ClosedGroupCiphertextMessage -import org.session.libsignal.utilities.Hex -import org.session.libsignal.libsignal.util.Pair -import org.session.libsignal.service.api.messages.SignalServiceEnvelope -import org.session.libsignal.service.internal.push.SignalServiceProtos -import org.session.libsignal.service.loki.api.utilities.DecryptionUtilities -import org.session.libsignal.service.loki.api.utilities.EncryptionUtilities -import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded -import org.session.libsignal.service.loki.utilities.toHexString -import javax.crypto.Mac -import javax.crypto.spec.SecretKeySpec - -public object ClosedGroupUtilities { - - sealed class Error(val description: String) : Exception() { - object InvalidGroupPublicKey : Error("Invalid group public key.") - object NoData : Error("Received an empty envelope.") - object NoGroupPrivateKey : Error("Missing group private key.") - object ParsingFailed : Error("Couldn't parse closed group ciphertext message.") - } - - @JvmStatic - public fun encrypt(data: ByteArray, groupPublicKey: String, userPublicKey: String): ByteArray { - // 1. ) Encrypt the data with the user's sender key - val ciphertextAndKeyIndex = SharedSenderKeysImplementation.shared.encrypt(data, groupPublicKey, userPublicKey) - val ivAndCiphertext = ciphertextAndKeyIndex.first - val keyIndex = ciphertextAndKeyIndex.second - val x0 = ClosedGroupCiphertextMessage(ivAndCiphertext, Hex.fromStringCondensed(userPublicKey), keyIndex); - // 2. ) Encrypt the result for the group's public key to hide the sender public key and key index - val x1 = EncryptionUtilities.encryptForX25519PublicKey(x0.serialize(), groupPublicKey.removing05PrefixIfNeeded()) - // 3. ) Wrap the result - return SignalServiceProtos.ClosedGroupCiphertextMessageWrapper.newBuilder() - .setCiphertext(ByteString.copyFrom(x1.ciphertext)) - .setEphemeralPublicKey(ByteString.copyFrom(x1.ephemeralPublicKey)) - .build().toByteArray() - } - - @JvmStatic - public fun decrypt(envelope: SignalServiceEnvelope): Pair { - // 1. ) Check preconditions - val groupPublicKey = envelope.source - if (groupPublicKey == null || !SharedSenderKeysImplementation.shared.isClosedGroup(groupPublicKey)) { - throw Error.InvalidGroupPublicKey - } - val data = envelope.content - if (data.count() == 0) { - throw Error.NoData - } - val groupPrivateKey = SharedSenderKeysImplementation.shared.getKeyPair(groupPublicKey)?.privateKey?.serialize() - if (groupPrivateKey == null) { - throw Error.NoGroupPrivateKey - } - // 2. ) Parse the wrapper - val x0 = SignalServiceProtos.ClosedGroupCiphertextMessageWrapper.parseFrom(data) - val ivAndCiphertext = x0.ciphertext.toByteArray() - val ephemeralPublicKey = x0.ephemeralPublicKey.toByteArray() - // 3. ) Decrypt the data inside - val ephemeralSharedSecret = Curve25519.getInstance(Curve25519.BEST).calculateAgreement(ephemeralPublicKey, groupPrivateKey) - val mac = Mac.getInstance("HmacSHA256") - mac.init(SecretKeySpec("LOKI".toByteArray(), "HmacSHA256")) - val symmetricKey = mac.doFinal(ephemeralSharedSecret) - val x1 = DecryptionUtilities.decryptUsingAESGCM(ivAndCiphertext, symmetricKey) - // 4. ) Parse the closed group ciphertext message - val x2 = ClosedGroupCiphertextMessage.from(x1) - if (x2 == null) { - throw Error.ParsingFailed - } - val senderPublicKey = x2.senderPublicKey.toHexString() - // 5. ) Use the info inside the closed group ciphertext message to decrypt the actual message content - val plaintext = SharedSenderKeysImplementation.shared.decrypt(x2.ivAndCiphertext, groupPublicKey, senderPublicKey, x2.keyIndex) - // 6. ) Return - return Pair(plaintext, senderPublicKey) - } -} diff --git a/libsignal/src/main/java/org/session/libsignal/service/loki/protocol/closedgroups/SharedSenderKeysDatabaseProtocol.kt b/libsignal/src/main/java/org/session/libsignal/service/loki/protocol/closedgroups/SharedSenderKeysDatabaseProtocol.kt deleted file mode 100644 index 48f805be15..0000000000 --- a/libsignal/src/main/java/org/session/libsignal/service/loki/protocol/closedgroups/SharedSenderKeysDatabaseProtocol.kt +++ /dev/null @@ -1,25 +0,0 @@ -package org.session.libsignal.service.loki.protocol.closedgroups - -enum class ClosedGroupRatchetCollectionType { Old, Current } - -interface SharedSenderKeysDatabaseProtocol { - - // region Ratchets & Sender Keys - fun getClosedGroupRatchet(groupPublicKey: String, senderPublicKey: String, collection: ClosedGroupRatchetCollectionType): ClosedGroupRatchet? - fun setClosedGroupRatchet(groupPublicKey: String, senderPublicKey: String, ratchet: ClosedGroupRatchet, collection: ClosedGroupRatchetCollectionType) - fun removeAllClosedGroupRatchets(groupPublicKey: String, collection: ClosedGroupRatchetCollectionType) - fun getAllClosedGroupRatchets(groupPublicKey: String, collection: ClosedGroupRatchetCollectionType): Set> - fun getAllClosedGroupSenderKeys(groupPublicKey: String, collection: ClosedGroupRatchetCollectionType): Set - // endregion - - // region Private & Public Keys - fun getClosedGroupPrivateKey(groupPublicKey: String): String? - fun setClosedGroupPrivateKey(groupPublicKey: String, groupPrivateKey: String) - fun removeClosedGroupPrivateKey(groupPublicKey: String) - fun getAllClosedGroupPublicKeys(): Set - // endregion - - // region Convenience - fun isSSKBasedClosedGroup(groupPublicKey: String): Boolean - // endregion -} diff --git a/libsignal/src/main/java/org/session/libsignal/service/loki/protocol/closedgroups/SharedSenderKeysImplementation.kt b/libsignal/src/main/java/org/session/libsignal/service/loki/protocol/closedgroups/SharedSenderKeysImplementation.kt deleted file mode 100644 index 80a9379928..0000000000 --- a/libsignal/src/main/java/org/session/libsignal/service/loki/protocol/closedgroups/SharedSenderKeysImplementation.kt +++ /dev/null @@ -1,217 +0,0 @@ -package org.session.libsignal.service.loki.protocol.closedgroups - -import org.session.libsignal.libsignal.ecc.DjbECPrivateKey -import org.session.libsignal.libsignal.ecc.DjbECPublicKey -import org.session.libsignal.libsignal.ecc.ECKeyPair -import org.session.libsignal.utilities.logging.Log -import org.session.libsignal.libsignal.util.ByteUtil -import org.session.libsignal.utilities.Hex -import org.session.libsignal.service.internal.util.Util -import org.session.libsignal.service.loki.api.utilities.EncryptionUtilities -import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded -import org.session.libsignal.service.loki.utilities.toHexString -import javax.crypto.Cipher -import javax.crypto.Mac -import javax.crypto.spec.GCMParameterSpec -import javax.crypto.spec.SecretKeySpec - -public final class SharedSenderKeysImplementation(private val database: SharedSenderKeysDatabaseProtocol, private val delegate: SharedSenderKeysImplementationDelegate) { - private val gcmTagSize = 128 - private val ivSize = 12 - - // A quick overview of how shared sender key based closed groups work: - // - // • When a user creates a group, they generate a key pair for the group along with a ratchet for - // every member of the group. They bundle this together with some other group info such as the group - // name in a `ClosedGroupUpdateMessage` and send that using established channels to every member of - // the group. Note that because a user can only pick from their existing contacts when selecting - // the group members they shouldn't need to establish sessions before being able to send the - // `ClosedGroupUpdateMessage`. - // • After the group is created, every user polls for the public key associated with the group. - // • Upon receiving a `ClosedGroupUpdateMessage` of type `.new`, a user sends session requests to all - // other members of the group they don't yet have a session with for reasons outlined below. - // • When a user sends a message they step their ratchet and use the resulting message key to encrypt - // the message. - // • When another user receives that message, they step the ratchet associated with the sender and - // use the resulting message key to decrypt the message. - // • When a user leaves or is kicked from a group, all members must generate new ratchets to ensure that - // removed users can't decrypt messages going forward. To this end every user deletes all ratchets - // associated with the group in question upon receiving a group update message that indicates that - // a user left. They then generate a new ratchet for themselves and send it out to all members of - // the group. The user should already have established sessions with all other members at this point - // because of the behavior outlined a few points above. - // • When a user adds a new member to the group, they generate a ratchet for that new member and - // send that bundled in a `ClosedGroupUpdateMessage` to the group. They send a - // `ClosedGroupUpdateMessage` with the newly generated ratchet but also the existing ratchets of - // every other member of the group to the user that joined. - - // region Initialization - companion object { - - public lateinit var shared: SharedSenderKeysImplementation - - public fun configureIfNeeded(database: SharedSenderKeysDatabaseProtocol, delegate: SharedSenderKeysImplementationDelegate) { - if (::shared.isInitialized) { return; } - shared = SharedSenderKeysImplementation(database, delegate) - } - } - // endregion - - // region Error - public class LoadingFailed(val groupPublicKey: String, val senderPublicKey: String) - : Exception("Couldn't get ratchet for closed group with public key: $groupPublicKey, sender public key: $senderPublicKey.") - public class MessageKeyMissing(val targetKeyIndex: Int, val groupPublicKey: String, val senderPublicKey: String) - : Exception("Couldn't find message key for old key index: $targetKeyIndex, public key: $groupPublicKey, sender public key: $senderPublicKey.") - public class GenericRatchetingException : Exception("An error occurred.") - // endregion - - // region Private API - private fun hmac(key: ByteArray, input: ByteArray): ByteArray { - val mac = Mac.getInstance("HmacSHA256") - mac.init(SecretKeySpec(key, "HmacSHA256")) - return mac.doFinal(input) - } - - private fun step(ratchet: ClosedGroupRatchet): ClosedGroupRatchet { - val nextMessageKey = hmac(Hex.fromStringCondensed(ratchet.chainKey), ByteArray(1) { 1.toByte() }) - val nextChainKey = hmac(Hex.fromStringCondensed(ratchet.chainKey), ByteArray(1) { 2.toByte() }) - val nextKeyIndex = ratchet.keyIndex + 1 - val messageKeys = ratchet.messageKeys + listOf( nextMessageKey.toHexString() ) - return ClosedGroupRatchet(nextChainKey.toHexString(), nextKeyIndex, messageKeys) - } - - /** - * Sync. Don't call from the main thread. - */ - private fun stepRatchetOnce(groupPublicKey: String, senderPublicKey: String): ClosedGroupRatchet { - val ratchet = database.getClosedGroupRatchet(groupPublicKey, senderPublicKey, ClosedGroupRatchetCollectionType.Current) - if (ratchet == null) { - val exception = LoadingFailed(groupPublicKey, senderPublicKey) - Log.d("Loki", exception.message ?: "An error occurred.") - throw exception - } - try { - val result = step(ratchet) - database.setClosedGroupRatchet(groupPublicKey, senderPublicKey, result, ClosedGroupRatchetCollectionType.Current) - return result - } catch (exception: Exception) { - Log.d("Loki", "Couldn't step ratchet due to error: $exception.") - throw exception - } - } - - private fun stepRatchet(groupPublicKey: String, senderPublicKey: String, targetKeyIndex: Int, isRetry: Boolean = false): ClosedGroupRatchet { - val collection = if (isRetry) ClosedGroupRatchetCollectionType.Old else ClosedGroupRatchetCollectionType.Current - val ratchet = database.getClosedGroupRatchet(groupPublicKey, senderPublicKey, collection) - if (ratchet == null) { - val exception = LoadingFailed(groupPublicKey, senderPublicKey) - Log.d("Loki", exception.message ?: "An error occurred.") - throw exception - } - if (targetKeyIndex < ratchet.keyIndex) { - // There's no need to advance the ratchet if this is invoked for an old key index - if (ratchet.messageKeys.count() <= targetKeyIndex) { - val exception = MessageKeyMissing(targetKeyIndex, groupPublicKey, senderPublicKey) - Log.d("Loki", exception.message ?: "An error occurred.") - throw exception - } - return ratchet - } else { - var currentKeyIndex = ratchet.keyIndex - var result: ClosedGroupRatchet = ratchet // Explicitly typed because otherwise the compiler has trouble inferring that this can't be null - while (currentKeyIndex < targetKeyIndex) { - try { - result = step(result) - currentKeyIndex = result.keyIndex - } catch (exception: Exception) { - Log.d("Loki", "Couldn't step ratchet due to error: $exception.") - throw exception - } - } - val collection = if (isRetry) ClosedGroupRatchetCollectionType.Old else ClosedGroupRatchetCollectionType.Current - database.setClosedGroupRatchet(groupPublicKey, senderPublicKey, result, collection) - return result - } - } - // endregion - - // region Public API - public fun generateRatchet(groupPublicKey: String, senderPublicKey: String): ClosedGroupRatchet { - val rootChainKey = Util.getSecretBytes(32).toHexString() - val ratchet = ClosedGroupRatchet(rootChainKey, 0, listOf()) - database.setClosedGroupRatchet(groupPublicKey, senderPublicKey, ratchet, ClosedGroupRatchetCollectionType.Current) - return ratchet - } - - public fun encrypt(plaintext: ByteArray, groupPublicKey: String, senderPublicKey: String): Pair { - val ratchet: ClosedGroupRatchet - try { - ratchet = stepRatchetOnce(groupPublicKey, senderPublicKey) - } catch (exception: Exception) { - if (exception is LoadingFailed) { - delegate.requestSenderKey(groupPublicKey, senderPublicKey) - } - throw exception - } - val iv = Util.getSecretBytes(ivSize) - val cipher = Cipher.getInstance("AES/GCM/NoPadding") - val messageKey = ratchet.messageKeys.last() - cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(Hex.fromStringCondensed(messageKey), "AES"), GCMParameterSpec(gcmTagSize, iv)) - return Pair(ByteUtil.combine(iv, cipher.doFinal(plaintext)), ratchet.keyIndex) - } - - public fun decrypt(ivAndCiphertext: ByteArray, groupPublicKey: String, senderPublicKey: String, keyIndex: Int, isRetry: Boolean = false): ByteArray { - val ratchet: ClosedGroupRatchet - try { - ratchet = stepRatchet(groupPublicKey, senderPublicKey, keyIndex, isRetry) - } catch (exception: Exception) { - if (!isRetry) { - return decrypt(ivAndCiphertext, groupPublicKey, senderPublicKey, keyIndex, true) - } else { - if (exception is LoadingFailed) { - delegate.requestSenderKey(groupPublicKey, senderPublicKey) - } - throw exception - } - } - val iv = ivAndCiphertext.sliceArray(0 until ivSize) - val ciphertext = ivAndCiphertext.sliceArray(ivSize until ivAndCiphertext.count()) - val messageKeys = ratchet.messageKeys - val lastNMessageKeys: List - if (messageKeys.count() > 16) { // Pick an arbitrary number of message keys to try; this helps resolve issues caused by messages arriving out of order - lastNMessageKeys = messageKeys.subList(messageKeys.lastIndex - 16, messageKeys.lastIndex) - } else { - lastNMessageKeys = messageKeys - } - if (lastNMessageKeys.isEmpty()) { - throw MessageKeyMissing(keyIndex, groupPublicKey, senderPublicKey) - } - var exception: Exception? = null - for (messageKey in lastNMessageKeys.reversed()) { // Reversed because most likely the last one is the one we need - val cipher = Cipher.getInstance("AES/GCM/NoPadding") - cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(Hex.fromStringCondensed(messageKey), "AES"), GCMParameterSpec(EncryptionUtilities.gcmTagSize, iv)) - try { - return cipher.doFinal(ciphertext) - } catch (e: Exception) { - exception = e - } - } - if (!isRetry) { - return decrypt(ivAndCiphertext, groupPublicKey, senderPublicKey, keyIndex, true) - } else { - delegate.requestSenderKey(groupPublicKey, senderPublicKey) - throw exception ?: GenericRatchetingException() - } - } - - public fun isClosedGroup(publicKey: String): Boolean { - return database.getAllClosedGroupPublicKeys().contains(publicKey) - } - - public fun getKeyPair(groupPublicKey: String): ECKeyPair? { - val privateKey = database.getClosedGroupPrivateKey(groupPublicKey) ?: return null - return ECKeyPair(DjbECPublicKey(Hex.fromStringCondensed(groupPublicKey.removing05PrefixIfNeeded())), - DjbECPrivateKey(Hex.fromStringCondensed(privateKey))) - } - // endregion -} diff --git a/libsignal/src/main/java/org/session/libsignal/service/loki/protocol/closedgroups/SharedSenderKeysImplementationDelegate.kt b/libsignal/src/main/java/org/session/libsignal/service/loki/protocol/closedgroups/SharedSenderKeysImplementationDelegate.kt deleted file mode 100644 index db90892ef6..0000000000 --- a/libsignal/src/main/java/org/session/libsignal/service/loki/protocol/closedgroups/SharedSenderKeysImplementationDelegate.kt +++ /dev/null @@ -1,6 +0,0 @@ -package org.session.libsignal.service.loki.protocol.closedgroups - -public interface SharedSenderKeysImplementationDelegate { - - public fun requestSenderKey(groupPublicKey: String, senderPublicKey: String) -} diff --git a/libsignal/src/main/java/org/session/libsignal/service/loki/protocol/sessionmanagement/SessionManagementProtocol.kt b/libsignal/src/main/java/org/session/libsignal/service/loki/protocol/sessionmanagement/SessionManagementProtocol.kt index 8f31b3dbd5..48a9077f67 100644 --- a/libsignal/src/main/java/org/session/libsignal/service/loki/protocol/sessionmanagement/SessionManagementProtocol.kt +++ b/libsignal/src/main/java/org/session/libsignal/service/loki/protocol/sessionmanagement/SessionManagementProtocol.kt @@ -3,23 +3,20 @@ package org.session.libsignal.service.loki.protocol.sessionmanagement import org.session.libsignal.utilities.logging.Log import org.session.libsignal.libsignal.loki.SessionResetProtocol import org.session.libsignal.libsignal.loki.SessionResetStatus -import org.session.libsignal.libsignal.state.SignalProtocolStore import org.session.libsignal.libsignal.util.guava.Optional import org.session.libsignal.service.api.SignalServiceMessageSender import org.session.libsignal.service.api.push.SignalServiceAddress -import org.session.libsignal.service.loki.protocol.closedgroups.SharedSenderKeysDatabaseProtocol -public class SessionManagementProtocol(private val sessionResetImpl: SessionResetProtocol, private val sskDatabase: SharedSenderKeysDatabaseProtocol, - private val delegate: SessionManagementProtocolDelegate) { +public class SessionManagementProtocol(private val sessionResetImpl: SessionResetProtocol, private val delegate: SessionManagementProtocolDelegate) { // region Initialization companion object { public lateinit var shared: SessionManagementProtocol - public fun configureIfNeeded(sessionResetImpl: SessionResetProtocol, sskDatabase: SharedSenderKeysDatabaseProtocol, delegate: SessionManagementProtocolDelegate) { + public fun configureIfNeeded(sessionResetImpl: SessionResetProtocol, delegate: SessionManagementProtocolDelegate) { if (::shared.isInitialized) { return; } - shared = SessionManagementProtocol(sessionResetImpl, sskDatabase, delegate) + shared = SessionManagementProtocol(sessionResetImpl, delegate) } } // endregion