mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-22 09:49:30 +01:00
Migrate identity keys to SignalStore.
This commit is contained in:
@@ -1,14 +1,23 @@
|
||||
package org.thoughtcrime.securesms.keyvalue
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.preference.PreferenceManager
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||
import org.thoughtcrime.securesms.crypto.MasterCipher
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService
|
||||
import org.thoughtcrime.securesms.util.Base64
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
import org.whispersystems.libsignal.IdentityKey
|
||||
import org.whispersystems.libsignal.IdentityKeyPair
|
||||
import org.whispersystems.libsignal.ecc.Curve
|
||||
import org.whispersystems.signalservice.api.push.ACI
|
||||
import org.whispersystems.signalservice.api.push.PNI
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||
@@ -26,6 +35,10 @@ internal class AccountValues internal constructor(store: KeyValueStore) : Signal
|
||||
private const val KEY_FCM_TOKEN_LAST_SET_TIME = "account.fcm_token_last_set_time"
|
||||
private const val KEY_DEVICE_NAME = "account.device_name"
|
||||
private const val KEY_DEVICE_ID = "account.device_id"
|
||||
private const val KEY_ACI_IDENTITY_PUBLIC_KEY = "account.aci_identity_public_key"
|
||||
private const val KEY_ACI_IDENTITY_PRIVATE_KEY = "account.aci_identity_private_key"
|
||||
private const val KEY_PNI_IDENTITY_PUBLIC_KEY = "account.pni_identity_public_key"
|
||||
private const val KEY_PNI_IDENTITY_PRIVATE_KEY = "account.pni_identity_private_key"
|
||||
|
||||
@VisibleForTesting
|
||||
const val KEY_E164 = "account.e164"
|
||||
@@ -37,14 +50,23 @@ internal class AccountValues internal constructor(store: KeyValueStore) : Signal
|
||||
|
||||
init {
|
||||
if (!store.containsKey(KEY_ACI)) {
|
||||
migrateFromSharedPrefs(ApplicationDependencies.getApplication())
|
||||
migrateFromSharedPrefsV1(ApplicationDependencies.getApplication())
|
||||
}
|
||||
|
||||
if (!store.containsKey(KEY_ACI_IDENTITY_PUBLIC_KEY)) {
|
||||
migrateFromSharedPrefsV2(ApplicationDependencies.getApplication())
|
||||
}
|
||||
}
|
||||
|
||||
public override fun onFirstEverAppLaunch() = Unit
|
||||
|
||||
public override fun getKeysToIncludeInBackup(): List<String> {
|
||||
return emptyList()
|
||||
return listOf(
|
||||
KEY_ACI_IDENTITY_PUBLIC_KEY,
|
||||
KEY_ACI_IDENTITY_PRIVATE_KEY,
|
||||
KEY_PNI_IDENTITY_PUBLIC_KEY,
|
||||
KEY_PNI_IDENTITY_PRIVATE_KEY,
|
||||
)
|
||||
}
|
||||
|
||||
/** The local user's [ACI]. */
|
||||
@@ -84,6 +106,74 @@ internal class AccountValues internal constructor(store: KeyValueStore) : Signal
|
||||
get() = getInteger(KEY_REGISTRATION_ID, 0)
|
||||
set(value) = putInteger(KEY_REGISTRATION_ID, value)
|
||||
|
||||
/** The identity key pair for the ACI identity. */
|
||||
val aciIdentityKey: IdentityKeyPair
|
||||
get() {
|
||||
require(store.containsKey(KEY_ACI_IDENTITY_PUBLIC_KEY)) { "Not yet set!" }
|
||||
return IdentityKeyPair(
|
||||
IdentityKey(getBlob(KEY_ACI_IDENTITY_PUBLIC_KEY, null)),
|
||||
Curve.decodePrivatePoint(getBlob(KEY_ACI_IDENTITY_PRIVATE_KEY, null))
|
||||
)
|
||||
}
|
||||
|
||||
/** The identity key pair for the PNI identity. */
|
||||
val pniIdentityKey: IdentityKeyPair
|
||||
get() {
|
||||
require(store.containsKey(KEY_PNI_IDENTITY_PUBLIC_KEY)) { "Not yet set!" }
|
||||
return IdentityKeyPair(
|
||||
IdentityKey(getBlob(KEY_PNI_IDENTITY_PUBLIC_KEY, null)),
|
||||
Curve.decodePrivatePoint(getBlob(KEY_PNI_IDENTITY_PRIVATE_KEY, null))
|
||||
)
|
||||
}
|
||||
|
||||
/** Generates and saves an identity key pair for the ACI identity. Should only be done once. */
|
||||
fun generateAciIdentityKey() {
|
||||
Log.i(TAG, "Generating a new ACI identity key pair.")
|
||||
require(!store.containsKey(KEY_ACI_IDENTITY_PUBLIC_KEY)) { "Already generated!" }
|
||||
|
||||
val key: IdentityKeyPair = IdentityKeyUtil.generateIdentityKeyPair()
|
||||
store
|
||||
.beginWrite()
|
||||
.putBlob(KEY_ACI_IDENTITY_PUBLIC_KEY, key.publicKey.serialize())
|
||||
.putBlob(KEY_ACI_IDENTITY_PRIVATE_KEY, key.privateKey.serialize())
|
||||
.commit()
|
||||
}
|
||||
|
||||
/** Generates and saves an identity key pair for the PNI identity. Should only be done once. */
|
||||
fun generatePniIdentityKey() {
|
||||
Log.i(TAG, "Generating a new PNI identity key pair.")
|
||||
require(!store.containsKey(KEY_PNI_IDENTITY_PUBLIC_KEY)) { "Already generated!" }
|
||||
|
||||
val key: IdentityKeyPair = IdentityKeyUtil.generateIdentityKeyPair()
|
||||
store
|
||||
.beginWrite()
|
||||
.putBlob(KEY_ACI_IDENTITY_PUBLIC_KEY, key.publicKey.serialize())
|
||||
.putBlob(KEY_ACI_IDENTITY_PRIVATE_KEY, key.privateKey.serialize())
|
||||
.commit()
|
||||
}
|
||||
|
||||
/** When acting as a linked device, this method lets you store the identity keys sent from the primary device */
|
||||
fun setIdentityKeysFromPrimaryDevice(aciKeys: IdentityKeyPair) {
|
||||
require(isLinkedDevice) { "Must be a linked device!" }
|
||||
store
|
||||
.beginWrite()
|
||||
.putBlob(KEY_ACI_IDENTITY_PUBLIC_KEY, aciKeys.publicKey.serialize())
|
||||
.putBlob(KEY_ACI_IDENTITY_PRIVATE_KEY, aciKeys.privateKey.serialize())
|
||||
.commit()
|
||||
}
|
||||
|
||||
/** Only to be used when restoring an identity public key from an old backup */
|
||||
fun restoreLegacyIdentityPublicKeyFromBackup(base64: String) {
|
||||
Log.w(TAG, "Restoring legacy identity public key from backup.")
|
||||
putBlob(KEY_ACI_IDENTITY_PUBLIC_KEY, Base64.decode(base64))
|
||||
}
|
||||
|
||||
/** Only to be used when restoring an identity private key from an old backup */
|
||||
fun restoreLegacyIdentityPrivateKeyFromBackup(base64: String) {
|
||||
Log.w(TAG, "Restoring legacy identity private key from backup.")
|
||||
putBlob(KEY_ACI_IDENTITY_PRIVATE_KEY, Base64.decode(base64))
|
||||
}
|
||||
|
||||
/** Indicates whether the user has the ability to receive FCM messages. Largely coupled to whether they have Play Service. */
|
||||
var fcmEnabled: Boolean
|
||||
@JvmName("isFcmEnabled")
|
||||
@@ -159,8 +249,8 @@ internal class AccountValues internal constructor(store: KeyValueStore) : Signal
|
||||
ApplicationDependencies.getGroupsV2Authorization().clear()
|
||||
}
|
||||
|
||||
private fun migrateFromSharedPrefs(context: Context) {
|
||||
Log.i(TAG, "Migrating account values from shared prefs.")
|
||||
private fun migrateFromSharedPrefsV1(context: Context) {
|
||||
Log.i(TAG, "[V1] Migrating account values from shared prefs.")
|
||||
|
||||
putString(KEY_ACI, TextSecurePreferences.getStringPreference(context, "pref_local_uuid", null))
|
||||
putString(KEY_E164, TextSecurePreferences.getStringPreference(context, "pref_local_number", null))
|
||||
@@ -172,4 +262,63 @@ internal class AccountValues internal constructor(store: KeyValueStore) : Signal
|
||||
putInteger(KEY_FCM_TOKEN_VERSION, TextSecurePreferences.getIntegerPreference(context, "pref_gcm_registration_id_version", 0))
|
||||
putLong(KEY_FCM_TOKEN_LAST_SET_TIME, TextSecurePreferences.getLongPreference(context, "pref_gcm_registration_id_last_set_time", 0))
|
||||
}
|
||||
|
||||
private fun migrateFromSharedPrefsV2(context: Context) {
|
||||
Log.i(TAG, "[V2] Migrating account values from shared prefs.")
|
||||
|
||||
val masterSecretPrefs: SharedPreferences = context.getSharedPreferences("SecureSMS-Preferences", 0)
|
||||
val defaultPrefs: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
||||
if (masterSecretPrefs.hasStringData("pref_identity_public_v3")) {
|
||||
Log.i(TAG, "Migrating modern identity key.")
|
||||
|
||||
val identityPublic = Base64.decode(masterSecretPrefs.getString("pref_identity_public_v3", null)!!)
|
||||
val identityPrivate = Base64.decode(masterSecretPrefs.getString("pref_identity_private_v3", null)!!)
|
||||
|
||||
store
|
||||
.beginWrite()
|
||||
.putBlob(KEY_ACI_IDENTITY_PUBLIC_KEY, identityPublic)
|
||||
.putBlob(KEY_ACI_IDENTITY_PRIVATE_KEY, identityPrivate)
|
||||
.commit()
|
||||
} else if (masterSecretPrefs.hasStringData("pref_identity_public_curve25519")) {
|
||||
Log.i(TAG, "Migrating legacy identity key.")
|
||||
|
||||
val masterCipher = MasterCipher(KeyCachingService.getMasterSecret(context))
|
||||
val identityPublic = Base64.decode(masterSecretPrefs.getString("pref_identity_public_curve25519", null)!!)
|
||||
val identityPrivate = masterCipher.decryptKey(Base64.decode(masterSecretPrefs.getString("pref_identity_private_curve25519", null)!!)).serialize()
|
||||
|
||||
store
|
||||
.beginWrite()
|
||||
.putBlob(KEY_ACI_IDENTITY_PUBLIC_KEY, identityPublic)
|
||||
.putBlob(KEY_ACI_IDENTITY_PRIVATE_KEY, identityPrivate)
|
||||
.commit()
|
||||
} else {
|
||||
Log.w(TAG, "No pre-existing identity key! No migration.")
|
||||
}
|
||||
|
||||
masterSecretPrefs
|
||||
.edit()
|
||||
.remove("pref_identity_public_v3")
|
||||
.remove("pref_identity_private_v3")
|
||||
.remove("pref_identity_public_curve25519")
|
||||
.remove("pref_identity_private_curve25519")
|
||||
.commit()
|
||||
|
||||
defaultPrefs
|
||||
.edit()
|
||||
.remove("pref_local_uuid")
|
||||
.remove("pref_identity_public_v3")
|
||||
.remove("pref_gcm_password")
|
||||
.remove("pref_gcm_registered")
|
||||
.remove("pref_local_registration_id")
|
||||
.remove("pref_gcm_disabled")
|
||||
.remove("pref_gcm_registration_id")
|
||||
.remove("pref_gcm_registration_id_version")
|
||||
.remove("pref_gcm_registration_id_last_set_time")
|
||||
.commit()
|
||||
}
|
||||
|
||||
private fun SharedPreferences.hasStringData(key: String): Boolean {
|
||||
return this.getString(key, null) != null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,9 +131,7 @@ public final class KeyValueStore implements KeyValueReader {
|
||||
|
||||
/**
|
||||
* Forces the store to re-fetch all of it's data from the database.
|
||||
* Should only be used for testing!
|
||||
*/
|
||||
@VisibleForTesting
|
||||
synchronized void resetCache() {
|
||||
dataSet = null;
|
||||
initializeIfNecessary();
|
||||
|
||||
@@ -150,6 +150,13 @@ public final class SignalStore {
|
||||
getInstance().store.resetCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restoring a backup changes the underlying disk values, so the cache needs to be reset.
|
||||
*/
|
||||
public static void onPostBackupRestore() {
|
||||
getInstance().store.resetCache();
|
||||
}
|
||||
|
||||
public static @NonNull AccountValues account() {
|
||||
return getInstance().accountValues;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user