From f10d1eac61d418acee92356c35b1180974cffd90 Mon Sep 17 00:00:00 2001 From: Alan Evans Date: Mon, 10 Feb 2020 10:06:12 -0500 Subject: [PATCH] Migrate profile key into database. --- .../securesms/crypto/ProfileKeyUtil.java | 39 +++++++------------ .../crypto/UnidentifiedAccessUtil.java | 8 ++-- .../securesms/database/RecipientDatabase.java | 2 +- .../database/helpers/SQLCipherOpenHelper.java | 21 +++++++++- .../jobs/MultiDeviceContactUpdateJob.java | 14 +++---- .../securesms/jobs/RefreshAttributesJob.java | 3 +- .../securesms/jobs/RotateProfileKeyJob.java | 11 ++++-- .../recipients/RecipientDetails.java | 5 +-- .../service/CodeVerificationRequest.java | 24 +++++++++++- .../thoughtcrime/securesms/util/Base64.java | 8 ++++ .../securesms/util/TextSecurePreferences.java | 9 ----- 11 files changed, 87 insertions(+), 57 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/crypto/ProfileKeyUtil.java b/app/src/main/java/org/thoughtcrime/securesms/crypto/ProfileKeyUtil.java index 6879fdfffd..9a4ede3c5a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/crypto/ProfileKeyUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/crypto/ProfileKeyUtil.java @@ -1,38 +1,25 @@ package org.thoughtcrime.securesms.crypto; - import android.content.Context; + import androidx.annotation.NonNull; -import org.thoughtcrime.securesms.util.Base64; -import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.thoughtcrime.securesms.util.Util; +import org.thoughtcrime.securesms.recipients.Recipient; -import java.io.IOException; +public final class ProfileKeyUtil { -public class ProfileKeyUtil { - - public static synchronized boolean hasProfileKey(@NonNull Context context) { - return TextSecurePreferences.getProfileKey(context) != null; + private ProfileKeyUtil() { } - public static synchronized @NonNull byte[] getProfileKey(@NonNull Context context) { - try { - String encodedProfileKey = TextSecurePreferences.getProfileKey(context); - - if (encodedProfileKey == null) { - encodedProfileKey = Util.getSecret(32); - TextSecurePreferences.setProfileKey(context, encodedProfileKey); - } - - return Base64.decode(encodedProfileKey); - } catch (IOException e) { - throw new AssertionError(e); + /** + * @deprecated Will inline later as part of Versioned profiles. + */ + @Deprecated + public static @NonNull byte[] getProfileKey(@NonNull Context context) { + byte[] profileKey = Recipient.self().getProfileKey(); + if (profileKey == null) { + throw new AssertionError(); } - } - - public static synchronized @NonNull byte[] rotateProfileKey(@NonNull Context context) { - TextSecurePreferences.setProfileKey(context, null); - return getProfileKey(context); + return profileKey; } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/crypto/UnidentifiedAccessUtil.java b/app/src/main/java/org/thoughtcrime/securesms/crypto/UnidentifiedAccessUtil.java index 611abfeda4..b964232590 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/crypto/UnidentifiedAccessUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/crypto/UnidentifiedAccessUtil.java @@ -42,7 +42,7 @@ public class UnidentifiedAccessUtil { { try { byte[] theirUnidentifiedAccessKey = getTargetUnidentifiedAccessKey(recipient); - byte[] ourUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(context); + byte[] ourUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(ProfileKeyUtil.getProfileKey(context)); byte[] ourUnidentifiedAccessCertificate = recipient.resolve().isUuidSupported() && Recipient.self().isUuidSupported() ? TextSecurePreferences.getUnidentifiedAccessCertificate(context) : TextSecurePreferences.getUnidentifiedAccessCertificateLegacy(context); @@ -75,7 +75,7 @@ public class UnidentifiedAccessUtil { public static Optional getAccessForSync(@NonNull Context context) { try { - byte[] ourUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(context); + byte[] ourUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(ProfileKeyUtil.getProfileKey(context)); byte[] ourUnidentifiedAccessCertificate = Recipient.self().isUuidSupported() ? TextSecurePreferences.getUnidentifiedAccessCertificate(context) : TextSecurePreferences.getUnidentifiedAccessCertificateLegacy(context); @@ -97,8 +97,8 @@ public class UnidentifiedAccessUtil { } } - public static @NonNull byte[] getSelfUnidentifiedAccessKey(@NonNull Context context) { - return UnidentifiedAccess.deriveAccessKeyFrom(ProfileKeyUtil.getProfileKey(context)); + public static @NonNull byte[] getSelfUnidentifiedAccessKey(@NonNull byte[] selfProfileKey) { + return UnidentifiedAccess.deriveAccessKeyFrom(selfProfileKey); } private static @Nullable byte[] getTargetUnidentifiedAccessKey(@NonNull Recipient recipient) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java index 27a351c57c..681b13cded 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -34,8 +34,8 @@ import org.whispersystems.libsignal.InvalidKeyException; import org.whispersystems.libsignal.util.Pair; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.push.SignalServiceAddress; -import org.whispersystems.signalservice.api.util.UuidUtil; import org.whispersystems.signalservice.api.storage.SignalContactRecord; +import org.whispersystems.signalservice.api.util.UuidUtil; import java.io.Closeable; import java.io.IOException; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index 44055c11f9..24a487136b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -9,6 +9,7 @@ import android.database.Cursor; import android.net.Uri; import android.os.Build; import android.os.SystemClock; +import android.preference.PreferenceManager; import android.text.TextUtils; import androidx.annotation.NonNull; @@ -47,10 +48,12 @@ import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter; import org.thoughtcrime.securesms.service.KeyCachingService; +import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.ServiceUtil; import org.thoughtcrime.securesms.util.SqlUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.thoughtcrime.securesms.util.Util; import java.io.File; import java.util.List; @@ -105,8 +108,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { private static final int STICKER_PACK_ORDER = 44; private static final int MEGAPHONES = 45; private static final int MEGAPHONE_FIRST_APPEARANCE = 46; + private static final int PROFILE_KEY_TO_DB = 47; - private static final int DATABASE_VERSION = 46; + private static final int DATABASE_VERSION = 47; private static final String DATABASE_NAME = "signal.db"; private final Context context; @@ -724,6 +728,21 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { db.execSQL("ALTER TABLE megaphone ADD COLUMN first_visible INTEGER DEFAULT 0"); } + if (oldVersion < PROFILE_KEY_TO_DB) { + String localNumber = TextSecurePreferences.getLocalNumber(context); + if (!TextUtils.isEmpty(localNumber)) { + String encodedProfileKey = PreferenceManager.getDefaultSharedPreferences(context).getString("pref_profile_key", null); + byte[] profileKey = encodedProfileKey != null ? Base64.decodeOrThrow(encodedProfileKey) : Util.getSecretBytes(32); + ContentValues values = new ContentValues(1); + + values.put("profile_key", Base64.encodeBytes(profileKey)); + + if (db.update("recipient", values, "phone = ?", new String[]{localNumber}) == 0) { + throw new AssertionError("No rows updated!"); + } + } + } + db.setTransactionSuccessful(); } finally { db.endTransaction(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java index b9c9870691..828402a71a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java @@ -4,15 +4,14 @@ import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.net.Uri; import android.provider.ContactsContract; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import org.thoughtcrime.securesms.ApplicationContext; -import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.IdentityDatabase; -import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; @@ -42,8 +41,6 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -201,14 +198,17 @@ public class MultiDeviceContactUpdateJob extends BaseJob { archived.contains(recipient.getId()))); } - if (ProfileKeyUtil.hasProfileKey(context)) { - Recipient self = Recipient.self(); + + Recipient self = Recipient.self(); + byte[] profileKey = self.getProfileKey(); + + if (profileKey != null) { out.write(new DeviceContact(RecipientUtil.toSignalServiceAddress(context, self), Optional.absent(), Optional.absent(), Optional.of(self.getColor().serialize()), Optional.absent(), - Optional.of(ProfileKeyUtil.getProfileKey(context)), + Optional.of(profileKey), false, self.getExpireMessages() > 0 ? Optional.of(self.getExpireMessages()) : Optional.absent(), Optional.fromNullable(inboxPositions.get(self.getId())), diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java index 37afc24362..86eda9fb62 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.jobs; import androidx.annotation.NonNull; +import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobmanager.Data; @@ -47,7 +48,7 @@ public class RefreshAttributesJob extends BaseJob { public void onRun() throws IOException { int registrationId = TextSecurePreferences.getLocalRegistrationId(context); boolean fetchesMessages = TextSecurePreferences.isFcmDisabled(context); - byte[] unidentifiedAccessKey = UnidentifiedAccessUtil.getSelfUnidentifiedAccessKey(context); + byte[] unidentifiedAccessKey = UnidentifiedAccessUtil.getSelfUnidentifiedAccessKey(ProfileKeyUtil.getProfileKey(context)); boolean universalUnidentifiedAccess = TextSecurePreferences.isUniversalUnidentifiedAccess(context); String pin = null; String registrationLockToken = null; diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RotateProfileKeyJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/RotateProfileKeyJob.java index 9b6dc73614..aed70f6b77 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RotateProfileKeyJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/RotateProfileKeyJob.java @@ -3,7 +3,8 @@ package org.thoughtcrime.securesms.jobs; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; @@ -14,6 +15,7 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.signalservice.api.SignalServiceAccountManager; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; import org.whispersystems.signalservice.api.util.StreamDetails; +import org.whispersystems.signalservice.internal.util.Util; import java.io.File; import java.io.FileInputStream; @@ -48,9 +50,12 @@ public class RotateProfileKeyJob extends BaseJob { @Override public void onRun() throws Exception { - SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager(); - byte[] profileKey = ProfileKeyUtil.rotateProfileKey(context); + SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager(); + RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context); + byte[] profileKey = Util.getSecretBytes(32); + Recipient self = Recipient.self(); + recipientDatabase.setProfileKey(self.getId(), profileKey); accountManager.setProfileName(profileKey, TextSecurePreferences.getProfileName(context).serialize()); accountManager.setProfileAvatar(profileKey, getProfileAvatar()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java index 45765bd70d..a48eded9c0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java @@ -7,9 +7,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import org.thoughtcrime.securesms.color.MaterialColor; -import org.thoughtcrime.securesms.database.RecipientDatabase.InsightsBannerTier; -import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus; +import org.thoughtcrime.securesms.database.RecipientDatabase.InsightsBannerTier; import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings; import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState; import org.thoughtcrime.securesms.database.RecipientDatabase.UnidentifiedAccessMode; @@ -90,7 +89,7 @@ public class RecipientDetails { this.profileName = isLocalNumber ? TextSecurePreferences.getProfileName(context) : settings.getProfileName(); this.defaultSubscriptionId = settings.getDefaultSubscriptionId(); this.registered = settings.getRegistered(); - this.profileKey = isLocalNumber ? ProfileKeyUtil.getProfileKey(context) : settings.getProfileKey(); + this.profileKey = settings.getProfileKey(); this.profileAvatar = settings.getProfileAvatar(); this.profileSharing = settings.isProfileSharing(); this.systemContact = systemContact; diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/service/CodeVerificationRequest.java b/app/src/main/java/org/thoughtcrime/securesms/registration/service/CodeVerificationRequest.java index ed1621ef9f..b19005e3c8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/service/CodeVerificationRequest.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/service/CodeVerificationRequest.java @@ -22,12 +22,13 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.lock.PinHashing; import org.thoughtcrime.securesms.lock.RegistrationLockReminders; import org.thoughtcrime.securesms.logging.Log; -import org.thoughtcrime.securesms.migrations.RegistrationPinV2MigrationJob; import org.thoughtcrime.securesms.push.AccountManagerFactory; +import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.service.DirectoryRefreshListener; import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener; import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.IdentityKeyPair; import org.whispersystems.libsignal.state.PreKeyRecord; import org.whispersystems.libsignal.state.SignedPreKeyRecord; @@ -192,8 +193,15 @@ public final class CodeVerificationRequest { { boolean isV2KbsPin = kbsTokenResponse != null; int registrationId = KeyHelper.generateRegistrationId(false); - byte[] unidentifiedAccessKey = UnidentifiedAccessUtil.getSelfUnidentifiedAccessKey(context); boolean universalUnidentifiedAccess = TextSecurePreferences.isUniversalUnidentifiedAccess(context); + byte[] profileKey = findExistingProfileKey(context, credentials.getE164number()); + + if (profileKey == null) { + profileKey = Util.getSecretBytes(32); + Log.i(TAG, "No profile key found, created a new one"); + } + + byte[] unidentifiedAccessKey = UnidentifiedAccessUtil.getSelfUnidentifiedAccessKey(profileKey); TextSecurePreferences.setLocalRegistrationId(context, registrationId); SessionUtil.archiveAllSessions(context); @@ -227,6 +235,7 @@ public final class CodeVerificationRequest { TextSecurePreferences.setLocalNumber(context, credentials.getE164number()); TextSecurePreferences.setLocalUuid(context, uuid); + recipientDatabase.setProfileKey(selfId, profileKey); ApplicationDependencies.getRecipientCache().clearSelf(); TextSecurePreferences.setFcmToken(context, fcmToken); @@ -260,6 +269,17 @@ public final class CodeVerificationRequest { } } + private static @Nullable byte[] findExistingProfileKey(@NonNull Context context, @NonNull String e164number) { + RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context); + Optional recipient = recipientDatabase.getByE164(e164number); + + if (recipient.isPresent()) { + return Recipient.resolved(recipient.get()).getProfileKey(); + } + + return null; + } + private static void repostPinToResetTries(@NonNull Context context, @Nullable String pin, @NonNull RegistrationLockData kbsData) { if (pin == null) return; diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/Base64.java b/app/src/main/java/org/thoughtcrime/securesms/util/Base64.java index 22ab8585d7..2ad8aed799 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/Base64.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/Base64.java @@ -16,4 +16,12 @@ public final class Base64 { public static @NonNull String encodeBytes(@NonNull byte[] source) { return org.whispersystems.util.Base64.encodeBytes(source); } + + public static @NonNull byte[] decodeOrThrow(@NonNull String s) { + try { + return org.whispersystems.util.Base64.decode(s); + } catch (IOException e) { + throw new AssertionError(); + } + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java b/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java index 95eacd4988..80fa0c1c2f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java @@ -123,7 +123,6 @@ public class TextSecurePreferences { private static final String MULTI_DEVICE_PROVISIONED_PREF = "pref_multi_device"; public static final String DIRECT_CAPTURE_CAMERA_ID = "pref_direct_capture_camera_id"; private static final String ALWAYS_RELAY_CALLS_PREF = "pref_turn_only"; - private static final String PROFILE_KEY_PREF = "pref_profile_key"; private static final String PROFILE_NAME_PREF = "pref_profile_name"; private static final String PROFILE_AVATAR_ID_PREF = "pref_profile_avatar_id"; public static final String READ_RECEIPTS_PREF = "pref_read_receipts"; @@ -435,14 +434,6 @@ public class TextSecurePreferences { setBooleanPreference(context, GIF_GRID_LAYOUT, isGrid); } - public static @Nullable String getProfileKey(Context context) { - return getStringPreference(context, PROFILE_KEY_PREF, null); - } - - public static void setProfileKey(Context context, String key) { - setStringPreference(context, PROFILE_KEY_PREF, key); - } - public static void setProfileName(Context context, ProfileName name) { setStringPreference(context, PROFILE_NAME_PREF, name.serialize()); }