From 2cfc714b643e72b0bbc213a74319066d87b56cdb Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Mon, 18 May 2015 15:16:27 -0700 Subject: [PATCH] Support for receiving incoming sync messages. Closes #3188 // FREEBIE --- AndroidManifest.xml | 3 +- build.gradle | 13 +- .../database/EncryptingSmsDatabase.java | 11 +- .../securesms/database/MmsDatabase.java | 4 +- .../securesms/database/SmsDatabase.java | 8 +- .../groups/GroupMessageProcessor.java | 5 - .../securesms/jobs/PushDecryptJob.java | 210 ++++++++++++------ .../securesms/mms/OutgoingMediaMessage.java | 37 +++ .../securesms/sms/MessageSender.java | 4 +- 9 files changed, 207 insertions(+), 88 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index ea5580dff1..bf4b3d8073 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -58,11 +58,10 @@ diff --git a/build.gradle b/build.gradle index 1b366007bc..e228ec9cae 100644 --- a/build.gradle +++ b/build.gradle @@ -66,8 +66,8 @@ dependencies { compile 'org.whispersystems:jobmanager:0.11.0' compile 'org.whispersystems:libpastelog:1.0.6' - compile 'org.whispersystems:textsecure-android:1.3.0' compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1' + compile 'org.whispersystems:textsecure-android:1.5.0' androidTestCompile 'com.google.dexmaker:dexmaker:1.2' androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' @@ -86,6 +86,7 @@ dependencyVerification { 'me.leolin:ShortcutBadger:3142d017234bfa0cdd69ccded7cc5ea63f13b97574803c8c616c9bbeaad33ad9', 'se.emilsjolander:stickylistheaders:89146b46c96fea0e40200474a2625cda10fe94891e4128f53cdb42375091b9b6', 'com.google.android.gms:play-services-base:832cb6b3130e871db6a412c4ab585656dbcc5e7948101f190186757785703f75', + 'com.jpardogo.materialtabstrip:library:c6ef812fba4f74be7dc4a905faa4c2908cba261a94c13d4f96d5e67e4aad4aaa', 'org.w3c:smil:085dc40f2bb249651578bfa07499fd08b16ad0886dbe2c4078586a408da62f9b', 'org.apache.httpcomponents:httpclient-android:6f56466a9bd0d42934b90bfbfe9977a8b654c058bf44a12bdc2877c4e1f033f1', 'com.github.chrisbanes.photoview:library:8b5344e206f125e7ba9d684008f36c4992d03853c57e5814125f88496126e3cc', @@ -103,23 +104,23 @@ dependencyVerification { 'com.madgag.spongycastle:prov:b8c3fec3a59aac1aa04ccf4dad7179351e54ef7672f53f508151b614c131398a', 'org.whispersystems:jobmanager:ea9cb943c4892fb90c1eea1be30efeb85cefca213d52c788419553b58d0ed70d', 'org.whispersystems:libpastelog:550d33c565380d90f4c671e7b8ed5f3a6da55a9fda468373177106b2eb5220b2', - 'org.whispersystems:textsecure-android:df4c1ac9ee8f7cd43c8c07d64b16e31875e04632afc3fe73f2a47292a86c79a1', 'com.amulyakhare:com.amulyakhare.textdrawable:54c92b5fba38cfd316a07e5a30528068f45ce8515a6890f1297df4c401af5dcb', + 'org.whispersystems:textsecure-android:b8001bd3c77fadcf84e0c3b21353f76d6db783bd17b28c5d7296d71198446d4b', 'com.nineoldandroids:library:68025a14e3e7673d6ad2f95e4b46d78d7d068343aa99256b686fe59de1b3163a', 'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff', 'com.madgag.spongycastle:core:8d6240b974b0aca4d3da9c7dd44d42339d8a374358aca5fc98e50a995764511f', - 'org.whispersystems:textsecure-java:59d7c998c83a0f96bbc84db36719c9be8007bce16ee1ba9748af5610ab429656', 'org.whispersystems:axolotl-android:40d3db5004a84749a73f68d2f0d01b2ae35a73c54df96d8c6c6723b96efb6fc0', + 'org.whispersystems:textsecure-java:b879094961f9ef0c851206223051e8478e6bbba0f039ea9340be20f40384bb20', + 'org.whispersystems:axolotl-java:6daee739b89d8d7101de6d98f77132fee48495c6ea647d880e77def842f999ea', + 'org.whispersystems:curve25519-android:3c29a4131a69b0d16baaa3d707678deb39602c3a3ffd75805ce7f9db252e5d0d', 'com.googlecode.libphonenumber:libphonenumber:eba17eae81dd622ea89a00a3a8c025b2f25d342e0d9644c5b62e16f15687c3ab', 'com.google.protobuf:protobuf-java:e0c1c64575c005601725e7c6a02cebf9e1285e888f756b2a1d73ffa8d725cc74', 'com.squareup.okhttp:okhttp:89b7f63e2e5b6c410266abc14f50fe52ea8d2d8a57260829e499b1cd9f0e61af', 'com.fasterxml.jackson.core:jackson-databind:835097bcdd11f5bc8a08378c70d4c8054dfa4b911691cc2752063c75534d198d', - 'org.whispersystems:axolotl-java:6daee739b89d8d7101de6d98f77132fee48495c6ea647d880e77def842f999ea', - 'org.whispersystems:curve25519-android:3c29a4131a69b0d16baaa3d707678deb39602c3a3ffd75805ce7f9db252e5d0d', + 'org.whispersystems:curve25519-java:9ccef8f5aba05d9942336f023c589d6278b4f9135bdc34a7bade1f4e7ad65fa3', 'com.squareup.okio:okio:5e1098bd3fdee4c3347f5ab815b40ba851e4ab1b348c5e49a5b0362f0ce6e978', 'com.fasterxml.jackson.core:jackson-annotations:0ca408c24202a7626ec8b861e99d85eca5e38b73311dd6dd12e3e9deecc3fe94', 'com.fasterxml.jackson.core:jackson-core:cbf4604784b4de226262845447a1ad3bb38a6728cebe86562e2c5afada8be2c0', - 'org.whispersystems:curve25519-java:9ccef8f5aba05d9942336f023c589d6278b4f9135bdc34a7bade1f4e7ad65fa3', 'com.android.support:support-v4:1e2e4d35ac7fd30db5ce3bc177b92e4d5af86acef2ef93e9221599d733346f56', 'com.android.support:support-annotations:7bc07519aa613b186001160403bcfd68260fa82c61cc7e83adeedc9b862b94ae', ] diff --git a/src/org/thoughtcrime/securesms/database/EncryptingSmsDatabase.java b/src/org/thoughtcrime/securesms/database/EncryptingSmsDatabase.java index ece5bd0c4e..c8878edf79 100644 --- a/src/org/thoughtcrime/securesms/database/EncryptingSmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/EncryptingSmsDatabase.java @@ -61,13 +61,14 @@ public class EncryptingSmsDatabase extends SmsDatabase { } public long insertMessageOutbox(MasterSecret masterSecret, long threadId, - OutgoingTextMessage message, boolean forceSms) + OutgoingTextMessage message, boolean forceSms, + long timestamp) { long type = Types.BASE_OUTBOX_TYPE; message = message.withBody(getEncryptedBody(masterSecret, message.getMessageBody())); type |= Types.ENCRYPTION_SYMMETRIC_BIT; - return insertMessageOutbox(threadId, message, type, forceSms); + return insertMessageOutbox(threadId, message, type, forceSms, timestamp); } public Pair insertMessageInbox(MasterSecret masterSecret, @@ -100,10 +101,10 @@ public class EncryptingSmsDatabase extends SmsDatabase { return insertMessageInbox(message, type); } - public void updateBundleMessageBody(MasterSecret masterSecret, long messageId, String body) { + public Pair updateBundleMessageBody(MasterSecret masterSecret, long messageId, String body) { String encryptedBody = getEncryptedBody(masterSecret, body); - updateMessageBodyAndType(messageId, encryptedBody, Types.TOTAL_MASK, - Types.BASE_INBOX_TYPE | Types.ENCRYPTION_SYMMETRIC_BIT | Types.SECURE_MESSAGE_BIT); + return updateMessageBodyAndType(messageId, encryptedBody, Types.TOTAL_MASK, + Types.BASE_INBOX_TYPE | Types.ENCRYPTION_SYMMETRIC_BIT | Types.SECURE_MESSAGE_BIT); } public void updateMessageBody(MasterSecret masterSecret, long messageId, String body) { diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java index e74d71325b..161d076388 100644 --- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -681,7 +681,7 @@ public class MmsDatabase extends MessagingDatabase { } public long insertMessageOutbox(MasterSecret masterSecret, OutgoingMediaMessage message, - long threadId, boolean forceSms) + long threadId, boolean forceSms, long timestamp) throws MmsException { long type = Types.BASE_OUTBOX_TYPE | Types.ENCRYPTION_SYMMETRIC_BIT; @@ -695,7 +695,7 @@ public class MmsDatabase extends MessagingDatabase { } SendReq sendRequest = new SendReq(); - sendRequest.setDate(System.currentTimeMillis() / 1000L); + sendRequest.setDate(timestamp / 1000L); sendRequest.setBody(message.getPduBody()); sendRequest.setContentType(ContentType.MULTIPART_MIXED.getBytes()); diff --git a/src/org/thoughtcrime/securesms/database/SmsDatabase.java b/src/org/thoughtcrime/securesms/database/SmsDatabase.java index f25096c2eb..b9f5710f23 100644 --- a/src/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -303,7 +303,7 @@ public class SmsDatabase extends MessagingDatabase { database.update(TABLE_NAME, contentValues, null, null); } - protected void updateMessageBodyAndType(long messageId, String body, long maskOff, long maskOn) { + protected Pair updateMessageBodyAndType(long messageId, String body, long maskOff, long maskOn) { SQLiteDatabase db = databaseHelper.getWritableDatabase(); db.execSQL("UPDATE " + TABLE_NAME + " SET " + BODY + " = ?, " + TYPE + " = (" + TYPE + " & " + (Types.TOTAL_MASK - maskOff) + " | " + maskOn + ") " + @@ -315,6 +315,8 @@ public class SmsDatabase extends MessagingDatabase { DatabaseFactory.getThreadDatabase(context).update(threadId); notifyConversationListeners(threadId); notifyConversationListListeners(); + + return new Pair<>(messageId, threadId); } public Pair copyMessageInbox(long messageId) { @@ -421,15 +423,13 @@ public class SmsDatabase extends MessagingDatabase { } protected long insertMessageOutbox(long threadId, OutgoingTextMessage message, - long type, boolean forceSms) + long type, boolean forceSms, long date) { if (message.isKeyExchange()) type |= Types.KEY_EXCHANGE_BIT; else if (message.isSecureMessage()) type |= Types.SECURE_MESSAGE_BIT; else if (message.isEndSession()) type |= Types.END_SESSION_BIT; if (forceSms) type |= Types.MESSAGE_FORCE_SMS_BIT; - long date = System.currentTimeMillis(); - ContentValues contentValues = new ContentValues(6); contentValues.put(ADDRESS, PhoneNumberUtils.formatNumber(message.getRecipients().getPrimaryRecipient().getNumber())); contentValues.put(THREAD_ID, threadId); diff --git a/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java b/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java index 46177342a4..e7a68bb56f 100644 --- a/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java +++ b/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java @@ -46,11 +46,6 @@ public class GroupMessageProcessor { return; } - if (!message.isSecure()) { - Log.w(TAG, "Received insecure group push action! Ignoring..."); - return; - } - GroupDatabase database = DatabaseFactory.getGroupDatabase(context); TextSecureGroup group = message.getGroupInfo().get(); byte[] id = group.getGroupId(); diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 0d887e4d38..5dfad5bfae 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -17,6 +17,8 @@ import org.thoughtcrime.securesms.database.PushDatabase; import org.thoughtcrime.securesms.groups.GroupMessageProcessor; import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; import org.thoughtcrime.securesms.mms.IncomingMediaMessage; +import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; +import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.Recipients; @@ -25,7 +27,9 @@ import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage; import org.thoughtcrime.securesms.sms.IncomingEndSessionMessage; import org.thoughtcrime.securesms.sms.IncomingPreKeyBundleMessage; import org.thoughtcrime.securesms.sms.IncomingTextMessage; +import org.thoughtcrime.securesms.sms.OutgoingTextMessage; import org.thoughtcrime.securesms.util.Base64; +import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.libaxolotl.DuplicateMessageException; @@ -45,6 +49,8 @@ import org.whispersystems.textsecure.api.crypto.TextSecureCipher; import org.whispersystems.textsecure.api.messages.TextSecureEnvelope; import org.whispersystems.textsecure.api.messages.TextSecureGroup; import org.whispersystems.textsecure.api.messages.TextSecureMessage; +import org.whispersystems.textsecure.api.messages.TextSecureSyncContext; +import org.whispersystems.textsecure.api.push.TextSecureAddress; import java.util.concurrent.TimeUnit; @@ -81,10 +87,12 @@ public class PushDecryptJob extends MasterSecretJob { @Override public void onRun(MasterSecret masterSecret) throws NoSuchMessageException { - PushDatabase database = DatabaseFactory.getPushDatabase(context); - TextSecureEnvelope envelope = database.get(messageId); + PushDatabase database = DatabaseFactory.getPushDatabase(context); + TextSecureEnvelope envelope = database.get(messageId); + Optional optionalSmsMessageId = smsMessageId > 0 ? Optional.of(smsMessageId) : + Optional.absent(); - handleMessage(masterSecret, envelope, smsMessageId); + handleMessage(masterSecret, envelope, optionalSmsMessageId); database.delete(messageId); } @@ -98,10 +106,11 @@ public class PushDecryptJob extends MasterSecretJob { } - private void handleMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, long smsMessageId) { + private void handleMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, Optional smsMessageId) { try { - AxolotlStore axolotlStore = new TextSecureAxolotlStore(context, masterSecret); - TextSecureCipher cipher = new TextSecureCipher(axolotlStore); + AxolotlStore axolotlStore = new TextSecureAxolotlStore(context, masterSecret); + TextSecureAddress localAddress = new TextSecureAddress(TextSecurePreferences.getLocalNumber(context)); + TextSecureCipher cipher = new TextSecureCipher(localAddress, axolotlStore); TextSecureMessage message = cipher.decrypt(envelope); @@ -135,7 +144,7 @@ public class PushDecryptJob extends MasterSecretJob { } private void handleEndSessionMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, - TextSecureMessage message, long smsMessageId) + TextSecureMessage message, Optional smsMessageId) { EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context); IncomingTextMessage incomingTextMessage = new IncomingTextMessage(envelope.getSource(), @@ -145,13 +154,13 @@ public class PushDecryptJob extends MasterSecretJob { long threadId; - if (smsMessageId <= 0) { + if (!smsMessageId.isPresent()) { IncomingEndSessionMessage incomingEndSessionMessage = new IncomingEndSessionMessage(incomingTextMessage); Pair messageAndThreadId = smsDatabase.insertMessageInbox(masterSecret, incomingEndSessionMessage); threadId = messageAndThreadId.second; } else { - smsDatabase.markAsEndSession(smsMessageId); - threadId = smsDatabase.getThreadIdForMessage(smsMessageId); + smsDatabase.markAsEndSession(smsMessageId.get()); + threadId = smsDatabase.getThreadIdForMessage(smsMessageId.get()); } SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret); @@ -161,117 +170,100 @@ public class PushDecryptJob extends MasterSecretJob { MessageNotifier.updateNotification(context, masterSecret, threadId); } - private void handleGroupMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, TextSecureMessage message, long smsMessageId) { + private void handleGroupMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, TextSecureMessage message, Optional smsMessageId) { GroupMessageProcessor.process(context, masterSecret, envelope, message); - if (smsMessageId > 0) { - DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId); + if (smsMessageId.isPresent()) { + DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId.get()); } } - private void handleMediaMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, TextSecureMessage message, long smsMessageId) + private void handleMediaMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, + TextSecureMessage message, Optional smsMessageId) throws MmsException { - String localNumber = TextSecurePreferences.getLocalNumber(context); - MmsDatabase database = DatabaseFactory.getMmsDatabase(context); - IncomingMediaMessage mediaMessage = new IncomingMediaMessage(masterSecret, envelope.getSource(), - localNumber, message.getTimestamp(), - Optional.fromNullable(envelope.getRelay()), - message.getBody(), - message.getGroupInfo(), - message.getAttachments()); - Pair messageAndThreadId; - if (message.isSecure()) { - messageAndThreadId = database.insertSecureDecryptedMessageInbox(masterSecret, mediaMessage, -1); + if (message.getSyncContext().isPresent()) { + messageAndThreadId = insertSyncMediaMessage(masterSecret, envelope, message); } else { - messageAndThreadId = database.insertMessageInbox(masterSecret, mediaMessage, null, -1); + messageAndThreadId = insertStandardMediaMessage(masterSecret, envelope, message); } ApplicationContext.getInstance(context) .getJobManager() .add(new AttachmentDownloadJob(context, messageAndThreadId.first)); - if (smsMessageId >= 0) { - DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId); + if (smsMessageId.isPresent()) { + DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId.get()); } MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); } private void handleTextMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, - TextSecureMessage message, long smsMessageId) + TextSecureMessage message, Optional smsMessageId) { - EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); - String body = message.getBody().isPresent() ? message.getBody().get() : ""; + Pair messageAndThreadId; - if (smsMessageId > 0) { - database.updateBundleMessageBody(masterSecret, smsMessageId, body); + if (message.getSyncContext().isPresent()) { + messageAndThreadId = insertSyncTextMessage(masterSecret, envelope, message, smsMessageId); } else { - IncomingTextMessage textMessage = new IncomingTextMessage(envelope.getSource(), - envelope.getSourceDevice(), - message.getTimestamp(), body, - message.getGroupInfo()); - - if (message.isSecure()) { - textMessage = new IncomingEncryptedMessage(textMessage, body); - } - - Pair messageAndThreadId = database.insertMessageInbox(masterSecret, textMessage); - MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); + messageAndThreadId = insertStandardTextMessage(masterSecret, envelope, message, smsMessageId); } + + MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); } - private void handleInvalidVersionMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, long smsMessageId) { + private void handleInvalidVersionMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, Optional smsMessageId) { EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context); - if (smsMessageId <= 0) { + if (!smsMessageId.isPresent()) { Pair messageAndThreadId = insertPlaceholder(masterSecret, envelope); smsDatabase.markAsInvalidVersionKeyExchange(messageAndThreadId.first); MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); } else { - smsDatabase.markAsInvalidVersionKeyExchange(smsMessageId); + smsDatabase.markAsInvalidVersionKeyExchange(smsMessageId.get()); } } - private void handleCorruptMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, long smsMessageId) { + private void handleCorruptMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, Optional smsMessageId) { EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context); - if (smsMessageId <= 0) { + if (!smsMessageId.isPresent()) { Pair messageAndThreadId = insertPlaceholder(masterSecret, envelope); smsDatabase.markAsDecryptFailed(messageAndThreadId.first); MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); } else { - smsDatabase.markAsDecryptFailed(smsMessageId); + smsDatabase.markAsDecryptFailed(smsMessageId.get()); } } - private void handleNoSessionMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, long smsMessageId) { + private void handleNoSessionMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, Optional smsMessageId) { EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context); - if (smsMessageId <= 0) { + if (!smsMessageId.isPresent()) { Pair messageAndThreadId = insertPlaceholder(masterSecret, envelope); smsDatabase.markAsNoSession(messageAndThreadId.first); MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); } else { - smsDatabase.markAsNoSession(smsMessageId); + smsDatabase.markAsNoSession(smsMessageId.get()); } } - private void handleLegacyMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, long smsMessageId) { + private void handleLegacyMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, Optional smsMessageId) { EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context); - if (smsMessageId <= 0) { + if (!smsMessageId.isPresent()) { Pair messageAndThreadId = insertPlaceholder(masterSecret, envelope); smsDatabase.markAsLegacyVersion(messageAndThreadId.first); MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); } else { - smsDatabase.markAsLegacyVersion(smsMessageId); + smsDatabase.markAsLegacyVersion(smsMessageId.get()); } } - private void handleDuplicateMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, long smsMessageId) { + private void handleDuplicateMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, Optional smsMessageId) { // Let's start ignoring these now // SmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context); // @@ -284,7 +276,7 @@ public class PushDecryptJob extends MasterSecretJob { // } } - private void handleUntrustedIdentityMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, long smsMessageId) { + private void handleUntrustedIdentityMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, Optional smsMessageId) { try { EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); Recipients recipients = RecipientFactory.getRecipientsFromString(context, envelope.getSource(), false); @@ -296,16 +288,16 @@ public class PushDecryptJob extends MasterSecretJob { envelope.getTimestamp(), encoded, Optional.absent()); - if (smsMessageId <= 0) { + if (!smsMessageId.isPresent()) { IncomingPreKeyBundleMessage bundleMessage = new IncomingPreKeyBundleMessage(textMessage, encoded); Pair messageAndThreadId = database.insertMessageInbox(masterSecret, bundleMessage); database.addMismatchedIdentity(messageAndThreadId.first, recipientId, identityKey); MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); } else { - database.updateMessageBody(masterSecret, smsMessageId, encoded); - database.markAsPreKeyBundle(smsMessageId); - database.addMismatchedIdentity(smsMessageId, recipientId, identityKey); + database.updateMessageBody(masterSecret, smsMessageId.get(), encoded); + database.markAsPreKeyBundle(smsMessageId.get()); + database.addMismatchedIdentity(smsMessageId.get(), recipientId, identityKey); } } catch (InvalidMessageException | InvalidVersionException e) { throw new AssertionError(e); @@ -323,4 +315,98 @@ public class PushDecryptJob extends MasterSecretJob { return database.insertMessageInbox(masterSecret, textMessage); } + + private Pair insertSyncTextMessage(MasterSecret masterSecret, + TextSecureEnvelope envelope, + TextSecureMessage message, + Optional smsMessageId) + { + EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); + Recipients recipients = getSyncMessageDestination(message); + String body = message.getBody().or(""); + OutgoingTextMessage outgoingTextMessage = new OutgoingTextMessage(recipients, body); + + long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients); + long messageId = database.insertMessageOutbox(masterSecret, threadId, outgoingTextMessage, false, message.getSyncContext().get().getTimestamp()); + + database.markAsSent(messageId); + database.markAsPush(messageId); + database.markAsSecure(messageId); + + if (smsMessageId.isPresent()) { + database.deleteMessage(smsMessageId.get()); + } + + return new Pair<>(messageId, threadId); + } + + private Pair insertStandardTextMessage(MasterSecret masterSecret, + TextSecureEnvelope envelope, + TextSecureMessage message, + Optional smsMessageId) + { + EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); + String body = message.getBody().isPresent() ? message.getBody().get() : ""; + + if (smsMessageId.isPresent()) { + return database.updateBundleMessageBody(masterSecret, smsMessageId.get(), body); + } else { + IncomingTextMessage textMessage = new IncomingTextMessage(envelope.getSource(), + envelope.getSourceDevice(), + message.getTimestamp(), body, + message.getGroupInfo()); + + textMessage = new IncomingEncryptedMessage(textMessage, body); + + return database.insertMessageInbox(masterSecret, textMessage); + } + } + + private Pair insertSyncMediaMessage(MasterSecret masterSecret, + TextSecureEnvelope envelope, + TextSecureMessage message) + throws MmsException + { + MmsDatabase database = DatabaseFactory.getMmsDatabase(context); + TextSecureSyncContext syncContext = message.getSyncContext().get(); + Recipients recipients = getSyncMessageDestination(message); + OutgoingMediaMessage mediaMessage = new OutgoingMediaMessage(context, masterSecret, recipients, + message.getAttachments().get(), + message.getBody().orNull()); + + mediaMessage = new OutgoingSecureMediaMessage(mediaMessage); + + long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients); + long messageId = database.insertMessageOutbox(masterSecret, mediaMessage, threadId, false, syncContext.getTimestamp()); + + database.markAsSent(messageId, "push".getBytes(), 0); + database.markAsPush(messageId); + + return new Pair<>(messageId, threadId); + } + + private Pair insertStandardMediaMessage(MasterSecret masterSecret, + TextSecureEnvelope envelope, + TextSecureMessage message) + throws MmsException + { + MmsDatabase database = DatabaseFactory.getMmsDatabase(context); + String localNumber = TextSecurePreferences.getLocalNumber(context); + IncomingMediaMessage mediaMessage = new IncomingMediaMessage(masterSecret, envelope.getSource(), + localNumber, message.getTimestamp(), + Optional.fromNullable(envelope.getRelay()), + message.getBody(), + message.getGroupInfo(), + message.getAttachments()); + + return database.insertSecureDecryptedMessageInbox(masterSecret, mediaMessage, -1); + } + + private Recipients getSyncMessageDestination(TextSecureMessage message) { + if (message.getGroupInfo().isPresent()) { + return RecipientFactory.getRecipientsFromString(context, GroupUtil.getEncodedId(message.getGroupInfo().get().getGroupId()), false); + } else { + return RecipientFactory.getRecipientsFromString(context, message.getSyncContext().get().getDestination(), false); + } + } } diff --git a/src/org/thoughtcrime/securesms/mms/OutgoingMediaMessage.java b/src/org/thoughtcrime/securesms/mms/OutgoingMediaMessage.java index a2de56c542..74c813d002 100644 --- a/src/org/thoughtcrime/securesms/mms/OutgoingMediaMessage.java +++ b/src/org/thoughtcrime/securesms/mms/OutgoingMediaMessage.java @@ -3,9 +3,18 @@ package org.thoughtcrime.securesms.mms; import android.content.Context; import android.text.TextUtils; +import org.thoughtcrime.securesms.crypto.MasterCipher; +import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.recipients.Recipients; +import org.thoughtcrime.securesms.util.Base64; +import org.thoughtcrime.securesms.util.Util; +import org.whispersystems.textsecure.api.messages.TextSecureAttachment; + +import java.util.List; import ws.com.google.android.mms.pdu.PduBody; +import ws.com.google.android.mms.pdu.PduPart; public class OutgoingMediaMessage { @@ -31,6 +40,14 @@ public class OutgoingMediaMessage { this(context, recipients, slideDeck.toPduBody(), message, distributionType); } + public OutgoingMediaMessage(Context context, MasterSecret masterSecret, + Recipients recipients, List attachments, + String message) + { + this(context, recipients, pduBodyFor(masterSecret, attachments), message, + ThreadDatabase.DistributionTypes.CONVERSATION); + } + public OutgoingMediaMessage(OutgoingMediaMessage that) { this.recipients = that.getRecipients(); this.body = that.body; @@ -57,4 +74,24 @@ public class OutgoingMediaMessage { return false; } + private static PduBody pduBodyFor(MasterSecret masterSecret, List attachments) { + PduBody body = new PduBody(); + + for (TextSecureAttachment attachment : attachments) { + if (attachment.isPointer()) { + PduPart media = new PduPart(); + byte[] encryptedKey = new MasterCipher(masterSecret).encryptBytes(attachment.asPointer().getKey()); + + media.setContentType(Util.toIsoBytes(attachment.getContentType())); + media.setContentLocation(Util.toIsoBytes(String.valueOf(attachment.asPointer().getId()))); + media.setContentDisposition(Util.toIsoBytes(Base64.encodeBytes(encryptedKey))); + media.setPendingPush(true); + + body.addPart(media); + } + } + + return body; + } + } diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index 258a5c6c1a..eaf7bd3b47 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -73,7 +73,7 @@ public class MessageSender { allocatedThreadId = threadId; } - long messageId = database.insertMessageOutbox(masterSecret, allocatedThreadId, message, forceSms); + long messageId = database.insertMessageOutbox(masterSecret, allocatedThreadId, message, forceSms, System.currentTimeMillis()); sendTextMessage(context, recipients, forceSms, keyExchange, messageId); @@ -99,7 +99,7 @@ public class MessageSender { } Recipients recipients = message.getRecipients(); - long messageId = database.insertMessageOutbox(masterSecret, message, allocatedThreadId, forceSms); + long messageId = database.insertMessageOutbox(masterSecret, message, allocatedThreadId, forceSms, System.currentTimeMillis()); sendMediaMessage(context, masterSecret, recipients, forceSms, messageId);