From c702ff676aa6464774da37fb3da132a07d82eb61 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Fri, 8 Nov 2019 15:33:10 -0500 Subject: [PATCH] Support additional sync behavior for linked devices. --- build.gradle | 2 +- .../securesms/CreateProfileActivity.java | 3 + .../crypto/UnidentifiedAccessUtil.java | 5 +- .../securesms/database/RecipientDatabase.java | 47 ++++++++ .../securesms/jobs/JobManagerFactories.java | 2 + .../MultiDeviceProfileContentUpdateJob.java | 74 ++++++++++++ .../securesms/jobs/PushDecryptJob.java | 35 ++++++ .../securesms/jobs/RefreshOwnProfileJob.java | 106 ++++++++++++++++++ .../jobs/RetrieveProfileAvatarJob.java | 5 + .../securesms/jobs/RetrieveProfileJob.java | 60 ++-------- .../recipients/LiveRecipientCache.java | 5 + .../securesms/recipients/Recipient.java | 1 + .../recipients/RecipientDetails.java | 3 +- .../securesms/util/ProfileUtil.java | 96 ++++++++++++++++ .../securesms/util/TextSecurePreferences.java | 8 ++ witness-verifications.gradle | 8 +- 16 files changed, 401 insertions(+), 59 deletions(-) create mode 100644 src/org/thoughtcrime/securesms/jobs/MultiDeviceProfileContentUpdateJob.java create mode 100644 src/org/thoughtcrime/securesms/jobs/RefreshOwnProfileJob.java create mode 100644 src/org/thoughtcrime/securesms/util/ProfileUtil.java diff --git a/build.gradle b/build.gradle index 5bfe1fd42f7..a26858adc6f 100644 --- a/build.gradle +++ b/build.gradle @@ -115,7 +115,7 @@ dependencies { implementation 'org.conscrypt:conscrypt-android:2.0.0' implementation 'org.signal:aesgcmprovider:0.0.3' - implementation 'org.whispersystems:signal-service-android:2.15.1' + implementation 'org.whispersystems:signal-service-android:2.15.3' implementation 'org.signal:ringrtc-android:0.1.9' diff --git a/src/org/thoughtcrime/securesms/CreateProfileActivity.java b/src/org/thoughtcrime/securesms/CreateProfileActivity.java index 8edcee7ce7c..5191d1f9c5c 100644 --- a/src/org/thoughtcrime/securesms/CreateProfileActivity.java +++ b/src/org/thoughtcrime/securesms/CreateProfileActivity.java @@ -39,6 +39,8 @@ import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobs.MultiDeviceProfileKeyUpdateJob; +import org.thoughtcrime.securesms.jobs.MultiDeviceProfileContentUpdateJob; +import org.thoughtcrime.securesms.jobs.RefreshOwnProfileJob; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.permissions.Permissions; @@ -380,6 +382,7 @@ protected Boolean doInBackground(Void... params) { } ApplicationDependencies.getJobManager().add(new MultiDeviceProfileKeyUpdateJob()); + ApplicationDependencies.getJobManager().add(new MultiDeviceProfileContentUpdateJob()); return true; } diff --git a/src/org/thoughtcrime/securesms/crypto/UnidentifiedAccessUtil.java b/src/org/thoughtcrime/securesms/crypto/UnidentifiedAccessUtil.java index 235384b8376..3e704302199 100644 --- a/src/org/thoughtcrime/securesms/crypto/UnidentifiedAccessUtil.java +++ b/src/org/thoughtcrime/securesms/crypto/UnidentifiedAccessUtil.java @@ -48,8 +48,9 @@ public static Optional getAccessFor(@NonNull Context con try { byte[] theirUnidentifiedAccessKey = getTargetUnidentifiedAccessKey(recipient); byte[] ourUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(context); - byte[] ourUnidentifiedAccessCertificate = recipient.resolve().isUuidSupported() ? TextSecurePreferences.getUnidentifiedAccessCertificate(context) - : TextSecurePreferences.getUnidentifiedAccessCertificateLegacy(context) ; + byte[] ourUnidentifiedAccessCertificate = recipient.resolve().isUuidSupported() && Recipient.self().isUuidSupported() + ? TextSecurePreferences.getUnidentifiedAccessCertificate(context) + : TextSecurePreferences.getUnidentifiedAccessCertificateLegacy(context); if (TextSecurePreferences.isUniversalUnidentifiedAccess(context)) { ourUnidentifiedAccessKey = Util.getSecretBytes(16); diff --git a/src/org/thoughtcrime/securesms/database/RecipientDatabase.java b/src/org/thoughtcrime/securesms/database/RecipientDatabase.java index 282f454873a..b5327934306 100644 --- a/src/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/src/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -16,12 +16,15 @@ import org.thoughtcrime.securesms.color.MaterialColor; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.Base64; +import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.util.guava.Optional; +import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.util.UuidUtil; import java.io.Closeable; @@ -762,6 +765,50 @@ public void updateSystemContactColors(@NonNull ColorUpdater updater) { return databaseHelper.getReadableDatabase().query(TABLE_NAME, SEARCH_PROJECTION, selection, args, null, null, orderBy); } + public void applyBlockedUpdate(@NonNull List blocked, List groupIds) { + List blockedE164 = Stream.of(blocked) + .filter(b -> b.getNumber().isPresent()) + .map(b -> b.getNumber().get()) + .toList(); + List blockedUuid = Stream.of(blocked) + .filter(b -> b.getUuid().isPresent()) + .map(b -> b.getUuid().get().toString().toLowerCase()) + .toList(); + + SQLiteDatabase db = databaseHelper.getWritableDatabase(); + + db.beginTransaction(); + try { + ContentValues resetBlocked = new ContentValues(); + resetBlocked.put(BLOCKED, 0); + db.update(TABLE_NAME, resetBlocked, null, null); + + ContentValues setBlocked = new ContentValues(); + setBlocked.put(BLOCKED, 1); + + for (String e164 : blockedE164) { + db.update(TABLE_NAME, setBlocked, PHONE + " = ?", new String[] { e164 }); + } + + for (String uuid : blockedUuid) { + db.update(TABLE_NAME, setBlocked, UUID + " = ?", new String[] { uuid }); + } + + List groupIdStrings = Stream.of(groupIds).map(g -> GroupUtil.getEncodedId(g, false)).toList(); + + for (String groupId : groupIdStrings) { + db.update(TABLE_NAME, setBlocked, GROUP_ID + " = ?", new String[] { groupId }); + } + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + + ApplicationDependencies.getRecipientCache().clear(); + } + + private int update(@NonNull RecipientId id, ContentValues contentValues) { SQLiteDatabase database = databaseHelper.getWritableDatabase(); return database.update(TABLE_NAME, contentValues, ID + " = ?", new String[] { id.serialize() }); diff --git a/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java b/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java index 49874208888..9ba1d777c89 100644 --- a/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java +++ b/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java @@ -51,6 +51,7 @@ public static Map getJobFactories(@NonNull Application appl put(MultiDeviceConfigurationUpdateJob.KEY, new MultiDeviceConfigurationUpdateJob.Factory()); put(MultiDeviceContactUpdateJob.KEY, new MultiDeviceContactUpdateJob.Factory()); put(MultiDeviceGroupUpdateJob.KEY, new MultiDeviceGroupUpdateJob.Factory()); + put(MultiDeviceProfileContentUpdateJob.KEY, new MultiDeviceProfileContentUpdateJob.Factory()); put(MultiDeviceProfileKeyUpdateJob.KEY, new MultiDeviceProfileKeyUpdateJob.Factory()); put(MultiDeviceReadUpdateJob.KEY, new MultiDeviceReadUpdateJob.Factory()); put(MultiDeviceStickerPackOperationJob.KEY, new MultiDeviceStickerPackOperationJob.Factory()); @@ -64,6 +65,7 @@ public static Map getJobFactories(@NonNull Application appl put(PushNotificationReceiveJob.KEY, new PushNotificationReceiveJob.Factory()); put(PushTextSendJob.KEY, new PushTextSendJob.Factory()); put(RefreshAttributesJob.KEY, new RefreshAttributesJob.Factory()); + put(RefreshOwnProfileJob.KEY, new RefreshOwnProfileJob.Factory()); put(RefreshPreKeysJob.KEY, new RefreshPreKeysJob.Factory()); put(RefreshUnidentifiedDeliveryAbilityJob.KEY, new RefreshUnidentifiedDeliveryAbilityJob.Factory()); put(RequestGroupInfoJob.KEY, new RequestGroupInfoJob.Factory()); diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceProfileContentUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceProfileContentUpdateJob.java new file mode 100644 index 00000000000..188cdd64c60 --- /dev/null +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceProfileContentUpdateJob.java @@ -0,0 +1,74 @@ +package org.thoughtcrime.securesms.jobs; + +import androidx.annotation.NonNull; + +import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; +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.logging.Log; +import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.whispersystems.signalservice.api.SignalServiceMessageSender; +import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; +import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; + +public class MultiDeviceProfileContentUpdateJob extends BaseJob { + + public static final String KEY = "MultiDeviceProfileContentUpdateJob"; + + private static final String TAG = Log.tag(MultiDeviceProfileContentUpdateJob.class); + + public MultiDeviceProfileContentUpdateJob() { + this(new Parameters.Builder() + .setQueue("MultiDeviceProfileUpdateJob") + .addConstraint(NetworkConstraint.KEY) + .setMaxAttempts(10) + .build()); + } + + private MultiDeviceProfileContentUpdateJob(@NonNull Parameters parameters) { + super(parameters); + } + + @Override + public @NonNull Data serialize() { + return Data.EMPTY; + } + + @Override + public @NonNull String getFactoryKey() { + return KEY; + } + + @Override + protected void onRun() throws Exception { + if (!TextSecurePreferences.isMultiDevice(context)) { + Log.i(TAG, "Not multi device, aborting..."); + return; + } + + SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender(); + + messageSender.sendMessage(SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.LOCAL_PROFILE), + UnidentifiedAccessUtil.getAccessForSync(context)); + } + + @Override + protected boolean onShouldRetry(@NonNull Exception e) { + return e instanceof PushNetworkException; + } + + + @Override + public void onCanceled() { + Log.w(TAG, "Did not succeed!"); + } + + public static final class Factory implements Job.Factory { + @Override + public @NonNull MultiDeviceProfileContentUpdateJob create(@NonNull Parameters parameters, @NonNull Data data) { + return new MultiDeviceProfileContentUpdateJob(parameters); + } + } +} diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 8db966bec52..57876da4cc6 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -114,6 +114,8 @@ import org.whispersystems.signalservice.api.messages.calls.IceUpdateMessage; import org.whispersystems.signalservice.api.messages.calls.OfferMessage; import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage; +import org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage; +import org.whispersystems.signalservice.api.messages.multidevice.ConfigurationMessage; import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage; import org.whispersystems.signalservice.api.messages.multidevice.RequestMessage; import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage; @@ -268,6 +270,9 @@ private void handleMessage(@NonNull SignalServiceEnvelope envelope, @NonNull Opt else if (syncMessage.getViewOnceOpen().isPresent()) handleSynchronizeViewOnceOpenMessage(syncMessage.getViewOnceOpen().get(), content.getTimestamp()); else if (syncMessage.getVerified().isPresent()) handleSynchronizeVerifiedMessage(syncMessage.getVerified().get()); else if (syncMessage.getStickerPackOperations().isPresent()) handleSynchronizeStickerPackOperation(syncMessage.getStickerPackOperations().get()); + else if (syncMessage.getConfiguration().isPresent()) handleSynchronizeConfigurationMessage(syncMessage.getConfiguration().get()); + else if (syncMessage.getBlockedList().isPresent()) handleSynchronizeBlockedListMessage(syncMessage.getBlockedList().get()); + else if (syncMessage.getFetchType().isPresent()) handleSynchronizeFetchMessage(syncMessage.getFetchType().get()); else Log.w(TAG, "Contains no known sync types..."); } else if (content.getCallMessage().isPresent()) { Log.i(TAG, "Got call message..."); @@ -546,6 +551,36 @@ private void handleSynchronizeStickerPackOperation(@NonNull List { + + @Override + public @NonNull RefreshOwnProfileJob create(@NonNull Parameters parameters, @NonNull Data data) { + return new RefreshOwnProfileJob(parameters); + } + } +} diff --git a/src/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.java b/src/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.java index 168ac1ae74e..f4614037b5a 100644 --- a/src/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.java +++ b/src/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.java @@ -14,6 +14,7 @@ import org.thoughtcrime.securesms.profiles.AvatarHelper; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; +import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.signalservice.api.SignalServiceMessageReceiver; import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; @@ -117,6 +118,10 @@ public void onRun() throws IOException { } database.setProfileAvatar(recipient.getId(), profileAvatar); + + if (recipient.isLocalNumber()) { + TextSecurePreferences.setProfileAvatarId(context, Util.getSecureRandom().nextInt()); + } } @Override diff --git a/src/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java b/src/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java index 7f236ade345..b04c80f5b21 100644 --- a/src/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java +++ b/src/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java @@ -22,6 +22,8 @@ import org.thoughtcrime.securesms.service.IncomingMessageObserver; import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.IdentityUtil; +import org.thoughtcrime.securesms.util.ProfileUtil; +import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.InvalidKeyException; @@ -39,6 +41,10 @@ import java.io.IOException; import java.util.List; +/** + * Retrieves a users profile and sets the appropriate local fields. If fetching the profile of the + * local user, use {@link RefreshOwnProfileJob} instead. + */ public class RetrieveProfileJob extends BaseJob { public static final String KEY = "RetrieveProfileJob"; @@ -94,25 +100,12 @@ private void handleIndividualRecipient(Recipient recipient) throws IOException { } private void handlePhoneNumberRecipient(Recipient recipient) throws IOException { - SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient); - Optional unidentifiedAccess = getUnidentifiedAccess(recipient); + SignalServiceProfile profile = ProfileUtil.retrieveProfile(context, recipient); - SignalServiceProfile profile; - - try { - profile = retrieveProfile(address, unidentifiedAccess); - } catch (NonSuccessfulResponseCodeException e) { - if (unidentifiedAccess.isPresent()) { - profile = retrieveProfile(address, Optional.absent()); - } else { - throw e; - } - } - - setIdentityKey(recipient, profile.getIdentityKey()); setProfileName(recipient, profile.getName()); setProfileAvatar(recipient, profile.getAvatar()); setProfileCapabilities(recipient, profile.getCapabilities()); + setIdentityKey(recipient, profile.getIdentityKey()); setUnidentifiedAccessMode(recipient, profile.getUnidentifiedAccess(), profile.isUnrestrictedUnidentifiedAccess()); } @@ -124,26 +117,6 @@ private void handleGroupRecipient(Recipient group) throws IOException { } } - private SignalServiceProfile retrieveProfile(@NonNull SignalServiceAddress address, Optional unidentifiedAccess) - throws IOException - { - SignalServiceMessagePipe authPipe = IncomingMessageObserver.getPipe(); - SignalServiceMessagePipe unidentifiedPipe = IncomingMessageObserver.getUnidentifiedPipe(); - SignalServiceMessagePipe pipe = unidentifiedPipe != null && unidentifiedAccess.isPresent() ? unidentifiedPipe - : authPipe; - - if (pipe != null) { - try { - return pipe.getProfile(address, unidentifiedAccess); - } catch (IOException e) { - Log.w(TAG, e); - } - } - - SignalServiceMessageReceiver receiver = ApplicationDependencies.getSignalServiceMessageReceiver(); - return receiver.retrieveProfile(address, unidentifiedAccess); - } - private void setIdentityKey(Recipient recipient, String identityKeyValue) { try { if (TextUtils.isEmpty(identityKeyValue)) { @@ -206,12 +179,7 @@ private void setProfileName(Recipient recipient, String profileName) { byte[] profileKey = recipient.getProfileKey(); if (profileKey == null) return; - String plaintextProfileName = null; - - if (profileName != null) { - ProfileCipher profileCipher = new ProfileCipher(profileKey); - plaintextProfileName = new String(profileCipher.decryptName(Base64.decode(profileName))); - } + String plaintextProfileName = ProfileUtil.decryptName(profileKey, profileName); if (!Util.equals(plaintextProfileName, recipient.getProfileName())) { DatabaseFactory.getRecipientDatabase(context).setProfileName(recipient.getId(), plaintextProfileName); @@ -237,16 +205,6 @@ private void setProfileCapabilities(@NonNull Recipient recipient, @Nullable Sign DatabaseFactory.getRecipientDatabase(context).setUuidSupported(recipient.getId(), capabilities.isUuid()); } - private Optional getUnidentifiedAccess(@NonNull Recipient recipient) { - Optional unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient); - - if (unidentifiedAccess.isPresent()) { - return unidentifiedAccess.get().getTargetUnidentifiedAccess(); - } - - return Optional.absent(); - } - public static final class Factory implements Job.Factory { @Override diff --git a/src/org/thoughtcrime/securesms/recipients/LiveRecipientCache.java b/src/org/thoughtcrime/securesms/recipients/LiveRecipientCache.java index 2c409a73eac..3c51b71dff8 100644 --- a/src/org/thoughtcrime/securesms/recipients/LiveRecipientCache.java +++ b/src/org/thoughtcrime/securesms/recipients/LiveRecipientCache.java @@ -126,4 +126,9 @@ public synchronized void warmUp() { } }); } + + @AnyThread + public synchronized void clear() { + recipients.clear(); + } } diff --git a/src/org/thoughtcrime/securesms/recipients/Recipient.java b/src/org/thoughtcrime/securesms/recipients/Recipient.java index 2bdfe8d5642..e5717dc43bb 100644 --- a/src/org/thoughtcrime/securesms/recipients/Recipient.java +++ b/src/org/thoughtcrime/securesms/recipients/Recipient.java @@ -34,6 +34,7 @@ import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter; import org.thoughtcrime.securesms.util.FeatureFlags; import org.thoughtcrime.securesms.util.GroupUtil; +import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Preconditions; diff --git a/src/org/thoughtcrime/securesms/recipients/RecipientDetails.java b/src/org/thoughtcrime/securesms/recipients/RecipientDetails.java index 767ca1c4580..86dee24c31a 100644 --- a/src/org/thoughtcrime/securesms/recipients/RecipientDetails.java +++ b/src/org/thoughtcrime/securesms/recipients/RecipientDetails.java @@ -8,6 +8,7 @@ import org.thoughtcrime.securesms.color.MaterialColor; import org.thoughtcrime.securesms.database.RecipientDatabase.InsightsBannerTier; +import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings; import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState; import org.thoughtcrime.securesms.database.RecipientDatabase.UnidentifiedAccessMode; @@ -82,7 +83,7 @@ public class RecipientDetails { this.profileName = isLocalNumber ? TextSecurePreferences.getProfileName(context) : settings.getProfileName(); this.defaultSubscriptionId = settings.getDefaultSubscriptionId(); this.registered = settings.getRegistered(); - this.profileKey = settings.getProfileKey(); + this.profileKey = isLocalNumber ? ProfileKeyUtil.getProfileKey(context) : settings.getProfileKey(); this.profileAvatar = settings.getProfileAvatar(); this.profileSharing = settings.isProfileSharing(); this.systemContact = systemContact; diff --git a/src/org/thoughtcrime/securesms/util/ProfileUtil.java b/src/org/thoughtcrime/securesms/util/ProfileUtil.java new file mode 100644 index 00000000000..667194e97b4 --- /dev/null +++ b/src/org/thoughtcrime/securesms/util/ProfileUtil.java @@ -0,0 +1,96 @@ +package org.thoughtcrime.securesms.util; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.WorkerThread; + +import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientUtil; +import org.thoughtcrime.securesms.service.IncomingMessageObserver; +import org.whispersystems.libsignal.util.guava.Optional; +import org.whispersystems.signalservice.api.SignalServiceMessagePipe; +import org.whispersystems.signalservice.api.SignalServiceMessageReceiver; +import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException; +import org.whispersystems.signalservice.api.crypto.ProfileCipher; +import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess; +import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair; +import org.whispersystems.signalservice.api.profiles.SignalServiceProfile; +import org.whispersystems.signalservice.api.push.SignalServiceAddress; +import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; + +import java.io.IOException; + +/** + * Aids in the retrieval and decryption of profiles. + */ +public class ProfileUtil { + + private static final String TAG = Log.tag(ProfileUtil.class); + + @WorkerThread + public static SignalServiceProfile retrieveProfile(@NonNull Context context, @NonNull Recipient recipient) throws IOException { + SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient); + Optional unidentifiedAccess = getUnidentifiedAccess(context, recipient); + + SignalServiceProfile profile; + + try { + profile = retrieveProfileInternal(address, unidentifiedAccess); + } catch (NonSuccessfulResponseCodeException e) { + if (unidentifiedAccess.isPresent()) { + profile = retrieveProfileInternal(address, Optional.absent()); + } else { + throw e; + } + } + + return profile; + } + + public static @Nullable String decryptName(@NonNull byte[] profileKey, @Nullable String encryptedName) + throws InvalidCiphertextException, IOException + { + if (encryptedName == null) { + return null; + } + + ProfileCipher profileCipher = new ProfileCipher(profileKey); + return new String(profileCipher.decryptName(Base64.decode(encryptedName))); + } + + @WorkerThread + private static SignalServiceProfile retrieveProfileInternal(@NonNull SignalServiceAddress address, Optional unidentifiedAccess) + throws IOException + { + SignalServiceMessagePipe authPipe = IncomingMessageObserver.getPipe(); + SignalServiceMessagePipe unidentifiedPipe = IncomingMessageObserver.getUnidentifiedPipe(); + SignalServiceMessagePipe pipe = unidentifiedPipe != null && unidentifiedAccess.isPresent() ? unidentifiedPipe + : authPipe; + + if (pipe != null) { + try { + return pipe.getProfile(address, unidentifiedAccess); + } catch (IOException e) { + Log.w(TAG, e); + } + } + + SignalServiceMessageReceiver receiver = ApplicationDependencies.getSignalServiceMessageReceiver(); + return receiver.retrieveProfile(address, unidentifiedAccess); + } + + private static Optional getUnidentifiedAccess(@NonNull Context context, @NonNull Recipient recipient) { + Optional unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient); + + if (unidentifiedAccess.isPresent()) { + return unidentifiedAccess.get().getTargetUnidentifiedAccess(); + } + + return Optional.absent(); + } +} diff --git a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java index 58157b00938..6828db096be 100644 --- a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java +++ b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java @@ -386,6 +386,10 @@ public static boolean isLinkPreviewsEnabled(Context context) { return getBooleanPreference(context, LINK_PREVIEWS, true); } + public static void setLinkPreviewsEnabled(Context context, boolean enabled) { + setBooleanPreference(context, LINK_PREVIEWS, enabled); + } + public static boolean isGifSearchInGridLayout(Context context) { return getBooleanPreference(context, GIF_GRID_LAYOUT, false); } @@ -607,6 +611,10 @@ public static boolean isUniversalUnidentifiedAccess(Context context) { return getBooleanPreference(context, UNIVERSAL_UNIDENTIFIED_ACCESS, false); } + public static void setShowUnidentifiedDeliveryIndicatorsEnabled(Context context, boolean enabled) { + setBooleanPreference(context, SHOW_UNIDENTIFIED_DELIVERY_INDICATORS, enabled); + } + public static boolean isShowUnidentifiedDeliveryIndicatorsEnabled(Context context) { return getBooleanPreference(context, SHOW_UNIDENTIFIED_DELIVERY_INDICATORS, false); } diff --git a/witness-verifications.gradle b/witness-verifications.gradle index 4df479e8339..3f1a44bd375 100644 --- a/witness-verifications.gradle +++ b/witness-verifications.gradle @@ -375,11 +375,11 @@ dependencyVerification { ['org.whispersystems:signal-protocol-java:2.8.1', 'b19db36839ab008fdccefc7f8c005f2ea43dc7c7298a209bc424e6f9b6d5617b'], - ['org.whispersystems:signal-service-android:2.15.1', - '27c6cf63393a2d466474e45162ec254c557f6a19e91962d0e4acc870373c41fd'], + ['org.whispersystems:signal-service-android:2.15.3', + '24e7a7760f14a261d44b8d76fe6004157f7e09d7898c88ddb501ceabea47b6f6'], - ['org.whispersystems:signal-service-java:2.15.1', - 'd0023125e232b11ead150bbf38361fa61a3dbf3adc81c2473c673735380db9c2'], + ['org.whispersystems:signal-service-java:2.15.3', + 'bc6d58924daf7c15e700164ce602e2647260c41820a43837f3f48f3d5be2e963'], ['pl.tajchert:waitingdots:0.1.0', '2835d49e0787dbcb606c5a60021ced66578503b1e9fddcd7a5ef0cd5f095ba2c'],