Implement auto-migration

This commit is contained in:
Niels Andriesse
2020-12-16 15:41:37 +11:00
parent 3cd1f0acff
commit 0ab429872c
11 changed files with 198 additions and 17 deletions

View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center_horizontal"
android:padding="@dimen/large_spacing"
app:behavior_hideable="true"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
<ImageView
android:layout_width="64dp"
android:layout_height="64dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_shield"
android:tint="?android:textColorPrimary" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/large_spacing"
android:text="Upgrade Successful!"
android:textStyle="bold"
android:textSize="@dimen/large_font_size" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/large_spacing"
android:text="Your new and improved Session ID is:"
android:textSize="@dimen/medium_font_size"
android:textAlignment="center" />
<TextView
style="@style/SessionIDTextView"
android:id="@+id/sessionIDTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18dp"
android:layout_marginTop="@dimen/medium_spacing"
android:text="05987d601943c267879be41830888066c6a024cbdc9a548d06813924bf3372ea78" />
<Button
style="@style/Widget.Session.Button.Common.UnimportantOutline"
android:id="@+id/copyButton"
android:layout_width="240dp"
android:layout_height="@dimen/medium_button_height"
android:layout_marginTop="@dimen/very_large_spacing"
android:text="Copy" />
<Button
style="@style/Widget.Session.Button.Common.UnimportantOutline"
android:id="@+id/okButton"
android:layout_width="240dp"
android:layout_height="@dimen/medium_button_height"
android:layout_marginTop="@dimen/medium_spacing"
android:text="@string/ok" />
</LinearLayout>

View File

@@ -588,14 +588,19 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
});
}
public void clearAllData() {
public void clearAllData(boolean isMigratingToV2KeyPair) {
String token = TextSecurePreferences.getFCMToken(this);
if (token != null && !token.isEmpty()) {
LokiPushNotificationManager.unregister(token, this);
}
boolean wasUnlinked = TextSecurePreferences.getWasUnlinked(this);
String displayName = TextSecurePreferences.getProfileName(this);
boolean isUsingFCM = TextSecurePreferences.isUsingFCM(this);
TextSecurePreferences.clearAll(this);
TextSecurePreferences.setWasUnlinked(this, wasUnlinked);
if (isMigratingToV2KeyPair) {
TextSecurePreferences.setIsMigratingKeyPair(this, true);
TextSecurePreferences.setIsUsingFCM(this, isUsingFCM);
TextSecurePreferences.setProfileName(this, displayName);
}
MasterSecretUtil.clear(this);
if (!deleteDatabase("signal.db")) {
Log.d("Loki", "Failed to delete database.");

View File

@@ -169,11 +169,6 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
}
this.broadcastReceiver = broadcastReceiver
LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, IntentFilter("blockedContactsChanged"))
// Clear all data if this is a secondary device
if (TextSecurePreferences.getMasterHexEncodedPublicKey(this) != null) {
TextSecurePreferences.setWasUnlinked(this, true)
ApplicationContext.getInstance(this).clearAllData()
}
}
override fun onResume() {
@@ -185,7 +180,11 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
if (hasViewedSeed || !isMasterDevice) {
seedReminderView.visibility = View.GONE
}
// Show key pair migration sheet if needed
showKeyPairMigrationSheetIfNeeded()
showKeyPairMigrationSuccessSheetIfNeeded()
}
private fun showKeyPairMigrationSheetIfNeeded() {
if (KeyPairUtilities.hasV2KeyPair(this)) { return }
val lastNudge = TextSecurePreferences.getLastKeyPairMigrationNudge(this)
val nudgeInterval: Long = 3 * 24 * 60 * 60 * 1000 // 3 days
@@ -196,6 +195,13 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
TextSecurePreferences.setLastKeyPairMigrationNudge(this, Date().time)
}
private fun showKeyPairMigrationSuccessSheetIfNeeded() {
if (!KeyPairUtilities.hasV2KeyPair(this) || !TextSecurePreferences.getIsMigratingKeyPair(this)) { return }
val keyPairMigrationSuccessSheet = KeyPairMigrationSuccessBottomSheet()
keyPairMigrationSuccessSheet.show(supportFragmentManager, keyPairMigrationSuccessSheet.tag)
TextSecurePreferences.setIsMigratingKeyPair(this, false)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == CreateClosedGroupActivity.closedGroupCreatedResultCode) {

View File

@@ -17,6 +17,7 @@ import org.thoughtcrime.securesms.loki.dialogs.LinkDeviceSlaveModeDialog
import org.thoughtcrime.securesms.loki.dialogs.LinkDeviceSlaveModeDialogDelegate
import org.thoughtcrime.securesms.loki.protocol.SessionResetImplementation
import org.thoughtcrime.securesms.loki.protocol.shelved.MultiDeviceProtocol
import org.thoughtcrime.securesms.loki.utilities.KeyPairUtilities
import org.thoughtcrime.securesms.loki.utilities.push
import org.thoughtcrime.securesms.loki.utilities.setUpActionBarSessionLogo
import org.thoughtcrime.securesms.loki.utilities.show
@@ -44,9 +45,11 @@ class LandingActivity : BaseActionBarActivity(), LinkDeviceSlaveModeDialogDelega
fakeChatView.startAnimating()
registerButton.setOnClickListener { register() }
restoreButton.setOnClickListener { restore() }
// linkButton.setOnClickListener { linkDevice() }
if (TextSecurePreferences.getWasUnlinked(this)) {
Toast.makeText(this, R.string.activity_landing_device_unlinked_dialog_title, Toast.LENGTH_LONG).show()
if (TextSecurePreferences.getIsMigratingKeyPair(this)) {
val displayName = TextSecurePreferences.getProfileName(this)
if (displayName != null) {
KeyPairUtilities.migrateToV2KeyPair(this)
}
}
}

View File

@@ -305,7 +305,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
val dialog = AlertDialog.Builder(this)
dialog.setMessage("Youre upgrading to a new Session ID. This will give you improved privacy and security, but it will clear ALL app data. Contacts and conversations will be lost. Proceed?")
dialog.setPositiveButton(R.string.yes) { _, _ ->
applicationContext.clearAllData()
applicationContext.clearAllData(true)
}
dialog.setNegativeButton(R.string.cancel) { _, _ ->
// Do nothing

View File

@@ -27,13 +27,13 @@ class ClearAllDataDialog : DialogFragment() {
private fun clearAllData() {
if (KeyPairUtilities.hasV2KeyPair(requireContext())) {
ApplicationContext.getInstance(context).clearAllData()
ApplicationContext.getInstance(context).clearAllData(false)
} else {
val dialog = AlertDialog.Builder(requireContext())
val message = "Weve upgraded the way Session IDs are generated, so you will be unable to restore your current Session ID."
dialog.setMessage(message)
dialog.setPositiveButton("Yes") { _, _ ->
ApplicationContext.getInstance(context).clearAllData()
ApplicationContext.getInstance(context).clearAllData(false)
}
dialog.setNegativeButton("Cancel") { _, _ ->
// Do nothing

View File

@@ -43,7 +43,7 @@ class KeyPairMigrationBottomSheet : BottomSheetDialogFragment() {
val dialog = AlertDialog.Builder(requireContext())
dialog.setMessage("Youre upgrading to a new Session ID. This will give you improved privacy and security, but it will clear ALL app data. Contacts and conversations will be lost. Proceed?")
dialog.setPositiveButton(R.string.yes) { _, _ ->
applicationContext.clearAllData()
applicationContext.clearAllData(true)
}
dialog.setNegativeButton(R.string.cancel) { _, _ ->
// Do nothing

View File

@@ -0,0 +1,58 @@
package org.thoughtcrime.securesms.loki.dialogs
import android.app.AlertDialog
import android.app.Dialog
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.Toast
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import kotlinx.android.synthetic.main.fragment_key_pair_migration_bottom_sheet.*
import kotlinx.android.synthetic.main.fragment_key_pair_migration_success_bottom_sheet.*
import network.loki.messenger.R
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.loki.utilities.hexEncodedPublicKey
class KeyPairMigrationSuccessBottomSheet : BottomSheetDialogFragment() {
private val sessionID by lazy {
TextSecurePreferences.getLocalNumber(requireContext())
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_key_pair_migration_success_bottom_sheet, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sessionIDTextView.text = sessionID
copyButton.setOnClickListener { copySessionID() }
okButton.setOnClickListener { dismiss() }
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState)
// Expand the bottom sheet by default
dialog.setOnShowListener {
val d = dialog as BottomSheetDialog
val bottomSheet = d.findViewById<FrameLayout>(com.google.android.material.R.id.design_bottom_sheet)
BottomSheetBehavior.from(bottomSheet!!).setState(BottomSheetBehavior.STATE_EXPANDED);
}
return dialog
}
private fun copySessionID() {
val clipboard = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("Session ID", sessionID)
clipboard.setPrimaryClip(clip)
Toast.makeText(requireContext(), R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
}
}

View File

@@ -197,7 +197,7 @@ object MultiDeviceProtocol {
FileServerAPI.shared.removeDeviceLink(slaveDeviceLink) // Attempt to clean up on the file server
}
TextSecurePreferences.setWasUnlinked(context, true)
ApplicationContext.getInstance(context).clearAllData()
ApplicationContext.getInstance(context).clearAllData(false)
}
}
}

View File

@@ -1,18 +1,30 @@
package org.thoughtcrime.securesms.loki.utilities
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.goterl.lazycode.lazysodium.LazySodiumAndroid
import com.goterl.lazycode.lazysodium.SodiumAndroid
import com.goterl.lazycode.lazysodium.utils.Key
import com.goterl.lazycode.lazysodium.utils.KeyPair
import kotlinx.android.synthetic.main.activity_pn_mode.*
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.database.Address
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.IdentityDatabase
import org.thoughtcrime.securesms.loki.activities.HomeActivity
import org.thoughtcrime.securesms.util.Base64
import org.thoughtcrime.securesms.util.Hex
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.curve25519.Curve25519
import org.whispersystems.libsignal.ecc.DjbECPrivateKey
import org.whispersystems.libsignal.ecc.DjbECPublicKey
import org.whispersystems.libsignal.ecc.ECKeyPair
import org.whispersystems.libsignal.util.KeyHelper
import org.whispersystems.signalservice.loki.utilities.hexEncodedPublicKey
import org.whispersystems.signalservice.loki.utilities.toHexString
object KeyPairUtilities {
@@ -60,4 +72,32 @@ object KeyPairUtilities {
val ed25519SecretKey = Key.fromBase64String(hexEncodedED25519SecretKey)
return KeyPair(ed25519PublicKey, ed25519SecretKey)
}
fun migrateToV2KeyPair(context: AppCompatActivity) {
val keyPairGenerationResult = generate()
val seed = keyPairGenerationResult.seed
val ed25519KeyPair = keyPairGenerationResult.ed25519KeyPair
val x25519KeyPair = keyPairGenerationResult.x25519KeyPair
store(context, seed, ed25519KeyPair, x25519KeyPair)
val userHexEncodedPublicKey = x25519KeyPair.hexEncodedPublicKey
val registrationID = KeyHelper.generateRegistrationId(false)
TextSecurePreferences.setLocalRegistrationId(context, registrationID)
DatabaseFactory.getIdentityDatabase(context).saveIdentity(Address.fromSerialized(userHexEncodedPublicKey),
IdentityKeyUtil.getIdentityKeyPair(context).publicKey, IdentityDatabase.VerifiedStatus.VERIFIED,
true, System.currentTimeMillis(), true)
TextSecurePreferences.setLocalNumber(context, userHexEncodedPublicKey)
TextSecurePreferences.setRestorationTime(context, 0)
TextSecurePreferences.setHasViewedSeed(context, false)
TextSecurePreferences.setHasSeenWelcomeScreen(context, true)
TextSecurePreferences.setPromptedPushRegistration(context, true)
TextSecurePreferences.setIsUsingFCM(context, TextSecurePreferences.isUsingFCM(context))
TextSecurePreferences.setHasSeenMultiDeviceRemovalSheet(context)
TextSecurePreferences.setHasSeenLightThemeIntroSheet(context)
val application = ApplicationContext.getInstance(context)
application.setUpStorageAPIIfNeeded()
application.setUpP2PAPIIfNeeded()
val intent = Intent(context, HomeActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
context.show(intent)
}
}

View File

@@ -1350,5 +1350,13 @@ public class TextSecurePreferences {
public static void setLastKeyPairMigrationNudge(Context context, long newValue) {
setLongPreference(context, "last_key_pair_migration_nudge", newValue);
}
public static boolean getIsMigratingKeyPair(Context context) {
return getBooleanPreference(context, "is_migrating_key_pair", false);
}
public static void setIsMigratingKeyPair(Context context, boolean newValue) {
setBooleanPreference(context, "is_migrating_key_pair", newValue);
}
// endregion
}