mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-21 10:17:56 +00:00
Migrate identity keys to SignalStore.
This commit is contained in:
@@ -52,6 +52,7 @@ public final class AppInitialization {
|
||||
Log.i(TAG, "onPostBackupRestore()");
|
||||
|
||||
ApplicationDependencies.getMegaphoneRepository().onFirstEverAppLaunch();
|
||||
SignalStore.onPostBackupRestore();
|
||||
SignalStore.onFirstEverAppLaunch();
|
||||
SignalStore.onboarding().clearAll();
|
||||
TextSecurePreferences.onPostBackupRestore(context);
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.qr.ScanListener;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
@@ -187,7 +188,7 @@ public class DeviceActivity extends PassphraseRequiredActivity
|
||||
}
|
||||
|
||||
ECPublicKey publicKey = Curve.decodePoint(Base64.decode(publicKeyEncoded), 0);
|
||||
IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context);
|
||||
IdentityKeyPair identityKeyPair = SignalStore.account().getAciIdentityKey();
|
||||
Optional<byte[]> profileKey = Optional.of(ProfileKeyUtil.getProfileKey(getContext()));
|
||||
|
||||
TextSecurePreferences.setMultiDevice(DeviceActivity.this, true);
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.os.Bundle;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.util.VersionTracker;
|
||||
|
||||
/**
|
||||
@@ -61,7 +62,7 @@ public class PassphraseCreateActivity extends PassphraseActivity {
|
||||
passphrase);
|
||||
|
||||
MasterSecretUtil.generateAsymmetricMasterSecret(PassphraseCreateActivity.this, masterSecret);
|
||||
IdentityKeyUtil.generateIdentityKeys(PassphraseCreateActivity.this);
|
||||
SignalStore.account().generateAciIdentityKey();
|
||||
VersionTracker.updateLastSeenVersion(PassphraseCreateActivity.this);
|
||||
|
||||
return null;
|
||||
|
||||
@@ -182,12 +182,6 @@ public class FullBackupExporter extends FullBackupBase {
|
||||
stopwatch.split("table::" + table);
|
||||
}
|
||||
|
||||
for (BackupProtos.SharedPreference preference : IdentityKeyUtil.getBackupRecord(context)) {
|
||||
throwIfCanceled(cancellationSignal);
|
||||
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count, estimatedCount));
|
||||
outputStream.write(preference);
|
||||
}
|
||||
|
||||
for (BackupProtos.SharedPreference preference : TextSecurePreferences.getPreferencesToSaveToBackup(context)) {
|
||||
throwIfCanceled(cancellationSignal);
|
||||
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count, estimatedCount));
|
||||
|
||||
@@ -32,6 +32,7 @@ import org.thoughtcrime.securesms.database.SearchDatabase;
|
||||
import org.thoughtcrime.securesms.database.StickerDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.keyvalue.KeyValueDataSet;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.BackupUtil;
|
||||
@@ -250,6 +251,17 @@ public class FullBackupImporter extends FullBackupBase {
|
||||
private static void processPreference(@NonNull Context context, SharedPreference preference) {
|
||||
SharedPreferences preferences = context.getSharedPreferences(preference.getFile(), 0);
|
||||
|
||||
// Identity keys were moved from shared prefs into SignalStore. Need to handle importing backups made before the migration.
|
||||
if ("SecureSMS-Preferences".equals(preference.getFile())) {
|
||||
if ("pref_identity_public_v3".equals(preference.getKey()) && preference.hasValue()) {
|
||||
SignalStore.account().restoreLegacyIdentityPublicKeyFromBackup(preference.getValue());
|
||||
} else if ("pref_identity_private_v3".equals(preference.getKey()) && preference.hasValue()) {
|
||||
SignalStore.account().restoreLegacyIdentityPrivateKeyFromBackup(preference.getValue());
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (preference.hasValue()) {
|
||||
preferences.edit().putString(preference.getKey(), preference.getValue()).commit();
|
||||
} else if (preference.hasBooleanValue()) {
|
||||
|
||||
@@ -9,7 +9,7 @@ import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.crypto.ReentrantSessionLock;
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
|
||||
import org.thoughtcrime.securesms.crypto.storage.SignalIdentityKeyStore;
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||
@@ -40,7 +40,7 @@ public class UntrustedSendDialog extends AlertDialog.Builder implements DialogIn
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
final TextSecureIdentityKeyStore identityStore = ApplicationDependencies.getProtocolStore().aci().identities();
|
||||
final SignalIdentityKeyStore identityStore = ApplicationDependencies.getProtocolStore().aci().identities();
|
||||
|
||||
SimpleTask.run(() -> {
|
||||
try(SignalSessionLock.Lock unused = ReentrantSessionLock.INSTANCE.acquire()) {
|
||||
|
||||
@@ -14,7 +14,7 @@ import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.SessionUtil;
|
||||
import org.thoughtcrime.securesms.crypto.ReentrantSessionLock;
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
|
||||
import org.thoughtcrime.securesms.crypto.storage.SignalIdentityKeyStore;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord;
|
||||
@@ -96,7 +96,7 @@ final class SafetyNumberChangeRepository {
|
||||
|
||||
@WorkerThread
|
||||
private TrustAndVerifyResult trustOrVerifyChangedRecipientsInternal(@NonNull List<ChangedRecipient> changedRecipients) {
|
||||
TextSecureIdentityKeyStore identityStore = ApplicationDependencies.getProtocolStore().aci().identities();
|
||||
SignalIdentityKeyStore identityStore = ApplicationDependencies.getProtocolStore().aci().identities();
|
||||
|
||||
try(SignalSessionLock.Lock unused = ReentrantSessionLock.INSTANCE.acquire()) {
|
||||
for (ChangedRecipient changedRecipient : changedRecipients) {
|
||||
@@ -129,10 +129,10 @@ final class SafetyNumberChangeRepository {
|
||||
SignalProtocolAddress mismatchAddress = new SignalProtocolAddress(changedRecipient.getRecipient().requireServiceId(), SignalServiceAddress.DEFAULT_DEVICE_ID);
|
||||
|
||||
Log.d(TAG, "Saving identity for: " + changedRecipient.getRecipient().getId() + " " + changedRecipient.getIdentityRecord().getIdentityKey().hashCode());
|
||||
TextSecureIdentityKeyStore.SaveResult result = ApplicationDependencies.getProtocolStore().aci().identities().saveIdentity(mismatchAddress, changedRecipient.getIdentityRecord().getIdentityKey(), true);
|
||||
SignalIdentityKeyStore.SaveResult result = ApplicationDependencies.getProtocolStore().aci().identities().saveIdentity(mismatchAddress, changedRecipient.getIdentityRecord().getIdentityKey(), true);
|
||||
|
||||
Log.d(TAG, "Saving identity result: " + result);
|
||||
if (result == TextSecureIdentityKeyStore.SaveResult.NO_CHANGE) {
|
||||
if (result == SignalIdentityKeyStore.SaveResult.NO_CHANGE) {
|
||||
Log.i(TAG, "Archiving sessions explicitly as they appear to be out of sync.");
|
||||
SessionUtil.archiveSession(changedRecipient.getRecipient().getId(), SignalServiceAddress.DEFAULT_DEVICE_ID);
|
||||
SessionUtil.archiveSiblingSessions(mismatchAddress);
|
||||
|
||||
@@ -17,27 +17,11 @@
|
||||
*/
|
||||
package org.thoughtcrime.securesms.crypto;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.backup.BackupProtos;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
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.util.guava.Preconditions;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Utility class for working with identity keys.
|
||||
@@ -47,63 +31,6 @@ import java.util.List;
|
||||
|
||||
public class IdentityKeyUtil {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = Log.tag(IdentityKeyUtil.class);
|
||||
|
||||
private static final String IDENTITY_PUBLIC_KEY_CIPHERTEXT_LEGACY_PREF = "pref_identity_public_curve25519";
|
||||
private static final String IDENTITY_PRIVATE_KEY_CIPHERTEXT_LEGACY_PREF = "pref_identity_private_curve25519";
|
||||
|
||||
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 boolean hasIdentityKey(Context context) {
|
||||
SharedPreferences preferences = context.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0);
|
||||
|
||||
return
|
||||
preferences.contains(IDENTITY_PUBLIC_KEY_PREF) &&
|
||||
preferences.contains(IDENTITY_PRIVATE_KEY_PREF);
|
||||
}
|
||||
|
||||
public static @NonNull IdentityKey getIdentityKey(@NonNull Context context) {
|
||||
if (!hasIdentityKey(context)) throw new AssertionError("There isn't one!");
|
||||
|
||||
try {
|
||||
byte[] publicKeyBytes = Base64.decode(retrieve(context, IDENTITY_PUBLIC_KEY_PREF));
|
||||
return new IdentityKey(publicKeyBytes, 0);
|
||||
} catch (IOException | InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static @NonNull IdentityKeyPair getIdentityKeyPair(@NonNull Context context) {
|
||||
if (!hasIdentityKey(context)) throw new AssertionError("There isn't one!");
|
||||
|
||||
try {
|
||||
IdentityKey publicKey = getIdentityKey(context);
|
||||
ECPrivateKey privateKey = Curve.decodePrivatePoint(Base64.decode(retrieve(context, IDENTITY_PRIVATE_KEY_PREF)));
|
||||
|
||||
return new IdentityKeyPair(publicKey, privateKey);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void generateIdentityKeys(Context context) {
|
||||
IdentityKeyPair identityKeyPair = generateIdentityKeyPair();
|
||||
|
||||
save(context, IDENTITY_PUBLIC_KEY_PREF, Base64.encodeBytes(identityKeyPair.getPublicKey().serialize()));
|
||||
save(context, IDENTITY_PRIVATE_KEY_PREF, Base64.encodeBytes(identityKeyPair.getPrivateKey().serialize()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Only call when configuring as a secondary linked device.
|
||||
*/
|
||||
public static void setIdentityKeys(Context context, IdentityKeyPair identityKeyPair) {
|
||||
Preconditions.checkState(SignalStore.account().isLinkedDevice(), "Identity keys can only be set directly by a linked device");
|
||||
save(context, IDENTITY_PUBLIC_KEY_PREF, Base64.encodeBytes(identityKeyPair.getPublicKey().serialize()));
|
||||
save(context, IDENTITY_PRIVATE_KEY_PREF, Base64.encodeBytes(identityKeyPair.getPrivateKey().serialize()));
|
||||
}
|
||||
|
||||
public static IdentityKeyPair generateIdentityKeyPair() {
|
||||
ECKeyPair djbKeyPair = Curve.generateKeyPair();
|
||||
IdentityKey djbIdentityKey = new IdentityKey(djbKeyPair.getPublicKey());
|
||||
@@ -111,78 +38,4 @@ public class IdentityKeyUtil {
|
||||
|
||||
return new IdentityKeyPair(djbIdentityKey, djbPrivateKey);
|
||||
}
|
||||
|
||||
public static void migrateIdentityKeys(@NonNull Context context,
|
||||
@NonNull MasterSecret masterSecret)
|
||||
{
|
||||
if (!hasIdentityKey(context)) {
|
||||
if (hasLegacyIdentityKeys(context)) {
|
||||
IdentityKeyPair legacyPair = getLegacyIdentityKeyPair(context, masterSecret);
|
||||
|
||||
save(context, IDENTITY_PUBLIC_KEY_PREF, Base64.encodeBytes(legacyPair.getPublicKey().serialize()));
|
||||
save(context, IDENTITY_PRIVATE_KEY_PREF, Base64.encodeBytes(legacyPair.getPrivateKey().serialize()));
|
||||
|
||||
delete(context, IDENTITY_PUBLIC_KEY_CIPHERTEXT_LEGACY_PREF);
|
||||
delete(context, IDENTITY_PRIVATE_KEY_CIPHERTEXT_LEGACY_PREF);
|
||||
} else {
|
||||
generateIdentityKeys(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static List<BackupProtos.SharedPreference> getBackupRecord(@NonNull Context context) {
|
||||
SharedPreferences preferences = context.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0);
|
||||
|
||||
return new LinkedList<BackupProtos.SharedPreference>() {{
|
||||
add(BackupProtos.SharedPreference.newBuilder()
|
||||
.setFile(MasterSecretUtil.PREFERENCES_NAME)
|
||||
.setKey(IDENTITY_PUBLIC_KEY_PREF)
|
||||
.setValue(preferences.getString(IDENTITY_PUBLIC_KEY_PREF, null))
|
||||
.build());
|
||||
add(BackupProtos.SharedPreference.newBuilder()
|
||||
.setFile(MasterSecretUtil.PREFERENCES_NAME)
|
||||
.setKey(IDENTITY_PRIVATE_KEY_PREF)
|
||||
.setValue(preferences.getString(IDENTITY_PRIVATE_KEY_PREF, null))
|
||||
.build());
|
||||
}};
|
||||
}
|
||||
|
||||
private static boolean hasLegacyIdentityKeys(Context context) {
|
||||
return
|
||||
retrieve(context, IDENTITY_PUBLIC_KEY_CIPHERTEXT_LEGACY_PREF) != null &&
|
||||
retrieve(context, IDENTITY_PRIVATE_KEY_CIPHERTEXT_LEGACY_PREF) != null;
|
||||
}
|
||||
|
||||
private static IdentityKeyPair getLegacyIdentityKeyPair(@NonNull Context context,
|
||||
@NonNull MasterSecret masterSecret)
|
||||
{
|
||||
try {
|
||||
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
||||
byte[] publicKeyBytes = Base64.decode(retrieve(context, IDENTITY_PUBLIC_KEY_CIPHERTEXT_LEGACY_PREF));
|
||||
IdentityKey identityKey = new IdentityKey(publicKeyBytes, 0);
|
||||
ECPrivateKey privateKey = masterCipher.decryptKey(Base64.decode(retrieve(context, IDENTITY_PRIVATE_KEY_CIPHERTEXT_LEGACY_PREF)));
|
||||
|
||||
return new IdentityKeyPair(identityKey, privateKey);
|
||||
} catch (IOException | InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private 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) {
|
||||
SharedPreferences preferences = context.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0);
|
||||
Editor preferencesEditor = preferences.edit();
|
||||
|
||||
preferencesEditor.putString(key, value);
|
||||
if (!preferencesEditor.commit()) throw new AssertionError("failed to save identity key/value to shared preferences");
|
||||
}
|
||||
|
||||
private static void delete(Context context, String key) {
|
||||
context.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0).edit().remove(key).commit();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import androidx.annotation.Nullable;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.SessionUtil;
|
||||
import org.thoughtcrime.securesms.crypto.storage.SignalIdentityKeyStore.SaveResult;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
@@ -19,12 +20,12 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.IdentityUtil;
|
||||
import org.thoughtcrime.securesms.util.LRUCache;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
import org.whispersystems.libsignal.IdentityKeyPair;
|
||||
import org.whispersystems.libsignal.SignalProtocolAddress;
|
||||
import org.whispersystems.libsignal.state.IdentityKeyStore;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.push.AccountIdentifier;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -32,9 +33,14 @@ import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class TextSecureIdentityKeyStore implements IdentityKeyStore {
|
||||
/**
|
||||
* We technically need a separate ACI and PNI identity store, but we want them both to share the same underlying data, including the same cache.
|
||||
* So this class represents the core store, and we can create multiple {@link SignalIdentityKeyStore} that use this same instance, changing only what each of
|
||||
* those reports as their own identity key.
|
||||
*/
|
||||
public class SignalBaseIdentityKeyStore {
|
||||
|
||||
private static final String TAG = Log.tag(TextSecureIdentityKeyStore.class);
|
||||
private static final String TAG = Log.tag(SignalBaseIdentityKeyStore.class);
|
||||
|
||||
private static final Object LOCK = new Object();
|
||||
private static final int TIMESTAMP_THRESHOLD_SECONDS = 5;
|
||||
@@ -42,26 +48,19 @@ public class TextSecureIdentityKeyStore implements IdentityKeyStore {
|
||||
private final Context context;
|
||||
private final Cache cache;
|
||||
|
||||
public TextSecureIdentityKeyStore(Context context) {
|
||||
public SignalBaseIdentityKeyStore(@NonNull Context context) {
|
||||
this(context, SignalDatabase.identities());
|
||||
}
|
||||
|
||||
TextSecureIdentityKeyStore(@NonNull Context context, @NonNull IdentityDatabase identityDatabase) {
|
||||
SignalBaseIdentityKeyStore(@NonNull Context context, @NonNull IdentityDatabase identityDatabase) {
|
||||
this.context = context;
|
||||
this.cache = new Cache(identityDatabase);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityKeyPair getIdentityKeyPair() {
|
||||
return IdentityKeyUtil.getIdentityKeyPair(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLocalRegistrationId() {
|
||||
return SignalStore.account().getRegistrationId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) {
|
||||
return saveIdentity(address, identityKey, false) == SaveResult.UPDATE;
|
||||
}
|
||||
@@ -121,15 +120,14 @@ public class TextSecureIdentityKeyStore implements IdentityKeyStore {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) {
|
||||
public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, IdentityKeyStore.Direction direction) {
|
||||
Recipient self = Recipient.self();
|
||||
|
||||
boolean isSelf = address.getName().equals(self.requireAci().toString()) ||
|
||||
address.getName().equals(self.requireE164());
|
||||
|
||||
if (isSelf) {
|
||||
return identityKey.equals(IdentityKeyUtil.getIdentityKey(context));
|
||||
return identityKey.equals(SignalStore.account().getAciIdentityKey().getPublicKey());
|
||||
}
|
||||
|
||||
IdentityStoreRecord record = cache.get(address.getName());
|
||||
@@ -144,7 +142,6 @@ public class TextSecureIdentityKeyStore implements IdentityKeyStore {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityKey getIdentity(SignalProtocolAddress address) {
|
||||
IdentityStoreRecord record = cache.get(address.getName());
|
||||
return record != null ? record.getIdentityKey() : null;
|
||||
@@ -317,11 +314,4 @@ public class TextSecureIdentityKeyStore implements IdentityKeyStore {
|
||||
cache.remove(addressName);
|
||||
}
|
||||
}
|
||||
|
||||
public enum SaveResult {
|
||||
NEW,
|
||||
UPDATE,
|
||||
NON_BLOCKING_APPROVAL_REQUIRED,
|
||||
NO_CHANGE
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package org.thoughtcrime.securesms.crypto.storage;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus;
|
||||
import org.thoughtcrime.securesms.database.identity.IdentityRecordList;
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
import org.whispersystems.libsignal.IdentityKeyPair;
|
||||
import org.whispersystems.libsignal.SignalProtocolAddress;
|
||||
import org.whispersystems.libsignal.state.IdentityKeyStore;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* A wrapper around an instance of {@link SignalBaseIdentityKeyStore} that lets us report different values for {@link #getIdentityKeyPair()}.
|
||||
* This lets us have multiple instances (one for ACI, one for PNI) that share the same underlying data while also reporting the correct identity key.
|
||||
*/
|
||||
public class SignalIdentityKeyStore implements IdentityKeyStore {
|
||||
|
||||
private final SignalBaseIdentityKeyStore baseStore;
|
||||
private final Supplier<IdentityKeyPair> identitySupplier;
|
||||
|
||||
public SignalIdentityKeyStore(@NonNull SignalBaseIdentityKeyStore baseStore, @NonNull Supplier<IdentityKeyPair> identitySupplier) {
|
||||
this.baseStore = baseStore;
|
||||
this.identitySupplier = identitySupplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityKeyPair getIdentityKeyPair() {
|
||||
return identitySupplier.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLocalRegistrationId() {
|
||||
return baseStore.getLocalRegistrationId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) {
|
||||
return baseStore.saveIdentity(address, identityKey);
|
||||
}
|
||||
|
||||
public @NonNull SaveResult saveIdentity(SignalProtocolAddress address, IdentityKey identityKey, boolean nonBlockingApproval) {
|
||||
return baseStore.saveIdentity(address, identityKey, nonBlockingApproval);
|
||||
}
|
||||
|
||||
public void saveIdentityWithoutSideEffects(@NonNull RecipientId recipientId,
|
||||
IdentityKey identityKey,
|
||||
VerifiedStatus verifiedStatus,
|
||||
boolean firstUse,
|
||||
long timestamp,
|
||||
boolean nonBlockingApproval)
|
||||
{
|
||||
baseStore.saveIdentityWithoutSideEffects(recipientId, identityKey, verifiedStatus, firstUse, timestamp, nonBlockingApproval);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) {
|
||||
return baseStore.isTrustedIdentity(address, identityKey, direction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityKey getIdentity(SignalProtocolAddress address) {
|
||||
return baseStore.getIdentity(address);
|
||||
}
|
||||
|
||||
public @NonNull Optional<IdentityRecord> getIdentityRecord(@NonNull RecipientId recipientId) {
|
||||
return baseStore.getIdentityRecord(recipientId);
|
||||
}
|
||||
|
||||
public @NonNull IdentityRecordList getIdentityRecords(@NonNull List<Recipient> recipients) {
|
||||
return baseStore.getIdentityRecords(recipients);
|
||||
}
|
||||
|
||||
public void setApproval(@NonNull RecipientId recipientId, boolean nonBlockingApproval) {
|
||||
baseStore.setApproval(recipientId, nonBlockingApproval);
|
||||
}
|
||||
|
||||
public void setVerified(@NonNull RecipientId recipientId, IdentityKey identityKey, VerifiedStatus verifiedStatus) {
|
||||
baseStore.setVerified(recipientId, identityKey, verifiedStatus);
|
||||
}
|
||||
|
||||
public void delete(@NonNull String addressName) {
|
||||
baseStore.delete(addressName);
|
||||
}
|
||||
|
||||
public void invalidate(@NonNull String addressName) {
|
||||
baseStore.invalidate(addressName);
|
||||
}
|
||||
|
||||
public enum SaveResult {
|
||||
NEW,
|
||||
UPDATE,
|
||||
NON_BLOCKING_APPROVAL_REQUIRED,
|
||||
NO_CHANGE
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
import org.whispersystems.libsignal.IdentityKeyPair;
|
||||
@@ -26,16 +25,16 @@ import java.util.UUID;
|
||||
|
||||
public class SignalServiceAccountDataStoreImpl implements SignalServiceAccountDataStore {
|
||||
|
||||
private final Context context;
|
||||
private final TextSecurePreKeyStore preKeyStore;
|
||||
private final TextSecurePreKeyStore signedPreKeyStore;
|
||||
private final TextSecureIdentityKeyStore identityKeyStore;
|
||||
private final TextSecureSessionStore sessionStore;
|
||||
private final SignalSenderKeyStore senderKeyStore;
|
||||
private final Context context;
|
||||
private final TextSecurePreKeyStore preKeyStore;
|
||||
private final TextSecurePreKeyStore signedPreKeyStore;
|
||||
private final SignalIdentityKeyStore identityKeyStore;
|
||||
private final TextSecureSessionStore sessionStore;
|
||||
private final SignalSenderKeyStore senderKeyStore;
|
||||
|
||||
public SignalServiceAccountDataStoreImpl(@NonNull Context context,
|
||||
@NonNull TextSecurePreKeyStore preKeyStore,
|
||||
@NonNull TextSecureIdentityKeyStore identityKeyStore,
|
||||
@NonNull SignalIdentityKeyStore identityKeyStore,
|
||||
@NonNull TextSecureSessionStore sessionStore,
|
||||
@NonNull SignalSenderKeyStore senderKeyStore)
|
||||
{
|
||||
@@ -193,7 +192,7 @@ public class SignalServiceAccountDataStoreImpl implements SignalServiceAccountDa
|
||||
senderKeyStore.clearSenderKeySharedWith(addresses);
|
||||
}
|
||||
|
||||
public @NonNull TextSecureIdentityKeyStore identities() {
|
||||
public @NonNull SignalIdentityKeyStore identities() {
|
||||
return identityKeyStore;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ public final class SignalServiceDataStoreImpl implements SignalServiceDataStore
|
||||
if (accountIdentifier.equals(SignalStore.account().getAci())) {
|
||||
return aciStore;
|
||||
} else if (accountIdentifier.equals(SignalStore.account().getPni())) {
|
||||
return pniStore;
|
||||
throw new AssertionError("Not to be used yet!");
|
||||
} else {
|
||||
throw new IllegalArgumentException("No matching store found for " + accountIdentifier);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import com.annimon.stream.Stream;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.devicelist.Device;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.util.AsyncLoader;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.whispersystems.libsignal.IdentityKeyPair;
|
||||
@@ -77,7 +78,7 @@ public class DeviceListLoader extends AsyncLoader<List<Device>> {
|
||||
throw new IOException("Got a DeviceName that wasn't properly populated.");
|
||||
}
|
||||
|
||||
return new Device(deviceInfo.getId(), new String(decryptName(deviceName, IdentityKeyUtil.getIdentityKeyPair(getContext()))), deviceInfo.getCreated(), deviceInfo.getLastSeen());
|
||||
return new Device(deviceInfo.getId(), new String(decryptName(deviceName, SignalStore.account().getAciIdentityKey())), deviceInfo.getCreated(), deviceInfo.getLastSeen());
|
||||
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed while reading the protobuf.", e);
|
||||
|
||||
@@ -13,10 +13,11 @@ import org.thoughtcrime.securesms.BuildConfig;
|
||||
import org.thoughtcrime.securesms.components.TypingStatusRepository;
|
||||
import org.thoughtcrime.securesms.components.TypingStatusSender;
|
||||
import org.thoughtcrime.securesms.crypto.ReentrantSessionLock;
|
||||
import org.thoughtcrime.securesms.crypto.storage.SignalBaseIdentityKeyStore;
|
||||
import org.thoughtcrime.securesms.crypto.storage.SignalServiceDataStoreImpl;
|
||||
import org.thoughtcrime.securesms.crypto.storage.SignalServiceAccountDataStoreImpl;
|
||||
import org.thoughtcrime.securesms.crypto.storage.SignalSenderKeyStore;
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
|
||||
import org.thoughtcrime.securesms.crypto.storage.SignalIdentityKeyStore;
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecurePreKeyStore;
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
|
||||
import org.thoughtcrime.securesms.database.DatabaseObserver;
|
||||
@@ -75,6 +76,7 @@ import org.whispersystems.signalservice.api.SignalWebSocket;
|
||||
import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
|
||||
import org.whispersystems.signalservice.api.push.ACI;
|
||||
import org.whispersystems.signalservice.api.push.PNI;
|
||||
import org.whispersystems.signalservice.api.services.DonationsService;
|
||||
import org.whispersystems.signalservice.api.util.CredentialsProvider;
|
||||
import org.whispersystems.signalservice.api.util.SleepTimer;
|
||||
@@ -276,12 +278,20 @@ public class ApplicationDependencyProvider implements ApplicationDependencies.Pr
|
||||
|
||||
@Override
|
||||
public @NonNull SignalServiceDataStoreImpl provideProtocolStore() {
|
||||
SignalServiceAccountDataStoreImpl aci = new SignalServiceAccountDataStoreImpl(context,
|
||||
new TextSecurePreKeyStore(context),
|
||||
new TextSecureIdentityKeyStore(context),
|
||||
new TextSecureSessionStore(context),
|
||||
new SignalSenderKeyStore(context));
|
||||
return new SignalServiceDataStoreImpl(context, aci, aci);
|
||||
SignalBaseIdentityKeyStore baseIdentityStore = new SignalBaseIdentityKeyStore(context);
|
||||
|
||||
SignalServiceAccountDataStoreImpl aciStore = new SignalServiceAccountDataStoreImpl(context,
|
||||
new TextSecurePreKeyStore(context),
|
||||
new SignalIdentityKeyStore(baseIdentityStore, () -> SignalStore.account().getAciIdentityKey()),
|
||||
new TextSecureSessionStore(context),
|
||||
new SignalSenderKeyStore(context));
|
||||
|
||||
SignalServiceAccountDataStoreImpl pniStore = new SignalServiceAccountDataStoreImpl(context,
|
||||
new TextSecurePreKeyStore(context),
|
||||
new SignalIdentityKeyStore(baseIdentityStore, () -> SignalStore.account().getPniIdentityKey()),
|
||||
new TextSecureSessionStore(context),
|
||||
new SignalSenderKeyStore(context));
|
||||
return new SignalServiceDataStoreImpl(context, aciStore, pniStore);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -61,7 +61,7 @@ public class CreateSignedPreKeyJob extends BaseJob {
|
||||
}
|
||||
|
||||
SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
|
||||
IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context);
|
||||
IdentityKeyPair identityKeyPair = SignalStore.account().getAciIdentityKey();
|
||||
SignedPreKeyRecord signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(context, identityKeyPair, true);
|
||||
|
||||
accountManager.setSignedPreKey(signedPreKeyRecord);
|
||||
|
||||
@@ -129,7 +129,7 @@ public final class PushDecryptMessageJob extends BaseJob {
|
||||
}
|
||||
|
||||
private boolean needsMigration() {
|
||||
return !IdentityKeyUtil.hasIdentityKey(context) || TextSecurePreferences.getNeedsSqlCipherMigration(context);
|
||||
return TextSecurePreferences.getNeedsSqlCipherMigration(context);
|
||||
}
|
||||
|
||||
private void postMigrationNotification() {
|
||||
|
||||
@@ -98,7 +98,7 @@ public class RefreshAttributesJob extends BaseJob {
|
||||
boolean phoneNumberDiscoverable = SignalStore.phoneNumberPrivacy().getPhoneNumberListingMode().isDiscoverable();
|
||||
|
||||
String deviceName = SignalStore.account().getDeviceName();
|
||||
byte[] encryptedDeviceName = (deviceName == null) ? null : DeviceNameCipher.encryptDeviceName(deviceName.getBytes(StandardCharsets.UTF_8), IdentityKeyUtil.getIdentityKeyPair(context));
|
||||
byte[] encryptedDeviceName = (deviceName == null) ? null : DeviceNameCipher.encryptDeviceName(deviceName.getBytes(StandardCharsets.UTF_8), SignalStore.account().getAciIdentityKey());
|
||||
|
||||
AccountAttributes.Capabilities capabilities = AppCapabilities.getCapabilities(kbsValues.hasPin() && !kbsValues.hasOptedOut());
|
||||
Log.i(TAG, "Calling setAccountAttributes() reglockV1? " + !TextUtils.isEmpty(registrationLockV1) + ", reglockV2? " + !TextUtils.isEmpty(registrationLockV2) + ", pin? " + kbsValues.hasPin() +
|
||||
|
||||
@@ -85,7 +85,7 @@ public class RefreshPreKeysJob extends BaseJob {
|
||||
}
|
||||
|
||||
List<PreKeyRecord> preKeyRecords = PreKeyUtil.generatePreKeys(context);
|
||||
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context);
|
||||
IdentityKeyPair identityKey = SignalStore.account().getAciIdentityKey();
|
||||
SignedPreKeyRecord signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(context, identityKey, false);
|
||||
|
||||
Log.i(TAG, "Registering new prekeys...");
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.libsignal.IdentityKeyPair;
|
||||
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
||||
@@ -53,7 +54,7 @@ public class RotateSignedPreKeyJob extends BaseJob {
|
||||
Log.i(TAG, "Rotating signed prekey...");
|
||||
|
||||
SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
|
||||
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context);
|
||||
IdentityKeyPair identityKey = SignalStore.account().getAciIdentityKey();
|
||||
SignedPreKeyRecord signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(context, identityKey, false);
|
||||
|
||||
accountManager.setSignedPreKey(signedPreKeyRecord);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -168,7 +168,6 @@ public class IncomingMessageProcessor {
|
||||
|
||||
private boolean needsToEnqueueDecryption() {
|
||||
return !jobManager.areQueuesEmpty(SetUtil.newHashSet(Job.Parameters.MIGRATION_QUEUE_KEY, PushDecryptMessageJob.QUEUE)) ||
|
||||
!IdentityKeyUtil.hasIdentityKey(context) ||
|
||||
TextSecurePreferences.getNeedsSqlCipherMigration(context);
|
||||
}
|
||||
|
||||
|
||||
@@ -113,10 +113,6 @@ public class LegacyMigrationJob extends MigrationJob {
|
||||
throw new RetryLaterException();
|
||||
}
|
||||
|
||||
if (lastSeenVersion < CURVE25519_VERSION) {
|
||||
IdentityKeyUtil.migrateIdentityKeys(context, masterSecret);
|
||||
}
|
||||
|
||||
if (lastSeenVersion < NO_V1_VERSION) {
|
||||
File v1sessions = new File(context.getFilesDir(), "sessions");
|
||||
|
||||
@@ -149,7 +145,6 @@ public class LegacyMigrationJob extends MigrationJob {
|
||||
// new TextSecureSessionStore(context, masterSecret).migrateSessions();
|
||||
// new TextSecurePreKeyStore(context, masterSecret).migrateRecords();
|
||||
|
||||
IdentityKeyUtil.migrateIdentityKeys(context, masterSecret);
|
||||
scheduleMessagesInPushDatabase(context);;
|
||||
}
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ public final class RegistrationRepository {
|
||||
PNI pni = PNI.parseOrThrow(response.getPni());
|
||||
boolean hasPin = response.isStorageCapable();
|
||||
|
||||
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context);
|
||||
IdentityKeyPair identityKey = SignalStore.account().getAciIdentityKey();
|
||||
List<PreKeyRecord> records = PreKeyUtil.generatePreKeys(context);
|
||||
SignedPreKeyRecord signedPreKey = PreKeyUtil.generateSignedPreKey(context, identityKey, true);
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.events.CallParticipant;
|
||||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
|
||||
@@ -149,7 +150,7 @@ public class OutgoingCallActionProcessor extends DeviceAwareActionProcessor {
|
||||
|
||||
try {
|
||||
byte[] remoteIdentityKey = WebRtcUtil.getPublicKeyBytes(receivedAnswerMetadata.getRemoteIdentityKey());
|
||||
byte[] localIdentityKey = WebRtcUtil.getPublicKeyBytes(IdentityKeyUtil.getIdentityKey(context).serialize());
|
||||
byte[] localIdentityKey = WebRtcUtil.getPublicKeyBytes(SignalStore.account().getAciIdentityKey().getPublicKey().serialize());
|
||||
|
||||
webRtcInteractor.getCallManager().receivedAnswer(callMetadata.getCallId(), callMetadata.getRemoteDevice(), answerMetadata.getOpaque(), remoteIdentityKey, localIdentityKey);
|
||||
} catch (CallException | InvalidKeyException e) {
|
||||
|
||||
@@ -215,7 +215,7 @@ public abstract class WebRtcActionProcessor {
|
||||
|
||||
try {
|
||||
byte[] remoteIdentityKey = WebRtcUtil.getPublicKeyBytes(receivedOfferMetadata.getRemoteIdentityKey());
|
||||
byte[] localIdentityKey = WebRtcUtil.getPublicKeyBytes(IdentityKeyUtil.getIdentityKey(context).serialize());
|
||||
byte[] localIdentityKey = WebRtcUtil.getPublicKeyBytes(SignalStore.account().getAciIdentityKey().getPublicKey().serialize());
|
||||
|
||||
webRtcInteractor.getCallManager().receivedOffer(callMetadata.getCallId(),
|
||||
callMetadata.getRemotePeer(),
|
||||
|
||||
@@ -10,7 +10,7 @@ import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.crypto.ReentrantSessionLock;
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
|
||||
import org.thoughtcrime.securesms.crypto.storage.SignalIdentityKeyStore;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.database.MessageDatabase;
|
||||
@@ -160,9 +160,9 @@ public final class IdentityUtil {
|
||||
|
||||
public static void processVerifiedMessage(Context context, VerifiedMessage verifiedMessage) {
|
||||
try(SignalSessionLock.Lock unused = ReentrantSessionLock.INSTANCE.acquire()) {
|
||||
TextSecureIdentityKeyStore identityStore = ApplicationDependencies.getProtocolStore().aci().identities();
|
||||
Recipient recipient = Recipient.externalPush(context, verifiedMessage.getDestination());
|
||||
Optional<IdentityRecord> identityRecord = identityStore.getIdentityRecord(recipient.getId());
|
||||
SignalIdentityKeyStore identityStore = ApplicationDependencies.getProtocolStore().aci().identities();
|
||||
Recipient recipient = Recipient.externalPush(context, verifiedMessage.getDestination());
|
||||
Optional<IdentityRecord> identityRecord = identityStore.getIdentityRecord(recipient.getId());
|
||||
|
||||
if (!identityRecord.isPresent() && verifiedMessage.getVerified() == VerifiedMessage.VerifiedState.DEFAULT) {
|
||||
Log.w(TAG, "No existing record for default status");
|
||||
|
||||
@@ -300,7 +300,7 @@ public final class ProfileUtil {
|
||||
if (!SignalStore.paymentsValues().mobileCoinPaymentsEnabled()) {
|
||||
return null;
|
||||
} else {
|
||||
IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(ApplicationDependencies.getApplication());
|
||||
IdentityKeyPair identityKeyPair = SignalStore.account().getAciIdentityKey();
|
||||
MobileCoinPublicAddress publicAddress = ApplicationDependencies.getPayments()
|
||||
.getWallet()
|
||||
.getMobileCoinPublicAddress();
|
||||
|
||||
@@ -8,7 +8,7 @@ import androidx.fragment.app.Fragment
|
||||
import org.signal.core.util.ThreadUtil
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.permissions.Permissions
|
||||
import org.thoughtcrime.securesms.qr.ScanListener
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
@@ -45,7 +45,7 @@ class VerifyIdentityFragment : Fragment(R.layout.fragment_container), ScanListen
|
||||
VerifyDisplayFragment.create(
|
||||
recipientId,
|
||||
remoteIdentity,
|
||||
IdentityKeyParcelable(IdentityKeyUtil.getIdentityKey(requireContext())),
|
||||
IdentityKeyParcelable(SignalStore.account().aciIdentityKey.publicKey),
|
||||
Recipient.self().requireE164(),
|
||||
isVerified
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user