diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 052acc34c9..ea47964fea 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -465,7 +465,7 @@ android:windowSoftInputMode="stateUnchanged" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/> - + tools:context=".loki.SeedActivity"> { - ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); - ClipData clip = ClipData.newPlainText("seed", seed); - clipboard.setPrimaryClip(clip); - Toast.makeText(getContext(), R.string.activity_settings_seed_copied_message, Toast.LENGTH_SHORT).show(); - }) - .setNeutralButton(R.string.activity_settings_seed_dialog_ok_button_title, null) - .show(); + try { + String hexEncodedSeed = IdentityKeyUtil.retrieve(getContext(), IdentityKeyUtil.lokiSeedKey); + if (hexEncodedSeed == null) { + hexEncodedSeed = SerializationKt.getHexEncodedPrivateKey(IdentityKeyUtil.getIdentityKeyPair(getContext())); // Legacy account + } + String seed = new MnemonicCodec(languageFileDirectory).encode(hexEncodedSeed, MnemonicCodec.Language.Configuration.Companion.getEnglish()); + new AlertDialog.Builder(getContext()) + .setTitle(R.string.activity_settings_seed_dialog_title) + .setMessage(seed) + .setPositiveButton(R.string.activity_settings_seed_dialog_copy_button_title, (DialogInterface.OnClickListener) (dialog, which) -> { + ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = ClipData.newPlainText("seed", seed); + clipboard.setPrimaryClip(clip); + Toast.makeText(getContext(), R.string.activity_settings_seed_copied_message, Toast.LENGTH_SHORT).show(); + }) + .setNeutralButton(R.string.activity_settings_seed_dialog_ok_button_title, null) + .show(); + } catch (Exception e) { + // Do nothing + } break; default: throw new AssertionError(); diff --git a/src/org/thoughtcrime/securesms/PassphraseCreateActivity.java b/src/org/thoughtcrime/securesms/PassphraseCreateActivity.java index e0b06454de..8bdbedc4dc 100644 --- a/src/org/thoughtcrime/securesms/PassphraseCreateActivity.java +++ b/src/org/thoughtcrime/securesms/PassphraseCreateActivity.java @@ -65,7 +65,7 @@ public class PassphraseCreateActivity extends PassphraseActivity { passphrase); MasterSecretUtil.generateAsymmetricMasterSecret(PassphraseCreateActivity.this, masterSecret); - IdentityKeyUtil.generateIdentityKeys(PassphraseCreateActivity.this); + IdentityKeyUtil.generateIdentityKeyPair(PassphraseCreateActivity.this); VersionTracker.updateLastSeenVersion(PassphraseCreateActivity.this); TextSecurePreferences.setLastExperienceVersionCode(PassphraseCreateActivity.this, Util.getCanonicalVersionCode()); diff --git a/src/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java b/src/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java index ca6928f2ec..799be030a2 100644 --- a/src/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java +++ b/src/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java @@ -24,14 +24,12 @@ import android.support.annotation.NonNull; import org.thoughtcrime.securesms.backup.BackupProtos; import org.thoughtcrime.securesms.util.Base64; -import org.thoughtcrime.securesms.util.Hex; import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.IdentityKeyPair; import org.whispersystems.libsignal.InvalidKeyException; import org.whispersystems.libsignal.ecc.Curve; import org.whispersystems.libsignal.ecc.ECKeyPair; import org.whispersystems.libsignal.ecc.ECPrivateKey; -import org.whispersystems.libsignal.logging.Log; import java.io.IOException; import java.util.LinkedList; @@ -54,6 +52,8 @@ public class IdentityKeyUtil { private static final String IDENTITY_PUBLIC_KEY_PREF = "pref_identity_public_v3"; private static final String IDENTITY_PRIVATE_KEY_PREF = "pref_identity_private_v3"; + public static final String lokiSeedKey = "loki_seed"; + public static boolean hasIdentityKey(Context context) { SharedPreferences preferences = context.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0); @@ -86,25 +86,21 @@ public class IdentityKeyUtil { } } - public static void generateIdentityKeyPair(Context context, String hexEncodedPrivateKey) { - try { - byte[] privateKey = Hex.fromStringCondensed(hexEncodedPrivateKey); - ECKeyPair keyPair = Curve.generateKeyPair(privateKey); - IdentityKey publicKey = new IdentityKey(keyPair.getPublicKey()); - save(context, IDENTITY_PUBLIC_KEY_PREF, Base64.encodeBytes(publicKey.serialize())); - save(context, IDENTITY_PRIVATE_KEY_PREF, Base64.encodeBytes(keyPair.getPrivateKey().serialize())); - } catch (Exception e) { - Log.d("Loki", "Couldn't restore key pair from seed due to error: " + e.getMessage() + "."); + public static void generateIdentityKeyPair(Context context, byte[] seed) { + ECKeyPair keyPair; + if (seed != null) { + keyPair = Curve.generateKeyPair(seed); + } else { + keyPair = Curve.generateKeyPair(); } + IdentityKey publicKey = new IdentityKey(keyPair.getPublicKey()); + ECPrivateKey privateKey = keyPair.getPrivateKey(); + save(context, IDENTITY_PUBLIC_KEY_PREF, Base64.encodeBytes(publicKey.serialize())); + save(context, IDENTITY_PRIVATE_KEY_PREF, Base64.encodeBytes(privateKey.serialize())); } - public static void generateIdentityKeys(Context context) { - ECKeyPair djbKeyPair = Curve.generateKeyPair(); - IdentityKey djbIdentityKey = new IdentityKey(djbKeyPair.getPublicKey()); - ECPrivateKey djbPrivateKey = djbKeyPair.getPrivateKey(); - - save(context, IDENTITY_PUBLIC_KEY_PREF, Base64.encodeBytes(djbIdentityKey.serialize())); - save(context, IDENTITY_PRIVATE_KEY_PREF, Base64.encodeBytes(djbPrivateKey.serialize())); + public static void generateIdentityKeyPair(Context context) { + generateIdentityKeyPair(context, null); } public static void migrateIdentityKeys(@NonNull Context context, @@ -120,7 +116,7 @@ public class IdentityKeyUtil { delete(context, IDENTITY_PUBLIC_KEY_CIPHERTEXT_LEGACY_PREF); delete(context, IDENTITY_PRIVATE_KEY_CIPHERTEXT_LEGACY_PREF); } else { - generateIdentityKeys(context); + generateIdentityKeyPair(context); } } } @@ -163,12 +159,12 @@ public class IdentityKeyUtil { } } - private static String retrieve(Context context, String key) { + public static String retrieve(Context context, String key) { SharedPreferences preferences = context.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0); return preferences.getString(key, null); } - private static void save(Context context, String key, String value) { + public static void save(Context context, String key, String value) { SharedPreferences preferences = context.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0); Editor preferencesEditor = preferences.edit(); diff --git a/src/org/thoughtcrime/securesms/loki/AccountDetailsActivity.kt b/src/org/thoughtcrime/securesms/loki/AccountDetailsActivity.kt index 38cfb86590..068a7950b5 100644 --- a/src/org/thoughtcrime/securesms/loki/AccountDetailsActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/AccountDetailsActivity.kt @@ -29,7 +29,7 @@ class AccountDetailsActivity : BaseActionBarActivity() { } val inputMethodManager = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager inputMethodManager.hideSoftInputFromWindow(nameEditText.windowToken, 0) - startActivity(Intent(this, KeyPairActivity::class.java)) + startActivity(Intent(this, SeedActivity::class.java)) finish() } } \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/loki/KeyPairActivity.kt b/src/org/thoughtcrime/securesms/loki/SeedActivity.kt similarity index 85% rename from src/org/thoughtcrime/securesms/loki/KeyPairActivity.kt rename to src/org/thoughtcrime/securesms/loki/SeedActivity.kt index 18d449f167..4a3387c8c4 100644 --- a/src/org/thoughtcrime/securesms/loki/KeyPairActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/SeedActivity.kt @@ -8,8 +8,8 @@ import android.os.Bundle import android.view.View import android.view.inputmethod.InputMethodManager import android.widget.Toast -import kotlinx.android.synthetic.main.activity_key_pair.* -import network.loki.messenger.R; +import kotlinx.android.synthetic.main.activity_seed.* +import network.loki.messenger.R import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.ConversationListActivity @@ -17,20 +17,20 @@ 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.util.Hex import org.thoughtcrime.securesms.util.TextSecurePreferences -import org.whispersystems.libsignal.IdentityKeyPair +import org.whispersystems.curve25519.Curve25519 import org.whispersystems.libsignal.util.KeyHelper import org.whispersystems.signalservice.loki.crypto.MnemonicCodec -import org.whispersystems.signalservice.loki.utilities.hexEncodedPrivateKey import org.whispersystems.signalservice.loki.utilities.hexEncodedPublicKey import java.io.File import java.io.FileOutputStream -class KeyPairActivity : BaseActionBarActivity() { +class SeedActivity : BaseActionBarActivity() { private lateinit var languageFileDirectory: File private var mode = Mode.Register set(newValue) { field = newValue; updateUI() } - private var keyPair: IdentityKeyPair? = null + private var seed: ByteArray? = null set(newValue) { field = newValue; updateMnemonic() } private var mnemonic: String? = null set(newValue) { field = newValue; updateMnemonicTextView() } @@ -42,9 +42,9 @@ class KeyPairActivity : BaseActionBarActivity() { // region Lifecycle override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_key_pair) + setContentView(R.layout.activity_seed) setUpLanguageFileDirectory() - updateKeyPair() + updateSeed() copyButton.setOnClickListener { copy() } toggleModeButton.setOnClickListener { toggleMode() } registerOrRestoreButton.setOnClickListener { registerOrRestore() } @@ -75,9 +75,8 @@ class KeyPairActivity : BaseActionBarActivity() { // endregion // region Updating - private fun updateKeyPair() { - IdentityKeyUtil.generateIdentityKeys(this) - keyPair = IdentityKeyUtil.getIdentityKeyPair(this) + private fun updateSeed() { + seed = Curve25519.getInstance("best").generateSeed(16) } private fun updateUI() { @@ -100,7 +99,8 @@ class KeyPairActivity : BaseActionBarActivity() { } private fun updateMnemonic() { - mnemonic = MnemonicCodec(languageFileDirectory).encode(keyPair!!.hexEncodedPrivateKey) + val hexEncodedSeed = Hex.toStringCondensed(seed) + mnemonic = MnemonicCodec(languageFileDirectory).encode(hexEncodedSeed) } private fun updateMnemonicTextView() { @@ -124,21 +124,24 @@ class KeyPairActivity : BaseActionBarActivity() { } private fun registerOrRestore() { - val keyPair: IdentityKeyPair + var seed: ByteArray when (mode) { - Mode.Register -> keyPair = this.keyPair!! + Mode.Register -> seed = this.seed!! Mode.Restore -> { val mnemonic = mnemonicEditText.text.toString() try { - val hexEncodedPrivateKey = MnemonicCodec(languageFileDirectory).decode(mnemonic) - IdentityKeyUtil.generateIdentityKeyPair(this, hexEncodedPrivateKey) - keyPair = IdentityKeyUtil.getIdentityKeyPair(this) + val hexEncodedSeed = MnemonicCodec(languageFileDirectory).decode(mnemonic) + seed = Hex.fromStringCondensed(hexEncodedSeed) } catch (e: Exception) { val message = if (e is MnemonicCodec.DecodingError) e.description else MnemonicCodec.DecodingError.Generic.description return Toast.makeText(this, message, Toast.LENGTH_SHORT).show() } } } + IdentityKeyUtil.save(this, IdentityKeyUtil.lokiSeedKey, Hex.toStringCondensed(seed)) + if (seed.count() == 16) seed = seed + seed + IdentityKeyUtil.generateIdentityKeyPair(this, seed) + val keyPair = IdentityKeyUtil.getIdentityKeyPair(this) val publicKey = keyPair.publicKey val hexEncodedPublicKey = keyPair.hexEncodedPublicKey val registrationID = KeyHelper.generateRegistrationId(false)