Migrate identity keys to SignalStore.

This commit is contained in:
Greyson Parrelli
2022-01-28 15:16:33 -05:00
parent 9a1b8c9bb2
commit db534cd376
31 changed files with 346 additions and 232 deletions

View File

@@ -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
}
}

View File

@@ -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();

View File

@@ -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;
}