diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index 01bc1f38ae..b8d61c667c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -58,6 +58,7 @@ import org.thoughtcrime.securesms.database.JobDatabase; import org.thoughtcrime.securesms.database.LokiAPIDatabase; import org.thoughtcrime.securesms.database.Storage; import org.thoughtcrime.securesms.database.model.EmojiSearchData; +import org.thoughtcrime.securesms.dependencies.ConfigFactory; import org.thoughtcrime.securesms.dependencies.DatabaseComponent; import org.thoughtcrime.securesms.dependencies.DatabaseModule; import org.thoughtcrime.securesms.emoji.EmojiSource; @@ -147,6 +148,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO @Inject MessageDataProvider messageDataProvider; @Inject JobDatabase jobDatabase; @Inject TextSecurePreferences textSecurePreferences; + @Inject ConfigFactory configFactory; CallMessageProcessor callMessageProcessor; MessagingModuleConfiguration messagingModuleConfiguration; @@ -460,7 +462,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO poller.setUserPublicKey(userPublicKey); return; } - poller = new Poller(); + poller = new Poller(configFactory); } public void startPollingIfNeeded() { @@ -538,6 +540,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO if (!deleteDatabase("signal.db")) { Log.d("Loki", "Failed to delete database."); } + configFactory.keyPairChanged(); Util.runOnMain(() -> new Handler().postDelayed(ApplicationContext.this::restartApplication, 200)); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ConfigDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/ConfigDatabase.kt index dc78970e81..30915c9daf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ConfigDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ConfigDatabase.kt @@ -1,13 +1,43 @@ package org.thoughtcrime.securesms.database import android.content.Context +import androidx.core.content.contentValuesOf +import androidx.core.database.getBlobOrNull import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper class ConfigDatabase(context: Context, helper: SQLCipherOpenHelper): Database(context, helper) { companion object { + private const val KEY = "key" + private const val VALUE = "value" + private const val TABLE_NAME = "configs_table" + + const val CREATE_CONFIG_TABLE_COMMAND = "CREATE TABLE $TABLE_NAME ($KEY TEXT NOT NULL, $VALUE BLOB NOT NULL, PRIMARY KEY($KEY));" + private const val KEY_WHERE = "$KEY = ?" + + const val USER_KEY = "user" + const val CONTACTS_KEY = "contacts" + // conversations use publicKey / URL } + fun storeConfig(key: String, data: ByteArray) { + val db = writableDatabase + val contentValues = contentValuesOf( + KEY to key, + VALUE to data + ) + db.insertOrUpdate(TABLE_NAME, contentValues, KEY_WHERE, arrayOf(key)) + } + + fun retrieveConfig(key: String): ByteArray? { + val db = readableDatabase + val query = db.query(TABLE_NAME, arrayOf(VALUE), KEY_WHERE, arrayOf(key),null, null, null) + val bytes = query?.use { cursor -> + if (!cursor.moveToFirst()) return null + cursor.getBlobOrNull(cursor.getColumnIndex(VALUE)) + } + return bytes + } } \ No newline at end of file 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 d2266b3924..ca6a9d7e04 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 @@ -15,6 +15,7 @@ import org.session.libsignal.utilities.Log; import org.thoughtcrime.securesms.crypto.DatabaseSecret; import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.database.BlindedIdMappingDatabase; +import org.thoughtcrime.securesms.database.ConfigDatabase; import org.thoughtcrime.securesms.database.DraftDatabase; import org.thoughtcrime.securesms.database.EmojiSearchDatabase; import org.thoughtcrime.securesms.database.GroupDatabase; @@ -75,9 +76,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { private static final int lokiV36 = 57; private static final int lokiV37 = 58; private static final int lokiV38 = 59; + private static final int lokiV39 = 60; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes - private static final int DATABASE_VERSION = lokiV38; + private static final int DATABASE_VERSION = lokiV39; private static final String DATABASE_NAME = "signal.db"; private final Context context; @@ -180,6 +182,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { db.execSQL(LokiAPIDatabase.RESET_SEQ_NO); // probably not needed but consistent with all migrations db.execSQL(EmojiSearchDatabase.CREATE_EMOJI_SEARCH_TABLE_COMMAND); db.execSQL(ReactionDatabase.CREATE_REACTION_TABLE_COMMAND); + db.execSQL(ConfigDatabase.CREATE_CONFIG_TABLE_COMMAND); executeStatements(db, SmsDatabase.CREATE_INDEXS); executeStatements(db, MmsDatabase.CREATE_INDEXS); @@ -414,6 +417,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { db.execSQL(EmojiSearchDatabase.CREATE_EMOJI_SEARCH_TABLE_COMMAND); } + if (oldVersion < lokiV39) { + db.execSQL(ConfigDatabase.CREATE_CONFIG_TABLE_COMMAND); + } + db.setTransactionSuccessful(); } finally { db.endTransaction(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt new file mode 100644 index 0000000000..7e6c94e580 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt @@ -0,0 +1,60 @@ +package org.thoughtcrime.securesms.dependencies + +import network.loki.messenger.libsession_util.Contacts +import network.loki.messenger.libsession_util.UserProfile +import org.session.libsession.utilities.ConfigFactoryProtocol +import org.thoughtcrime.securesms.database.ConfigDatabase + +class ConfigFactory(private val configDatabase: ConfigDatabase, private val maybeGetUserEdSecretKey: ()->ByteArray?): + ConfigFactoryProtocol { + + fun keyPairChanged() { // this should only happen restoring or clearing data + _userConfig?.free() + _contacts?.free() + _userConfig = null + _contacts = null + } + + private val userLock = Object() + private var _userConfig: UserProfile? = null + private val contactLock = Object() + private var _contacts: Contacts? = null + + override val userConfig: UserProfile? = synchronized(userLock) { + if (_userConfig == null) { + val secretKey = maybeGetUserEdSecretKey() ?: return@synchronized null + val userDump = configDatabase.retrieveConfig(ConfigDatabase.USER_KEY) + _userConfig = if (userDump != null) { + UserProfile.newInstance(secretKey, userDump) + } else { + UserProfile.newInstance(secretKey) + } + } + _userConfig + } + + override val contacts: Contacts? = synchronized(contactLock) { + if (_contacts == null) { + val secretKey = maybeGetUserEdSecretKey() ?: return@synchronized null + val contactsDump = configDatabase.retrieveConfig(ConfigDatabase.CONTACTS_KEY) + _contacts = if (contactsDump != null) { + Contacts.newInstance(secretKey, contactsDump) + } else { + Contacts.newInstance(secretKey) + } + } + _contacts + } + + + override fun saveUserConfigDump() { + val dumped = userConfig?.dump() ?: return + configDatabase.storeConfig(ConfigDatabase.USER_KEY, dumped) + } + + override fun saveContactConfigDump() { + val dumped = contacts?.dump() ?: return + configDatabase.storeConfig(ConfigDatabase.CONTACTS_KEY, dumped) + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/SessionUtilModule.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/SessionUtilModule.kt index a1acaf703a..e280e88e60 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/SessionUtilModule.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/SessionUtilModule.kt @@ -4,16 +4,14 @@ import android.content.Context import dagger.Module import dagger.Provides import dagger.hilt.InstallIn -import dagger.hilt.android.components.ActivityRetainedComponent import dagger.hilt.android.qualifiers.ApplicationContext -import dagger.hilt.android.scopes.ActivityRetainedScoped -import network.loki.messenger.libsession_util.UserProfile -import org.session.libsignal.utilities.guava.Optional +import dagger.hilt.components.SingletonComponent import org.thoughtcrime.securesms.crypto.KeyPairUtilities import org.thoughtcrime.securesms.database.ConfigDatabase +import javax.inject.Singleton @Module -@InstallIn(ActivityRetainedComponent::class) +@InstallIn(SingletonComponent::class) object SessionUtilModule { private fun maybeUserEdSecretKey(context: Context): ByteArray? { @@ -22,12 +20,10 @@ object SessionUtilModule { } @Provides - @ActivityRetainedScoped - fun provideUser(@ApplicationContext context: Context, configDatabase: ConfigDatabase): Optional = - maybeUserEdSecretKey(context)?.let { key -> - // also get the currently stored dump - val instance = UserProfile.newInstance(key) - Optional.of(instance) - } ?: Optional.absent() + @Singleton + fun provideConfigFactory(@ApplicationContext context: Context, configDatabase: ConfigDatabase): ConfigFactory = + ConfigFactory(configDatabase) { + maybeUserEdSecretKey(context) + } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index 72f29d3e8c..b84c49443a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -25,7 +25,6 @@ import kotlinx.coroutines.withContext import network.loki.messenger.R import network.loki.messenger.databinding.ActivityHomeBinding import network.loki.messenger.databinding.ViewMessageRequestBannerBinding -import network.loki.messenger.libsession_util.UserProfile import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode @@ -38,7 +37,6 @@ import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.ThreadUtils -import org.session.libsignal.utilities.guava.Optional import org.session.libsignal.utilities.toHexString import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.MuteDialog @@ -52,6 +50,7 @@ import org.thoughtcrime.securesms.database.MmsSmsDatabase import org.thoughtcrime.securesms.database.RecipientDatabase import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.model.ThreadRecord +import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.groups.OpenGroupManager import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter @@ -63,9 +62,15 @@ import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.onboarding.SeedActivity import org.thoughtcrime.securesms.onboarding.SeedReminderViewDelegate import org.thoughtcrime.securesms.preferences.SettingsActivity -import org.thoughtcrime.securesms.util.* +import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities +import org.thoughtcrime.securesms.util.DateUtils +import org.thoughtcrime.securesms.util.IP2Country +import org.thoughtcrime.securesms.util.disableClipping +import org.thoughtcrime.securesms.util.push +import org.thoughtcrime.securesms.util.show +import org.thoughtcrime.securesms.util.themeState import java.io.IOException -import java.util.* +import java.util.Locale import javax.inject.Inject @AndroidEntryPoint @@ -83,7 +88,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), @Inject lateinit var recipientDatabase: RecipientDatabase @Inject lateinit var groupDatabase: GroupDatabase @Inject lateinit var textSecurePreferences: TextSecurePreferences - @Inject lateinit var userProfile: Optional + @Inject lateinit var configFactory: ConfigFactory private val globalSearchViewModel by viewModels() private val homeViewModel by viewModels() diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/LinkDeviceActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/LinkDeviceActivity.kt index ee1631a00d..1e84fdbdbe 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/LinkDeviceActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/LinkDeviceActivity.kt @@ -15,6 +15,7 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentPagerAdapter import androidx.lifecycle.lifecycleScope import com.google.android.material.snackbar.Snackbar +import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.filter @@ -32,12 +33,19 @@ import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.crypto.KeyPairUtilities import org.thoughtcrime.securesms.crypto.MnemonicUtilities +import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.util.ScanQRCodeWrapperFragment import org.thoughtcrime.securesms.util.ScanQRCodeWrapperFragmentDelegate import org.thoughtcrime.securesms.util.push import org.thoughtcrime.securesms.util.setUpActionBarSessionLogo +import javax.inject.Inject +@AndroidEntryPoint class LinkDeviceActivity : BaseActionBarActivity(), ScanQRCodeWrapperFragmentDelegate { + + @Inject + lateinit var configFactory: ConfigFactory + private lateinit var binding: ActivityLinkDeviceBinding private val adapter = LinkDeviceActivityAdapter(this) private var restoreJob: Job? = null @@ -103,6 +111,7 @@ class LinkDeviceActivity : BaseActionBarActivity(), ScanQRCodeWrapperFragmentDel val keyPairGenerationResult = KeyPairUtilities.generate(seed) val x25519KeyPair = keyPairGenerationResult.x25519KeyPair KeyPairUtilities.store(this@LinkDeviceActivity, seed, keyPairGenerationResult.ed25519KeyPair, x25519KeyPair) + configFactory.keyPairChanged() val userHexEncodedPublicKey = x25519KeyPair.hexEncodedPublicKey val registrationID = KeyHelper.generateRegistrationId(false) TextSecurePreferences.setLocalRegistrationId(this@LinkDeviceActivity, registrationID) diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/RecoveryPhraseRestoreActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/RecoveryPhraseRestoreActivity.kt index 6a1c785ad6..5c9e03003b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/RecoveryPhraseRestoreActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/RecoveryPhraseRestoreActivity.kt @@ -11,6 +11,7 @@ import android.text.style.ClickableSpan import android.text.style.StyleSpan import android.view.View import android.widget.Toast +import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.R import network.loki.messenger.databinding.ActivityRecoveryPhraseRestoreBinding import org.session.libsession.utilities.TextSecurePreferences @@ -21,10 +22,17 @@ import org.session.libsignal.utilities.hexEncodedPublicKey import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.crypto.KeyPairUtilities import org.thoughtcrime.securesms.crypto.MnemonicUtilities +import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.util.push import org.thoughtcrime.securesms.util.setUpActionBarSessionLogo +import javax.inject.Inject +@AndroidEntryPoint class RecoveryPhraseRestoreActivity : BaseActionBarActivity() { + + @Inject + lateinit var configFactory: ConfigFactory + private lateinit var binding: ActivityRecoveryPhraseRestoreBinding // region Lifecycle override fun onCreate(savedInstanceState: Bundle?) { @@ -72,6 +80,7 @@ class RecoveryPhraseRestoreActivity : BaseActionBarActivity() { val keyPairGenerationResult = KeyPairUtilities.generate(seed) val x25519KeyPair = keyPairGenerationResult.x25519KeyPair KeyPairUtilities.store(this, seed, keyPairGenerationResult.ed25519KeyPair, x25519KeyPair) + configFactory.keyPairChanged() val userHexEncodedPublicKey = x25519KeyPair.hexEncodedPublicKey val registrationID = KeyHelper.generateRegistrationId(false) TextSecurePreferences.setLocalRegistrationId(this, registrationID) diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/RegisterActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/RegisterActivity.kt index 0105fbedf0..148e2a5039 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/RegisterActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/RegisterActivity.kt @@ -16,6 +16,7 @@ import android.text.style.StyleSpan import android.view.View import android.widget.Toast import com.goterl.lazysodium.utils.KeyPair +import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.R import network.loki.messenger.databinding.ActivityRegisterBinding import org.session.libsession.utilities.TextSecurePreferences @@ -24,10 +25,17 @@ import org.session.libsignal.utilities.KeyHelper import org.session.libsignal.utilities.hexEncodedPublicKey import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.crypto.KeyPairUtilities +import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.util.push import org.thoughtcrime.securesms.util.setUpActionBarSessionLogo +import javax.inject.Inject +@AndroidEntryPoint class RegisterActivity : BaseActionBarActivity() { + + @Inject + lateinit var configFactory: ConfigFactory + private lateinit var binding: ActivityRegisterBinding private var seed: ByteArray? = null private var ed25519KeyPair: KeyPair? = null @@ -110,6 +118,7 @@ class RegisterActivity : BaseActionBarActivity() { // region Interaction private fun register() { KeyPairUtilities.store(this, seed!!, ed25519KeyPair!!, x25519KeyPair!!) + configFactory.keyPairChanged() val userHexEncodedPublicKey = x25519KeyPair!!.hexEncodedPublicKey val registrationID = KeyHelper.generateRegistrationId(false) TextSecurePreferences.setLocalRegistrationId(this, registrationID) diff --git a/libsession/build.gradle b/libsession/build.gradle index 0515b3562c..f0957b4fb7 100644 --- a/libsession/build.gradle +++ b/libsession/build.gradle @@ -18,6 +18,7 @@ android { dependencies { implementation project(":libsignal") + implementation project(":libsession-util") implementation project(":liblazysodium") // implementation 'com.goterl:lazysodium-android:5.0.2@aar' implementation "net.java.dev.jna:jna:5.8.0@aar" diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/Poller.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/Poller.kt index 4a39b70c0f..ff7aebace9 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/Poller.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/Poller.kt @@ -12,6 +12,7 @@ import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.jobs.MessageReceiveParameters import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.SnodeModule +import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Snode import java.security.SecureRandom @@ -20,7 +21,7 @@ import java.util.TimerTask private class PromiseCanceledException : Exception("Promise canceled.") -class Poller { +class Poller(private val configFactory: ConfigFactoryProtocol) { var userPublicKey = MessagingModuleConfiguration.shared.storage.getUserPublicKey() ?: "" private var hasStarted: Boolean = false private val usedSnodes: MutableSet = mutableSetOf() diff --git a/libsession/src/main/java/org/session/libsession/utilities/ConfigFactoryProtocol.kt b/libsession/src/main/java/org/session/libsession/utilities/ConfigFactoryProtocol.kt new file mode 100644 index 0000000000..2492907212 --- /dev/null +++ b/libsession/src/main/java/org/session/libsession/utilities/ConfigFactoryProtocol.kt @@ -0,0 +1,11 @@ +package org.session.libsession.utilities + +import network.loki.messenger.libsession_util.Contacts +import network.loki.messenger.libsession_util.UserProfile + +interface ConfigFactoryProtocol { + val userConfig: UserProfile? + val contacts: Contacts? + fun saveUserConfigDump() + fun saveContactConfigDump() +} \ No newline at end of file