diff --git a/res/drawable/ic_shield.xml b/res/drawable/ic_shield.xml
new file mode 100644
index 0000000000..c4104074ed
--- /dev/null
+++ b/res/drawable/ic_shield.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/res/layout/fragment_key_pair_migration_bottom_sheet.xml b/res/layout/fragment_key_pair_migration_bottom_sheet.xml
new file mode 100644
index 0000000000..e9bb17570b
--- /dev/null
+++ b/res/layout/fragment_key_pair_migration_bottom_sheet.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/org/thoughtcrime/securesms/ApplicationContext.java b/src/org/thoughtcrime/securesms/ApplicationContext.java
index c40c330c34..20dbd7e6f6 100644
--- a/src/org/thoughtcrime/securesms/ApplicationContext.java
+++ b/src/org/thoughtcrime/securesms/ApplicationContext.java
@@ -588,7 +588,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
});
}
- public void clearData() {
+ public void clearAllData() {
String token = TextSecurePreferences.getFCMToken(this);
if (token != null && !token.isEmpty()) {
LokiPushNotificationManager.unregister(token, this);
diff --git a/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java b/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java
index a7449ca58d..b9dd7e675f 100644
--- a/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java
+++ b/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java
@@ -45,6 +45,7 @@ import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob;
import org.thoughtcrime.securesms.jobs.TypingSendJob;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
import org.thoughtcrime.securesms.logging.Log;
+import org.thoughtcrime.securesms.loki.api.SessionProtocolImpl;
import org.thoughtcrime.securesms.loki.protocol.SessionResetImplementation;
import org.thoughtcrime.securesms.loki.protocol.shelved.MultiDeviceOpenGroupUpdateJob;
import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment;
@@ -157,6 +158,7 @@ public class SignalCommunicationModule {
DatabaseFactory.getLokiThreadDatabase(context),
DatabaseFactory.getLokiMessageDatabase(context),
DatabaseFactory.getLokiPreKeyBundleDatabase(context),
+ new SessionProtocolImpl(context),
new SessionResetImplementation(context),
DatabaseFactory.getLokiUserDatabase(context),
DatabaseFactory.getGroupDatabase(context),
diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
index 900ba496af..56bf2f2f16 100644
--- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
@@ -65,6 +65,7 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreview;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.activities.HomeActivity;
+import org.thoughtcrime.securesms.loki.api.SessionProtocolImpl;
import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase;
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase;
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol;
@@ -259,7 +260,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
SignalProtocolStore axolotlStore = new SignalProtocolStoreImpl(context);
SessionResetProtocol sessionResetProtocol = new SessionResetImplementation(context);
SignalServiceAddress localAddress = new SignalServiceAddress(TextSecurePreferences.getLocalNumber(context));
- LokiServiceCipher cipher = new LokiServiceCipher(localAddress, axolotlStore, DatabaseFactory.getSSKDatabase(context), sessionResetProtocol, UnidentifiedAccessUtil.getCertificateValidator());
+ LokiServiceCipher cipher = new LokiServiceCipher(localAddress, axolotlStore, DatabaseFactory.getSSKDatabase(context), new SessionProtocolImpl(context), sessionResetProtocol, UnidentifiedAccessUtil.getCertificateValidator());
SignalServiceContent content = cipher.decrypt(envelope);
diff --git a/src/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt
index f6e28336b4..3d10e33ee4 100644
--- a/src/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt
+++ b/src/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt
@@ -6,8 +6,6 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.database.Cursor
-import android.net.Uri
-import android.os.AsyncTask
import android.os.Bundle
import android.text.Spannable
import android.text.SpannableString
@@ -31,14 +29,9 @@ import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.conversation.ConversationActivity
import org.thoughtcrime.securesms.database.DatabaseFactory
-import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.database.model.ThreadRecord
-import org.thoughtcrime.securesms.groups.GroupManager
import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob
-import org.thoughtcrime.securesms.loki.dialogs.ConversationOptionsBottomSheet
-import org.thoughtcrime.securesms.loki.dialogs.LightThemeFeatureIntroBottomSheet
-import org.thoughtcrime.securesms.loki.dialogs.MultiDeviceRemovalBottomSheet
-import org.thoughtcrime.securesms.loki.dialogs.UserDetailsBottomSheet
+import org.thoughtcrime.securesms.loki.dialogs.*
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol
import org.thoughtcrime.securesms.loki.protocol.SessionResetImplementation
import org.thoughtcrime.securesms.loki.utilities.*
@@ -57,6 +50,7 @@ import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.MultiD
import org.whispersystems.signalservice.loki.protocol.shelved.syncmessages.SyncMessagesProtocol
import org.whispersystems.signalservice.loki.utilities.toHexString
import java.io.IOException
+import java.util.*
class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListener, SeedReminderViewDelegate, NewConversationButtonSetViewDelegate {
private lateinit var glide: GlideRequests
@@ -178,7 +172,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
// Clear all data if this is a secondary device
if (TextSecurePreferences.getMasterHexEncodedPublicKey(this) != null) {
TextSecurePreferences.setWasUnlinked(this, true)
- ApplicationContext.getInstance(this).clearData()
+ ApplicationContext.getInstance(this).clearAllData()
}
}
@@ -191,36 +185,15 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
if (hasViewedSeed || !isMasterDevice) {
seedReminderView.visibility = View.GONE
}
-
- // Multi device removal sheet
- if (!TextSecurePreferences.getHasSeenMultiDeviceRemovalSheet(this)) {
- TextSecurePreferences.setHasSeenMultiDeviceRemovalSheet(this)
- val userPublicKey = TextSecurePreferences.getLocalNumber(this)
- val deviceLinks = DatabaseFactory.getLokiAPIDatabase(this).getDeviceLinks(userPublicKey)
- if (deviceLinks.isNotEmpty()) {
- val bottomSheet = MultiDeviceRemovalBottomSheet()
- bottomSheet.onOKTapped = {
- bottomSheet.dismiss()
- }
- bottomSheet.onLinkTapped = {
- bottomSheet.dismiss()
- val url = "https://getsession.org/faq"
- val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
- startActivity(intent)
- }
- bottomSheet.show(supportFragmentManager, bottomSheet.tag)
- return
- }
- }
-
- // Light theme introduction sheet
- if (!TextSecurePreferences.hasSeenLightThemeIntroSheet(this) &&
- UiModeUtilities.isDayUiMode(this)) {
- TextSecurePreferences.setHasSeenLightThemeIntroSheet(this)
- val bottomSheet = LightThemeFeatureIntroBottomSheet()
- bottomSheet.show(supportFragmentManager, bottomSheet.tag)
- return
- }
+ // Show key pair migration sheet if needed
+ if (KeyPairUtilities.hasV2KeyPair(this)) { return }
+ val lastNudge = TextSecurePreferences.getLastKeyPairMigrationNudge(this)
+ val nudgeInterval: Long = 3 * 24 * 60 * 60 * 1000 // 3 days
+ val nudge = (Date().time - lastNudge > nudgeInterval)
+ if (!nudge) { return }
+ val keyPairMigrationSheet = KeyPairMigrationBottomSheet()
+ keyPairMigrationSheet.show(supportFragmentManager, keyPairMigrationSheet.tag)
+ TextSecurePreferences.setLastKeyPairMigrationNudge(this, Date().time)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
diff --git a/src/org/thoughtcrime/securesms/loki/api/SessionProtocolImpl.kt b/src/org/thoughtcrime/securesms/loki/api/SessionProtocolImpl.kt
new file mode 100644
index 0000000000..bc6ff3b029
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/loki/api/SessionProtocolImpl.kt
@@ -0,0 +1,101 @@
+package org.thoughtcrime.securesms.loki.api
+
+import android.content.Context
+import android.util.Log
+import com.goterl.lazycode.lazysodium.LazySodiumAndroid
+import com.goterl.lazycode.lazysodium.SodiumAndroid
+import com.goterl.lazycode.lazysodium.interfaces.Box
+import com.goterl.lazycode.lazysodium.interfaces.Sign
+import com.goterl.lazycode.lazysodium.utils.KeyPair
+import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
+import org.thoughtcrime.securesms.database.DatabaseFactory
+import org.thoughtcrime.securesms.loki.utilities.KeyPairUtilities
+import org.thoughtcrime.securesms.util.TextSecurePreferences
+import org.whispersystems.libsignal.util.Hex
+import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope
+import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope.Type.CLOSED_GROUP_CIPHERTEXT_VALUE
+import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope.Type.UNIDENTIFIED_SENDER_VALUE
+import org.whispersystems.signalservice.loki.api.SnodeAPI
+import org.whispersystems.signalservice.loki.api.crypto.SessionProtocol
+import org.whispersystems.signalservice.loki.utilities.removing05PrefixIfNeeded
+import org.whispersystems.signalservice.loki.utilities.toHexString
+
+class SessionProtocolImpl(private val context: Context) : SessionProtocol {
+
+ override fun encrypt(plaintext: ByteArray, recipientHexEncodedX25519PublicKey: String): ByteArray {
+ val userED25519KeyPair = KeyPairUtilities.getUserED25519KeyPair(context) ?: throw SessionProtocol.Exception.NoUserED25519KeyPair
+ val recipientX25519PublicKey = Hex.fromStringCondensed(recipientHexEncodedX25519PublicKey.removing05PrefixIfNeeded())
+ val sodium = LazySodiumAndroid(SodiumAndroid())
+
+ val verificationData = plaintext + userED25519KeyPair.publicKey.asBytes + recipientX25519PublicKey
+ val signature = ByteArray(Sign.BYTES)
+ try {
+ sodium.cryptoSignDetached(signature, verificationData, verificationData.size.toLong(), userED25519KeyPair.secretKey.asBytes)
+ } catch (exception: Exception) {
+ Log.d("Loki", "Couldn't sign message due to error: $exception.")
+ throw SessionProtocol.Exception.SigningFailed
+ }
+ val plaintextWithMetadata = plaintext + userED25519KeyPair.publicKey.asBytes + signature
+ val ciphertext = ByteArray(plaintextWithMetadata.size + Box.SEALBYTES)
+ try {
+ sodium.cryptoBoxSeal(ciphertext, plaintextWithMetadata, plaintextWithMetadata.size.toLong(), recipientX25519PublicKey)
+ } catch (exception: Exception) {
+ Log.d("Loki", "Couldn't encrypt message due to error: $exception.")
+ throw SessionProtocol.Exception.EncryptionFailed
+ }
+
+ return ciphertext
+ }
+
+ override fun decrypt(envelope: SignalServiceEnvelope): Pair {
+ val ciphertext = envelope.content ?: throw SessionProtocol.Exception.NoData
+ val recipientX25519PrivateKey: ByteArray
+ val recipientX25519PublicKey: ByteArray
+ when (envelope.type) {
+ UNIDENTIFIED_SENDER_VALUE -> {
+ recipientX25519PrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize()
+ recipientX25519PublicKey = Hex.fromStringCondensed(TextSecurePreferences.getLocalNumber(context).removing05PrefixIfNeeded())
+ }
+ CLOSED_GROUP_CIPHERTEXT_VALUE -> {
+ val hexEncodedGroupPublicKey = envelope.source
+ val sskDB = DatabaseFactory.getSSKDatabase(context)
+ if (!sskDB.isSSKBasedClosedGroup(hexEncodedGroupPublicKey)) { throw SessionProtocol.Exception.InvalidGroupPublicKey }
+ val hexEncodedGroupPrivateKey = sskDB.getClosedGroupPrivateKey(hexEncodedGroupPublicKey) ?: throw SessionProtocol.Exception.NoGroupPrivateKey
+ recipientX25519PrivateKey = Hex.fromStringCondensed(hexEncodedGroupPrivateKey)
+ recipientX25519PublicKey = Hex.fromStringCondensed(hexEncodedGroupPublicKey.removing05PrefixIfNeeded())
+ }
+ else -> throw AssertionError()
+ }
+ val sodium = LazySodiumAndroid(SodiumAndroid())
+ val signatureSize = Sign.BYTES
+ val ed25519PublicKeySize = Sign.PUBLICKEYBYTES
+
+ // 1. ) Decrypt the message
+ val plaintextWithMetadata = ByteArray(ciphertext.size - Box.SEALBYTES)
+ try {
+ sodium.cryptoBoxSealOpen(plaintextWithMetadata, ciphertext, ciphertext.size.toLong(), recipientX25519PublicKey, recipientX25519PrivateKey)
+ } catch (exception: Exception) {
+ Log.d("Loki", "Couldn't decrypt message due to error: $exception.")
+ throw SessionProtocol.Exception.DecryptionFailed
+ }
+ if (plaintextWithMetadata.size <= (signatureSize + ed25519PublicKeySize)) { throw SessionProtocol.Exception.DecryptionFailed }
+ // 2. ) Get the message parts
+ val signature = plaintextWithMetadata.sliceArray(plaintextWithMetadata.size - signatureSize until plaintextWithMetadata.size)
+ val senderED25519PublicKey = plaintextWithMetadata.sliceArray(plaintextWithMetadata.size - (signatureSize + ed25519PublicKeySize) until plaintextWithMetadata.size - signatureSize)
+ val plaintext = plaintextWithMetadata.sliceArray(0 until plaintextWithMetadata.size - (signatureSize + ed25519PublicKeySize))
+ // 3. ) Verify the signature
+ val verificationData = (plaintext + senderED25519PublicKey + recipientX25519PublicKey)
+ try {
+ val isValid = sodium.cryptoSignVerifyDetached(signature, verificationData, verificationData.size, senderED25519PublicKey)
+ if (!isValid) { throw SessionProtocol.Exception.InvalidSignature }
+ } catch (exception: Exception) {
+ Log.d("Loki", "Couldn't verify message signature due to error: $exception.")
+ throw SessionProtocol.Exception.InvalidSignature
+ }
+ // 4. ) Get the sender's X25519 public key
+ val senderX25519PublicKey = ByteArray(Sign.CURVE25519_PUBLICKEYBYTES)
+ sodium.convertPublicKeyEd25519ToCurve25519(senderX25519PublicKey, senderED25519PublicKey)
+
+ return Pair(plaintext, "05" + senderX25519PublicKey.toHexString())
+ }
+}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt b/src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt
index 0b7cb3228d..706a5d45ba 100644
--- a/src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt
+++ b/src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt
@@ -3,13 +3,18 @@ package org.thoughtcrime.securesms.loki.database
import android.content.ContentValues
import android.content.Context
import android.util.Log
+import com.goterl.lazycode.lazysodium.utils.Key
+import com.goterl.lazycode.lazysodium.utils.KeyPair
+import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.database.Database
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
import org.thoughtcrime.securesms.loki.utilities.*
import org.thoughtcrime.securesms.util.TextSecurePreferences
+import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope
import org.whispersystems.signalservice.loki.api.Snode
import org.whispersystems.signalservice.loki.database.LokiAPIDatabaseProtocol
import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.DeviceLink
+import org.whispersystems.signalservice.loki.utilities.toHexString
import java.util.*
class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiAPIDatabaseProtocol {
diff --git a/src/org/thoughtcrime/securesms/loki/dialogs/ClearAllDataDialog.kt b/src/org/thoughtcrime/securesms/loki/dialogs/ClearAllDataDialog.kt
index 50e54567b9..fb0d4ad151 100644
--- a/src/org/thoughtcrime/securesms/loki/dialogs/ClearAllDataDialog.kt
+++ b/src/org/thoughtcrime/securesms/loki/dialogs/ClearAllDataDialog.kt
@@ -27,13 +27,13 @@ class ClearAllDataDialog : DialogFragment() {
private fun clearAllData() {
if (KeyPairUtilities.hasV2KeyPair(requireContext())) {
- ApplicationContext.getInstance(context).clearData()
+ ApplicationContext.getInstance(context).clearAllData()
} else {
val dialog = AlertDialog.Builder(requireContext())
val message = "We’ve 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).clearData()
+ ApplicationContext.getInstance(context).clearAllData()
}
dialog.setNegativeButton("Cancel") { _, _ ->
// Do nothing
diff --git a/src/org/thoughtcrime/securesms/loki/dialogs/KeyPairMigrationBottomSheet.kt b/src/org/thoughtcrime/securesms/loki/dialogs/KeyPairMigrationBottomSheet.kt
new file mode 100644
index 0000000000..ede0e74d55
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/loki/dialogs/KeyPairMigrationBottomSheet.kt
@@ -0,0 +1,53 @@
+package org.thoughtcrime.securesms.loki.dialogs
+
+import android.app.AlertDialog
+import android.app.Dialog
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+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 network.loki.messenger.R
+import org.thoughtcrime.securesms.ApplicationContext
+
+class KeyPairMigrationBottomSheet : BottomSheetDialogFragment() {
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ return inflater.inflate(R.layout.fragment_key_pair_migration_bottom_sheet, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ upgradeNowButton.setOnClickListener { upgradeNow() }
+ upgradeLaterButton.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(com.google.android.material.R.id.design_bottom_sheet)
+ BottomSheetBehavior.from(bottomSheet!!).setState(BottomSheetBehavior.STATE_EXPANDED);
+ }
+ return dialog
+ }
+
+ private fun upgradeNow() {
+ val applicationContext = requireContext().applicationContext as ApplicationContext
+ dismiss()
+ val dialog = AlertDialog.Builder(requireContext())
+ dialog.setMessage("You’re 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()
+ }
+ dialog.setNegativeButton(R.string.cancel) { _, _ ->
+ // Do nothing
+ }
+ dialog.create().show()
+ }
+}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/loki/protocol/shelved/MultiDeviceProtocol.kt b/src/org/thoughtcrime/securesms/loki/protocol/shelved/MultiDeviceProtocol.kt
index 130dd2b9b3..4f37c49c73 100644
--- a/src/org/thoughtcrime/securesms/loki/protocol/shelved/MultiDeviceProtocol.kt
+++ b/src/org/thoughtcrime/securesms/loki/protocol/shelved/MultiDeviceProtocol.kt
@@ -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).clearData()
+ ApplicationContext.getInstance(context).clearAllData()
}
}
}
diff --git a/src/org/thoughtcrime/securesms/loki/utilities/KeyPairUtilities.kt b/src/org/thoughtcrime/securesms/loki/utilities/KeyPairUtilities.kt
index 31038c251f..ff7f0fd37b 100644
--- a/src/org/thoughtcrime/securesms/loki/utilities/KeyPairUtilities.kt
+++ b/src/org/thoughtcrime/securesms/loki/utilities/KeyPairUtilities.kt
@@ -1,8 +1,10 @@
package org.thoughtcrime.securesms.loki.utilities
import android.content.Context
+import android.util.Log
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 org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.util.Base64
@@ -11,6 +13,7 @@ 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.signalservice.loki.utilities.toHexString
object KeyPairUtilities {
@@ -49,4 +52,12 @@ object KeyPairUtilities {
fun hasV2KeyPair(context: Context): Boolean {
return (IdentityKeyUtil.retrieve(context, IdentityKeyUtil.ED25519_SECRET_KEY) != null)
}
+
+ fun getUserED25519KeyPair(context: Context): KeyPair? {
+ val hexEncodedED25519PublicKey = IdentityKeyUtil.retrieve(context, IdentityKeyUtil.ED25519_PUBLIC_KEY) ?: return null
+ val hexEncodedED25519SecretKey = IdentityKeyUtil.retrieve(context, IdentityKeyUtil.ED25519_SECRET_KEY) ?: return null
+ val ed25519PublicKey = Key.fromBase64String(hexEncodedED25519PublicKey)
+ val ed25519SecretKey = Key.fromBase64String(hexEncodedED25519SecretKey)
+ return KeyPair(ed25519PublicKey, ed25519SecretKey)
+ }
}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java
index 4787b48cf8..2069274cd7 100644
--- a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java
+++ b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java
@@ -1342,5 +1342,13 @@ public class TextSecurePreferences {
public static void setLastSnodePoolRefreshDate(Context context, Date date) {
setLongPreference(context, "last_snode_pool_refresh_date", date.getTime());
}
+
+ public static long getLastKeyPairMigrationNudge(Context context) {
+ return getLongPreference(context, "last_key_pair_migration_nudge", 0);
+ }
+
+ public static void setLastKeyPairMigrationNudge(Context context, long newValue) {
+ setLongPreference(context, "last_key_pair_migration_nudge", newValue);
+ }
// endregion
}