diff --git a/src/org/thoughtcrime/securesms/ApplicationContext.java b/src/org/thoughtcrime/securesms/ApplicationContext.java index 611fe53b73..041bdd5e86 100644 --- a/src/org/thoughtcrime/securesms/ApplicationContext.java +++ b/src/org/thoughtcrime/securesms/ApplicationContext.java @@ -186,7 +186,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc if (setUpStorageAPIIfNeeded()) { String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this); if (userHexEncodedPublicKey != null) { - LokiFileServerAPI.Companion.getShared().getDeviceLinks(userHexEncodedPublicKey, true); if (TextSecurePreferences.getNeedsIsRevokedSlaveDeviceCheck(this)) { MultiDeviceUtilities.checkIsRevokedSlaveDevice(this); } diff --git a/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java b/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java index d1f37ce390..c9145e77e1 100644 --- a/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java +++ b/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java @@ -206,14 +206,11 @@ public class GroupMessageProcessor { @NonNull GroupRecord record) { String hexEncodedPublicKey = getMasterHexEncodedPublicKey(context, content.getSender()); - String ourPublicKey = getMasterHexEncodedPublicKey(context, TextSecurePreferences.getLocalNumber(context)); - // If the requester is a group member and we are admin then we should send them the group update - if (record.getMembers().contains(Address.fromSerialized(hexEncodedPublicKey)) && record.getAdmins().contains(Address.fromSerialized(ourPublicKey))) { + if (record.getMembers().contains(Address.fromSerialized(hexEncodedPublicKey))) { ApplicationContext.getInstance(context) .getJobManager() .add(new PushGroupUpdateJob(content.getSender(), group.getGroupId())); } - return null; } diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java index 52a81d5d57..48afa32221 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java @@ -85,18 +85,23 @@ public class MultiDeviceGroupUpdateJob extends BaseJob implements InjectableType reader = DatabaseFactory.getGroupDatabase(context).getGroups(); while ((record = reader.getNext()) != null) { - if (!record.isMms() && !record.isPublicChat() && !record.isRSSFeed()) { + if (record.isSignalGroup()) { List members = new LinkedList<>(); + List admins = new LinkedList<>(); for (Address member : record.getMembers()) { members.add(member.serialize()); } + for (Address admin : record.getAdmins()) { + admins.add(admin.serialize()); + } + Recipient recipient = Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedId(record.getId(), record.isMms())), false); Optional expirationTimer = recipient.getExpireMessages() > 0 ? Optional.of(recipient.getExpireMessages()) : Optional.absent(); out.write(new DeviceGroup(record.getId(), Optional.fromNullable(record.getTitle()), - members, getAvatar(record.getAvatar()), + members, admins, getAvatar(record.getAvatar()), record.isActive(), expirationTimer, Optional.of(recipient.getColor().serialize()), recipient.isBlocked())); @@ -120,7 +125,8 @@ public class MultiDeviceGroupUpdateJob extends BaseJob implements InjectableType @Override public boolean onShouldRetry(@NonNull Exception exception) { - if (exception instanceof PushNetworkException) return true; + // Loki - Disabled because we have our own retrying + // if (exception instanceof PushNetworkException) return true; return false; } @@ -139,7 +145,6 @@ public class MultiDeviceGroupUpdateJob extends BaseJob implements InjectableType .withLength(contactsFile.length()) .build(); - // TODO: Message ID messageSender.sendMessage(0, SignalServiceSyncMessage.forGroups(attachmentStream), UnidentifiedAccessUtil.getAccessForSync(context)); } diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 49163e4f2f..6d7595b9b8 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -107,6 +107,7 @@ import org.whispersystems.libsignal.state.PreKeyBundle; import org.whispersystems.libsignal.state.SignalProtocolStore; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceMessageSender; +import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceContent; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage.Preview; @@ -123,6 +124,8 @@ import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMess import org.whispersystems.signalservice.api.messages.multidevice.ContactsMessage; import org.whispersystems.signalservice.api.messages.multidevice.DeviceContact; import org.whispersystems.signalservice.api.messages.multidevice.DeviceContactsInputStream; +import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroup; +import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsInputStream; import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage; import org.whispersystems.signalservice.api.messages.multidevice.RequestMessage; import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage; @@ -389,6 +392,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { else if (syncMessage.getVerified().isPresent()) handleSynchronizeVerifiedMessage(syncMessage.getVerified().get()); else if (syncMessage.getStickerPackOperations().isPresent()) handleSynchronizeStickerPackOperation(syncMessage.getStickerPackOperations().get()); else if (syncMessage.getContacts().isPresent()) handleContactSyncMessage(syncMessage.getContacts().get()); + else if (syncMessage.getGroups().isPresent()) handleGroupSyncMessage(content, syncMessage.getGroups().get()); else Log.w(TAG, "Contains no known sync types..."); } else if (content.getCallMessage().isPresent()) { Log.i(TAG, "Got call message..."); @@ -719,6 +723,32 @@ public class PushDecryptJob extends BaseJob implements InjectableType { } } + private void handleGroupSyncMessage(@NonNull SignalServiceContent content, @NonNull SignalServiceAttachment groupMessage) { + if (groupMessage.isStream()) { + Log.d("Loki", "Received a group sync message."); + try { + InputStream in = groupMessage.asStream().getInputStream(); + DeviceGroupsInputStream groupsInputStream = new DeviceGroupsInputStream(in); + List groups = groupsInputStream.readAll(); + for (DeviceGroup group : groups) { + SignalServiceGroup serviceGroup = new SignalServiceGroup( + SignalServiceGroup.Type.UPDATE, + group.getId(), + SignalServiceGroup.GroupType.SIGNAL, + group.getName().orNull(), + group.getMembers(), + group.getAvatar().orNull(), + group.getAdmins() + ); + SignalServiceDataMessage dataMessage = new SignalServiceDataMessage(content.getTimestamp(), serviceGroup, null, null); + GroupMessageProcessor.process(context, content, dataMessage, false); + } + } catch (Exception e) { + Log.d("Loki", "Failed to sync group due to error: " + e + "."); + } + } + } + private void handleSynchronizeSentMessage(@NonNull SignalServiceContent content, @NonNull SentTranscriptMessage message) throws StorageFailedException diff --git a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt index a3e08cc8a5..6afb77f165 100644 --- a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt +++ b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt @@ -128,7 +128,7 @@ fun sendDeviceLinkMessage(context: Context, hexEncodedPublicKey: String, deviceL } Promise.ofSuccess(Unit) } catch (e: Exception) { - Log.d("Loki", "Failed to send device link message to: $hexEncodedPublicKey.") + Log.d("Loki", "Failed to send device link message to: $hexEncodedPublicKey due to error: $e.") Promise.ofFail(e) } } diff --git a/src/org/thoughtcrime/securesms/loki/redesign/activities/LinkedDevicesActivity.kt b/src/org/thoughtcrime/securesms/loki/redesign/activities/LinkedDevicesActivity.kt index 94b4c09c9a..759362b91c 100644 --- a/src/org/thoughtcrime/securesms/loki/redesign/activities/LinkedDevicesActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/redesign/activities/LinkedDevicesActivity.kt @@ -22,7 +22,6 @@ import org.thoughtcrime.securesms.loki.redesign.dialogs.* import org.thoughtcrime.securesms.loki.signAndSendDeviceLinkMessage import org.thoughtcrime.securesms.sms.MessageSender import org.thoughtcrime.securesms.util.TextSecurePreferences -import org.thoughtcrime.securesms.util.Util import org.whispersystems.signalservice.loki.api.DeviceLink import org.whispersystems.signalservice.loki.api.LokiFileServerAPI import java.util.* @@ -145,20 +144,19 @@ class LinkedDevicesActivity : PassphraseRequiredActionBarActivity, LoaderManager override fun onDeviceLinkRequestAuthorized(deviceLink: DeviceLink) { LokiFileServerAPI.shared.addDeviceLink(deviceLink).success { - signAndSendDeviceLinkMessage(this, deviceLink).success { + signAndSendDeviceLinkMessage(this, deviceLink).successUi { + LoaderManager.getInstance(this).restartLoader(0, null, this) + }.success { TextSecurePreferences.setMultiDevice(this, true) - Util.runOnMain { - LoaderManager.getInstance(this).restartLoader(0, null, this) - } Timer().schedule(4000) { + MessageSender.syncAllGroups(this@LinkedDevicesActivity) MessageSender.syncAllContacts(this@LinkedDevicesActivity, Address.fromSerialized(deviceLink.slaveHexEncodedPublicKey)) } + }.failUi { + Toast.makeText(this, "Couldn't link device", Toast.LENGTH_LONG).show() }.fail { LokiFileServerAPI.shared.removeDeviceLink(deviceLink) // If this fails we have a problem DatabaseFactory.getLokiPreKeyBundleDatabase(this).removePreKeyBundle(deviceLink.slaveHexEncodedPublicKey) - Util.runOnMain { - Toast.makeText(this, "Couldn't link device", Toast.LENGTH_LONG).show() - } } }.failUi { DatabaseFactory.getLokiPreKeyBundleDatabase(this).removePreKeyBundle(deviceLink.slaveHexEncodedPublicKey) diff --git a/src/org/thoughtcrime/securesms/loki/redesign/messaging/LokiPreKeyBundleDatabase.kt b/src/org/thoughtcrime/securesms/loki/redesign/messaging/LokiPreKeyBundleDatabase.kt index 22a4ee1e7c..caac819d34 100644 --- a/src/org/thoughtcrime/securesms/loki/redesign/messaging/LokiPreKeyBundleDatabase.kt +++ b/src/org/thoughtcrime/securesms/loki/redesign/messaging/LokiPreKeyBundleDatabase.kt @@ -16,6 +16,7 @@ import org.thoughtcrime.securesms.loki.redesign.utilities.insertOrUpdate import org.thoughtcrime.securesms.util.Base64 import org.thoughtcrime.securesms.util.TextSecurePreferences import org.whispersystems.libsignal.IdentityKey +import org.whispersystems.libsignal.InvalidKeyException import org.whispersystems.libsignal.ecc.Curve import org.whispersystems.libsignal.state.PreKeyBundle import org.whispersystems.libsignal.util.KeyHelper @@ -41,6 +42,25 @@ class LokiPreKeyBundleDatabase(context: Context, helper: SQLCipherOpenHelper) : } fun generatePreKeyBundle(hexEncodedPublicKey: String): PreKeyBundle? { + var failureCount = 0 + while (failureCount < 3) { + try { + val preKey = generatePreKeyBundle(hexEncodedPublicKey, failureCount > 0) ?: return null + // Verify the bundle is correct + if (!Curve.verifySignature(preKey.identityKey.publicKey, preKey.signedPreKey.serialize(), preKey.signedPreKeySignature)) { + throw InvalidKeyException() + } + return preKey; + } catch (e: InvalidKeyException) { + failureCount += 1 + } + } + Log.w("Loki", "Failed to generate a valid pre key bundle for: $hexEncodedPublicKey.") + return null + } + + private fun generatePreKeyBundle(hexEncodedPublicKey: String, forceClean: Boolean): PreKeyBundle? { + if (hexEncodedPublicKey.isEmpty()) return null var registrationID = TextSecurePreferences.getLocalRegistrationId(context) if (registrationID == 0) { registrationID = KeyHelper.generateRegistrationId(false) @@ -49,7 +69,7 @@ class LokiPreKeyBundleDatabase(context: Context, helper: SQLCipherOpenHelper) : val deviceID = SignalServiceAddress.DEFAULT_DEVICE_ID val preKeyRecord = DatabaseFactory.getLokiPreKeyRecordDatabase(context).getOrCreatePreKeyRecord(hexEncodedPublicKey) val identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context) - if (TextSecurePreferences.isSignedPreKeyRegistered(context)) { + if (!forceClean && TextSecurePreferences.isSignedPreKeyRegistered(context)) { Log.d("Loki", "A signed pre key has already been registered.") } else { Log.d("Loki", "Registering a new signed pre key.") diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index 16e7a06842..80a4baa482 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -37,6 +37,7 @@ import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.JobManager; import org.thoughtcrime.securesms.jobs.MmsSendJob; import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob; +import org.thoughtcrime.securesms.jobs.MultiDeviceGroupUpdateJob; import org.thoughtcrime.securesms.jobs.PushGroupSendJob; import org.thoughtcrime.securesms.jobs.PushMediaSendJob; import org.thoughtcrime.securesms.jobs.PushTextSendJob; @@ -81,6 +82,10 @@ public class MessageSender { ApplicationContext.getInstance(context).getJobManager().add(new MultiDeviceContactUpdateJob(context, recipient, true)); } + public static void syncAllGroups(Context context) { + ApplicationContext.getInstance(context).getJobManager().add(new MultiDeviceGroupUpdateJob()); + } + /** * Send a contact sync message to all our devices telling them that we want to sync `contact` */