diff --git a/build.gradle b/build.gradle index 14b2330e55..40174b803f 100644 --- a/build.gradle +++ b/build.gradle @@ -63,7 +63,7 @@ dependencies { compile 'org.whispersystems:jobmanager:1.0.2' compile 'org.whispersystems:libpastelog:1.0.7' - compile 'org.whispersystems:signal-service-android:2.6.1' + compile 'org.whispersystems:signal-service-android:2.6.3' compile 'org.whispersystems:webrtc-android:M59-S1' compile "me.leolin:ShortcutBadger:1.1.16" @@ -138,7 +138,7 @@ dependencyVerification { 'com.google.android.exoplayer:exoplayer:955085aa611a8f7cf6c61b88ae03d1a392f4ad94c9bfbc153f3dedb9ffb14718', 'org.whispersystems:jobmanager:506f679fc2fcf7bb6d10f00f41d6f6ea0abf75c70dc95b913398661ad538a181', 'org.whispersystems:libpastelog:bb331d9a98240fc139101128ba836c1edec3c40e000597cdbb29ebf4cbf34d88', - 'org.whispersystems:signal-service-android:9458c3f05698863f3abacbbcb3dfcbb6e2de26e4b1869e25baccebc1e140c97f', + 'org.whispersystems:signal-service-android:5c61299dd731bfed19ced2a080f462cd8423dc3d8a933c940da030954b408a9d', 'org.whispersystems:webrtc-android:de647643afbbea45a26a4f24db75aa10bc8de45426e8eb0d9d563cc10af4f582', 'me.leolin:ShortcutBadger:e3cb3e7625892129b0c92dd5e4bc649faffdd526d5af26d9c45ee31ff8851774', 'se.emilsjolander:stickylistheaders:a08ca948aa6b220f09d82f16bbbac395f6b78897e9eeac6a9f0b0ba755928eeb', @@ -173,7 +173,7 @@ dependencyVerification { 'com.google.android.gms:play-services-basement:95dd882c5ffba15b9a99de3fefb05d3a01946623af67454ca00055d222f85a8d', 'com.google.android.gms:play-services-iid:54e919f9957b8b7820da7ee9b83471d00d0cac1cf08ddea8b5b41aea80bb1a70', 'org.whispersystems:signal-protocol-android:5b8acded7f2a40178eb90ab8e8cbfec89d170d91b3ff5e78487d1098df6185a1', - 'org.whispersystems:signal-service-java:8c72b0055ed01fd98430105c872e93c9af7ce18197cd33566ca9ab561e3102be', + 'org.whispersystems:signal-service-java:5ffd2688335badcc2af178f8214a485ef087e5b3247bfa6b4e0867d525f3fe75', 'com.nineoldandroids:library:68025a14e3e7673d6ad2f95e4b46d78d7d068343aa99256b686fe59de1b3163a', 'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff', 'com.klinkerapps:logger:177e325259a8b111ad6745ec10db5861723c99f402222b80629f576f49408541', diff --git a/src/org/thoughtcrime/securesms/CreateProfileActivity.java b/src/org/thoughtcrime/securesms/CreateProfileActivity.java index 502cae79e3..857c23d80e 100644 --- a/src/org/thoughtcrime/securesms/CreateProfileActivity.java +++ b/src/org/thoughtcrime/securesms/CreateProfileActivity.java @@ -34,8 +34,10 @@ import org.thoughtcrime.securesms.contacts.avatars.BitmapContactPhoto; import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto; import org.thoughtcrime.securesms.contacts.avatars.ContactPhotoFactory; import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.dependencies.InjectableType; +import org.thoughtcrime.securesms.jobs.MultiDeviceProfileKeyUpdateJob; import org.thoughtcrime.securesms.profiles.AvatarHelper; import org.thoughtcrime.securesms.profiles.AvatarPhotoUriLoader; import org.thoughtcrime.securesms.profiles.ProfileMediaConstraints; @@ -344,15 +346,10 @@ public class CreateProfileActivity extends PassphraseRequiredActionBarActivity i { @Override protected Boolean doInBackground(Void... params) { - String encodedProfileKey = TextSecurePreferences.getProfileKey(CreateProfileActivity.this); - - if (encodedProfileKey == null) { - encodedProfileKey = Util.getSecret(32); - TextSecurePreferences.setProfileKey(CreateProfileActivity.this, encodedProfileKey); - } + byte[] profileKey = ProfileKeyUtil.getProfileKey(CreateProfileActivity.this); try { - accountManager.setProfileName(Base64.decode(encodedProfileKey), name); + accountManager.setProfileName(profileKey, name); TextSecurePreferences.setProfileName(getContext(), name); } catch (IOException e) { Log.w(TAG, e); @@ -360,13 +357,15 @@ public class CreateProfileActivity extends PassphraseRequiredActionBarActivity i } try { - accountManager.setProfileAvatar(Base64.decode(encodedProfileKey), avatar); + accountManager.setProfileAvatar(profileKey, avatar); AvatarHelper.setAvatar(getContext(), Address.fromSerialized(TextSecurePreferences.getLocalNumber(getContext())), avatarBytes); } catch (IOException e) { Log.w(TAG, e); return false; } + ApplicationContext.getInstance(getContext()).getJobManager().add(new MultiDeviceProfileKeyUpdateJob(getContext())); + return true; } diff --git a/src/org/thoughtcrime/securesms/DeviceActivity.java b/src/org/thoughtcrime/securesms/DeviceActivity.java index 2cea6f5f3d..c3be6c03a4 100644 --- a/src/org/thoughtcrime/securesms/DeviceActivity.java +++ b/src/org/thoughtcrime/securesms/DeviceActivity.java @@ -16,18 +16,20 @@ import android.widget.Toast; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; import org.thoughtcrime.securesms.push.AccountManagerFactory; import org.thoughtcrime.securesms.qr.ScanListener; import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicTheme; -import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; +import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask; import org.whispersystems.libsignal.IdentityKeyPair; import org.whispersystems.libsignal.InvalidKeyException; import org.whispersystems.libsignal.ecc.Curve; import org.whispersystems.libsignal.ecc.ECPublicKey; +import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceAccountManager; import org.whispersystems.signalservice.api.push.exceptions.NotFoundException; import org.whispersystems.signalservice.internal.push.DeviceLimitExceededException; @@ -156,10 +158,11 @@ public class DeviceActivity extends PassphraseRequiredActionBarActivity return BAD_CODE; } - ECPublicKey publicKey = Curve.decodePoint(Base64.decode(publicKeyEncoded), 0); - IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context); + ECPublicKey publicKey = Curve.decodePoint(Base64.decode(publicKeyEncoded), 0); + IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context); + Optional profileKey = Optional.of(ProfileKeyUtil.getProfileKey(getContext())); - accountManager.addDevice(ephemeralId, publicKey, identityKeyPair, verificationCode); + accountManager.addDevice(ephemeralId, publicKey, identityKeyPair, profileKey, verificationCode); TextSecurePreferences.setMultiDevice(context, true); return SUCCESS; } catch (NotFoundException e) { diff --git a/src/org/thoughtcrime/securesms/crypto/ProfileKeyUtil.java b/src/org/thoughtcrime/securesms/crypto/ProfileKeyUtil.java new file mode 100644 index 0000000000..0f2baffb9e --- /dev/null +++ b/src/org/thoughtcrime/securesms/crypto/ProfileKeyUtil.java @@ -0,0 +1,30 @@ +package org.thoughtcrime.securesms.crypto; + + +import android.content.Context; +import android.support.annotation.NonNull; + +import org.thoughtcrime.securesms.util.Base64; +import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.thoughtcrime.securesms.util.Util; + +import java.io.IOException; + +public class 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); + } + } + +} diff --git a/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java b/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java index 68c9c91260..2271bffb18 100644 --- a/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java +++ b/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java @@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.jobs.GcmRefreshJob; import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob; import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob; import org.thoughtcrime.securesms.jobs.MultiDeviceGroupUpdateJob; +import org.thoughtcrime.securesms.jobs.MultiDeviceProfileKeyUpdateJob; import org.thoughtcrime.securesms.jobs.MultiDeviceReadUpdateJob; import org.thoughtcrime.securesms.jobs.MultiDeviceVerifiedUpdateJob; import org.thoughtcrime.securesms.jobs.PushGroupSendJob; @@ -67,7 +68,8 @@ import dagger.Provides; RetrieveProfileJob.class, MultiDeviceVerifiedUpdateJob.class, CreateProfileActivity.class, - RetrieveProfileAvatarJob.class}) + RetrieveProfileAvatarJob.class, + MultiDeviceProfileKeyUpdateJob.class}) public class SignalCommunicationModule { private final Context context; diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java index b492161bd3..c976d5c5ee 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java @@ -101,7 +101,8 @@ public class MultiDeviceContactUpdateJob extends MasterSecretJob implements Inje Optional.fromNullable(recipient.getName()), getAvatar(recipient.getContactUri()), Optional.fromNullable(recipient.getColor().serialize()), - verifiedMessage)); + verifiedMessage, + Optional.fromNullable(recipient.getProfileKey()))); out.close(); sendUpdate(messageSender, contactDataFile, false); @@ -131,8 +132,9 @@ public class MultiDeviceContactUpdateJob extends MasterSecretJob implements Inje Optional verified = getVerifiedMessage(recipient, identity); Optional name = Optional.fromNullable(contactData.name); Optional color = Optional.of(recipient.getColor().serialize()); + Optional profileKey = Optional.fromNullable(recipient.getProfileKey()); - out.write(new DeviceContact(address.toPhoneString(), name, getAvatar(contactUri), color, verified)); + out.write(new DeviceContact(address.toPhoneString(), name, getAvatar(contactUri), color, verified, profileKey)); } out.close(); diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceProfileKeyUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceProfileKeyUpdateJob.java new file mode 100644 index 0000000000..4ad020ffaf --- /dev/null +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceProfileKeyUpdateJob.java @@ -0,0 +1,94 @@ +package org.thoughtcrime.securesms.jobs; + + +import android.content.Context; +import android.text.TextUtils; +import android.util.Log; + +import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; +import org.thoughtcrime.securesms.dependencies.InjectableType; +import org.thoughtcrime.securesms.util.Base64; +import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.whispersystems.jobqueue.JobParameters; +import org.whispersystems.jobqueue.requirements.NetworkRequirement; +import org.whispersystems.libsignal.util.guava.Optional; +import org.whispersystems.signalservice.api.SignalServiceMessageSender; +import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; +import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; +import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream; +import org.whispersystems.signalservice.api.messages.multidevice.ContactsMessage; +import org.whispersystems.signalservice.api.messages.multidevice.DeviceContact; +import org.whispersystems.signalservice.api.messages.multidevice.DeviceContactsOutputStream; +import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; +import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage; +import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import javax.inject.Inject; + +public class MultiDeviceProfileKeyUpdateJob extends MasterSecretJob implements InjectableType { + + private static final long serialVersionUID = 1L; + private static final String TAG = MultiDeviceProfileKeyUpdateJob.class.getSimpleName(); + + @Inject SignalServiceMessageSender messageSender; + + public MultiDeviceProfileKeyUpdateJob(Context context) { + super(context, JobParameters.newBuilder() + .withRequirement(new NetworkRequirement(context)) + .withPersistence() + .withGroupId(MultiDeviceProfileKeyUpdateJob.class.getSimpleName()) + .create()); + } + + @Override + public void onRun(MasterSecret masterSecret) throws IOException, UntrustedIdentityException { + if (!TextSecurePreferences.isMultiDevice(getContext())) { + Log.w(TAG, "Not multi device..."); + return; + } + + Optional profileKey = Optional.of(ProfileKeyUtil.getProfileKey(getContext())); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DeviceContactsOutputStream out = new DeviceContactsOutputStream(baos); + + out.write(new DeviceContact(TextSecurePreferences.getLocalNumber(getContext()), + Optional.absent(), + Optional.absent(), + Optional.absent(), + Optional.absent(), + profileKey)); + + out.close(); + + SignalServiceAttachmentStream attachmentStream = SignalServiceAttachment.newStreamBuilder() + .withStream(new ByteArrayInputStream(baos.toByteArray())) + .withContentType("application/octet-stream") + .withLength(baos.toByteArray().length) + .build(); + + SignalServiceSyncMessage syncMessage = SignalServiceSyncMessage.forContacts(new ContactsMessage(attachmentStream, false)); + + messageSender.sendMessage(syncMessage); + } + + @Override + public boolean onShouldRetryThrowable(Exception exception) { + if (exception instanceof PushNetworkException) return true; + return false; + } + + @Override + public void onAdded() { + + } + + @Override + public void onCanceled() { + Log.w(TAG, "Profile key sync failed!"); + } +} diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index ac487c0f09..e1d2c68094 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -26,7 +26,6 @@ import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.NoSuchMessageException; import org.thoughtcrime.securesms.database.PushDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase; -import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings; import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.groups.GroupMessageProcessor; @@ -167,7 +166,7 @@ public class PushDecryptJob extends ContextJob { handleUnknownGroupMessage(envelope, message.getGroupInfo().get()); } - if (message.getProfileKey().isPresent()) { + if (message.getProfileKey().isPresent() && message.getProfileKey().get().length == 32) { handleProfileKey(envelope, message); } } else if (content.getSyncMessage().isPresent()) { @@ -807,7 +806,7 @@ public class PushDecryptJob extends ContextJob { Address sourceAddress = Address.fromExternal(context, envelope.getSource()); Recipient recipient = Recipient.from(context, sourceAddress, false); - if (recipient.getProfileKey() == null || MessageDigest.isEqual(recipient.getProfileKey(), message.getProfileKey().get())) { + if (recipient.getProfileKey() == null || !MessageDigest.isEqual(recipient.getProfileKey(), message.getProfileKey().get())) { database.setProfileKey(recipient, message.getProfileKey().get()); ApplicationContext.getInstance(context).getJobManager().add(new RetrieveProfileJob(context, recipient)); } diff --git a/src/org/thoughtcrime/securesms/jobs/PushSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushSendJob.java index e1c6d7b751..6e8605efff 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushSendJob.java @@ -10,6 +10,7 @@ import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.TextSecureExpiredException; import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings; @@ -66,22 +67,11 @@ public abstract class PushSendJob extends SendJob { } protected Optional getProfileKey(@NonNull Recipient recipient) { - try { - if (!recipient.resolve().isSystemContact() && !recipient.resolve().isProfileSharing()) { - return Optional.absent(); - } - - String profileKey = TextSecurePreferences.getProfileKey(context); - - if (profileKey == null) { - profileKey = Util.getSecret(32); - TextSecurePreferences.setProfileKey(context, profileKey); - } - - return Optional.of(Base64.decode(profileKey)); - } catch (IOException e) { - throw new AssertionError(e); + if (!recipient.resolve().isSystemContact() && !recipient.resolve().isProfileSharing()) { + return Optional.absent(); } + + return Optional.of(ProfileKeyUtil.getProfileKey(context)); } protected SignalServiceAddress getPushAddress(Address address) {