From a3f1d9cdfdd32c401bb76a020e3e4e5157212068 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Mon, 3 Nov 2014 15:16:04 -0800 Subject: [PATCH] Beginning of libtextsecure refactor. 1) Break out appropriate components. 2) Switch the incoming pipeline from SendReceiveService to the JobManager. --- .../whispersystems/jobqueue/JobConsumer.java | 5 + .../whispersystems/jobqueue/JobManager.java | 8 +- .../api/TextSecureMessageReceiver.java | 155 ++++++ .../api/TextSecureMessageSender.java | 303 +++++++++++ .../api/messages/TextSecureAttachment.java | 27 + .../messages/TextSecureAttachmentPointer.java | 39 ++ .../messages/TextSecureAttachmentStream.java | 33 ++ .../api/messages/TextSecureGroup.java | 58 +++ .../api/messages/TextSecureMessage.java | 64 +++ .../textsecure/crypto/AttachmentCipher.java | 161 ------ .../crypto/AttachmentCipherInputStream.java | 20 +- .../crypto/AttachmentCipherOutputStream.java | 105 ++++ .../textsecure}/crypto/TextSecureCipher.java | 36 +- .../crypto}/UntrustedIdentityException.java | 2 +- .../textsecure/directory/BloomFilter.java | 86 ---- .../directory/DirectoryDescriptor.java | 26 - .../textsecure/directory/NumberFilter.java | 225 -------- .../push/IncomingEncryptedPushMessage.java | 10 +- .../textsecure/push/PushAddress.java | 2 +- .../textsecure/push/PushAttachmentData.java | 21 +- .../textsecure/push/PushServiceSocket.java | 35 +- .../textsecure/push/RawTransportDetails.java | 27 - .../exceptions}/EncapsulatedExceptions.java | 3 +- .../textsecure/storage/SessionUtil.java | 41 -- .../whispersystems/textsecure/util/Util.java | 11 +- .../securesms/ApplicationContext.java | 15 +- .../ApplicationPreferencesActivity.java | 2 +- .../securesms/AutoInitiateActivity.java | 4 +- .../securesms/ConversationActivity.java | 10 +- .../securesms/ConversationAdapter.java | 2 +- .../securesms/ConversationFragment.java | 12 +- .../securesms/ConversationItem.java | 14 +- .../securesms/ConversationListActivity.java | 2 +- .../securesms/ConversationListAdapter.java | 4 +- .../securesms/ConversationListFragment.java | 2 +- .../securesms/DatabaseUpgradeActivity.java | 46 +- .../securesms/ExportFragment.java | 5 +- .../securesms/GroupCreateActivity.java | 3 +- .../securesms/ImportExportActivity.java | 2 +- .../securesms/ImportFragment.java | 6 +- .../securesms/MediaPreviewActivity.java | 2 +- .../securesms/MmsPreferencesActivity.java | 11 +- .../securesms/NewConversationActivity.java | 2 +- .../securesms/PassphraseActivity.java | 4 +- .../securesms/PassphraseChangeActivity.java | 2 +- .../securesms/PassphraseCreateActivity.java | 4 +- .../securesms/PassphrasePromptActivity.java | 2 +- .../PassphraseRequiredActionBarActivity.java | 2 +- .../securesms/PassphraseRequiredActivity.java | 2 +- .../securesms/PassphraseRequiredMixin.java | 57 +-- ...assphraseRequiredSherlockListActivity.java | 2 +- .../securesms/ReceiveKeyActivity.java | 191 +++---- .../securesms/RegistrationActivity.java | 2 +- .../RegistrationProgressActivity.java | 2 +- .../securesms/RoutingActivity.java | 2 +- .../thoughtcrime/securesms/ShareActivity.java | 5 +- .../thoughtcrime/securesms/ShareFragment.java | 2 +- .../securesms/ShareListAdapter.java | 4 +- .../securesms/VerifyIdentityActivity.java | 6 +- .../securesms/ViewIdentityActivity.java | 2 +- .../securesms/ViewLocalIdentityActivity.java | 2 +- .../components/PushRegistrationReminder.java | 2 +- .../components/SystemSmsImportReminder.java | 2 +- .../crypto/AsymmetricMasterCipher.java | 3 - .../crypto/DecryptingPartInputStream.java | 2 - .../securesms/crypto/DecryptingQueue.java | 481 ------------------ .../crypto/EncryptingPartOutputStream.java | 2 - .../crypto/IdentityKeyParcelable.java | 2 +- .../securesms/crypto/IdentityKeyUtil.java | 2 - .../crypto/KeyExchangeInitiator.java | 6 +- .../crypto/KeyExchangeProcessor.java | 98 ---- .../securesms}/crypto/MasterCipher.java | 2 +- .../securesms}/crypto/MasterSecret.java | 2 +- .../securesms/crypto/MasterSecretUtil.java | 2 - .../securesms/crypto/MmsCipher.java | 134 +++++ .../securesms}/crypto/PreKeyUtil.java | 4 +- .../securesms}/crypto/PublicKey.java | 2 +- .../securesms/crypto/SecurityEvent.java | 43 ++ .../securesms/crypto/SmsCipher.java | 129 +++++ .../storage/TextSecureAxolotlStore.java | 128 +++++ .../TextSecureIdentityKeyStore.java | 5 +- .../storage/TextSecurePreKeyStore.java | 8 +- .../storage/TextSecureSessionStore.java | 9 +- .../securesms/database/DatabaseFactory.java | 7 +- .../securesms/database/DraftDatabase.java | 2 +- .../database/EncryptingPartDatabase.java | 2 +- .../database/EncryptingSmsDatabase.java | 13 +- .../securesms/database/GroupDatabase.java | 9 +- .../securesms/database/IdentityDatabase.java | 4 +- .../securesms/database/MmsDatabase.java | 31 +- .../securesms/database/MmsSmsColumns.java | 4 + .../securesms/database/MmsSmsDatabase.java | 2 +- .../database/PlaintextBackupExporter.java | 2 +- .../database/PlaintextBackupImporter.java | 4 +- .../securesms/database/PushDatabase.java | 32 ++ .../securesms/database/SmsDatabase.java | 4 +- .../securesms/database/SmsMigrator.java | 4 +- .../securesms/database/ThreadDatabase.java | 2 +- .../database/model/MessageRecord.java | 4 + .../securesms/gcm/GcmBroadcastReceiver.java | 54 +- .../groups/GroupMessageProcessor.java | 200 ++++++++ .../securesms/jobs/AttachmentDownloadJob.java | 154 ++++++ .../securesms/jobs/AvatarDownloadJob.java | 107 ++++ .../securesms/jobs/CreateSignedPreKeyJob.java | 4 +- .../securesms/jobs/MasterSecretJob.java | 24 + .../MmsDownloadJob.java} | 215 ++++---- .../securesms/jobs/MmsReceiveJob.java | 69 +++ .../securesms/jobs/PushDecryptJob.java | 252 +++++++++ .../securesms/jobs/PushReceiveJob.java | 98 ++++ .../securesms/jobs/SmsDecryptJob.java | 220 ++++++++ .../securesms/jobs/SmsReceiveJob.java | 110 ++++ .../securesms/jobs/SmsSendJob.java | 48 ++ .../EncryptingJobSerializer.java | 6 +- .../requirements/MasterSecretRequirement.java | 26 + .../MasterSecretRequirementProvider.java | 36 ++ .../securesms/mms/ImageSlide.java | 2 +- .../securesms/mms/IncomingMediaMessage.java | 65 ++- .../thoughtcrime/securesms/mms/MmsRadio.java | 2 + src/org/thoughtcrime/securesms/mms/Slide.java | 2 +- .../thoughtcrime/securesms/mms/SlideDeck.java | 2 +- .../thoughtcrime/securesms/mms/TextSlide.java | 2 +- .../notifications/MarkReadReceiver.java | 2 +- .../notifications/MessageNotifier.java | 3 +- .../notifications/NotificationState.java | 2 +- .../securesms/providers/PartProvider.java | 18 +- .../TextSecureMessageReceiverFactory.java | 21 + .../push/TextSecureMessageSenderFactory.java | 43 ++ .../service/ApplicationMigrationService.java | 2 +- .../securesms/service/AvatarDownloader.java | 85 ---- .../securesms/service/GroupReceiver.java | 161 ------ .../securesms/service/KeyCachingService.java | 60 ++- .../securesms/service/MmsListener.java | 9 +- .../securesms/service/MmsReceiver.java | 81 --- .../securesms/service/MmsSender.java | 4 +- .../securesms/service/PreKeyService.java | 6 +- .../securesms/service/PushDownloader.java | 117 ----- .../securesms/service/PushReceiver.java | 322 ------------ .../service/RegistrationService.java | 4 +- .../securesms/service/SendReceiveService.java | 139 ++--- .../securesms/service/SmsListener.java | 33 +- .../securesms/service/SmsReceiver.java | 233 --------- .../securesms/service/SmsSender.java | 10 +- .../securesms/sms/IncomingTextMessage.java | 16 +- .../securesms/sms/MessageSender.java | 11 +- .../sms/MultipartSmsMessageHandler.java | 27 +- .../InsecureFallbackApprovalException.java | 3 + .../securesms/transport/MmsTransport.java | 78 +-- .../securesms/transport/PushTransport.java | 362 ++++--------- .../securesms/transport/SmsTransport.java | 40 +- .../transport/UniversalTransport.java | 68 +-- .../securesms/util/MemoryCleaner.java | 7 +- .../securesms/util/SaveAttachmentTask.java | 2 +- 152 files changed, 3521 insertions(+), 3280 deletions(-) create mode 100644 library/src/org/whispersystems/textsecure/api/TextSecureMessageReceiver.java create mode 100644 library/src/org/whispersystems/textsecure/api/TextSecureMessageSender.java create mode 100644 library/src/org/whispersystems/textsecure/api/messages/TextSecureAttachment.java create mode 100644 library/src/org/whispersystems/textsecure/api/messages/TextSecureAttachmentPointer.java create mode 100644 library/src/org/whispersystems/textsecure/api/messages/TextSecureAttachmentStream.java create mode 100644 library/src/org/whispersystems/textsecure/api/messages/TextSecureGroup.java create mode 100644 library/src/org/whispersystems/textsecure/api/messages/TextSecureMessage.java delete mode 100644 library/src/org/whispersystems/textsecure/crypto/AttachmentCipher.java create mode 100644 library/src/org/whispersystems/textsecure/crypto/AttachmentCipherOutputStream.java rename {src/org/thoughtcrime/securesms => library/src/org/whispersystems/textsecure}/crypto/TextSecureCipher.java (51%) rename {src/org/thoughtcrime/securesms/transport => library/src/org/whispersystems/textsecure/crypto}/UntrustedIdentityException.java (93%) delete mode 100644 library/src/org/whispersystems/textsecure/directory/BloomFilter.java delete mode 100644 library/src/org/whispersystems/textsecure/directory/DirectoryDescriptor.java delete mode 100644 library/src/org/whispersystems/textsecure/directory/NumberFilter.java delete mode 100644 library/src/org/whispersystems/textsecure/push/RawTransportDetails.java rename {src/org/thoughtcrime/securesms/transport => library/src/org/whispersystems/textsecure/push/exceptions}/EncapsulatedExceptions.java (86%) delete mode 100644 library/src/org/whispersystems/textsecure/storage/SessionUtil.java delete mode 100644 src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java rename {library/src/org/whispersystems/textsecure => src/org/thoughtcrime/securesms}/crypto/IdentityKeyParcelable.java (97%) delete mode 100644 src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java rename {library/src/org/whispersystems/textsecure => src/org/thoughtcrime/securesms}/crypto/MasterCipher.java (99%) rename {library/src/org/whispersystems/textsecure => src/org/thoughtcrime/securesms}/crypto/MasterSecret.java (98%) create mode 100644 src/org/thoughtcrime/securesms/crypto/MmsCipher.java rename {library/src/org/whispersystems/textsecure => src/org/thoughtcrime/securesms}/crypto/PreKeyUtil.java (98%) rename {library/src/org/whispersystems/textsecure => src/org/thoughtcrime/securesms}/crypto/PublicKey.java (98%) create mode 100644 src/org/thoughtcrime/securesms/crypto/SecurityEvent.java create mode 100644 src/org/thoughtcrime/securesms/crypto/SmsCipher.java create mode 100644 src/org/thoughtcrime/securesms/crypto/storage/TextSecureAxolotlStore.java rename src/org/thoughtcrime/securesms/crypto/{ => storage}/TextSecureIdentityKeyStore.java (88%) rename {library/src/org/whispersystems/textsecure => src/org/thoughtcrime/securesms/crypto}/storage/TextSecurePreKeyStore.java (96%) rename {library/src/org/whispersystems/textsecure => src/org/thoughtcrime/securesms/crypto}/storage/TextSecureSessionStore.java (95%) create mode 100644 src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java create mode 100644 src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java create mode 100644 src/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java create mode 100644 src/org/thoughtcrime/securesms/jobs/MasterSecretJob.java rename src/org/thoughtcrime/securesms/{service/MmsDownloader.java => jobs/MmsDownloadJob.java} (51%) create mode 100644 src/org/thoughtcrime/securesms/jobs/MmsReceiveJob.java create mode 100644 src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java create mode 100644 src/org/thoughtcrime/securesms/jobs/PushReceiveJob.java create mode 100644 src/org/thoughtcrime/securesms/jobs/SmsDecryptJob.java create mode 100644 src/org/thoughtcrime/securesms/jobs/SmsReceiveJob.java create mode 100644 src/org/thoughtcrime/securesms/jobs/SmsSendJob.java rename src/org/thoughtcrime/securesms/jobs/{ => persistence}/EncryptingJobSerializer.java (91%) create mode 100644 src/org/thoughtcrime/securesms/jobs/requirements/MasterSecretRequirement.java create mode 100644 src/org/thoughtcrime/securesms/jobs/requirements/MasterSecretRequirementProvider.java create mode 100644 src/org/thoughtcrime/securesms/push/TextSecureMessageReceiverFactory.java create mode 100644 src/org/thoughtcrime/securesms/push/TextSecureMessageSenderFactory.java delete mode 100644 src/org/thoughtcrime/securesms/service/AvatarDownloader.java delete mode 100644 src/org/thoughtcrime/securesms/service/GroupReceiver.java delete mode 100644 src/org/thoughtcrime/securesms/service/MmsReceiver.java delete mode 100644 src/org/thoughtcrime/securesms/service/PushDownloader.java delete mode 100644 src/org/thoughtcrime/securesms/service/PushReceiver.java delete mode 100644 src/org/thoughtcrime/securesms/service/SmsReceiver.java diff --git a/jobqueue/src/main/java/org/whispersystems/jobqueue/JobConsumer.java b/jobqueue/src/main/java/org/whispersystems/jobqueue/JobConsumer.java index 648027dfec..4bbbaff931 100644 --- a/jobqueue/src/main/java/org/whispersystems/jobqueue/JobConsumer.java +++ b/jobqueue/src/main/java/org/whispersystems/jobqueue/JobConsumer.java @@ -16,10 +16,14 @@ */ package org.whispersystems.jobqueue; +import android.util.Log; + import org.whispersystems.jobqueue.persistence.PersistentStorage; public class JobConsumer extends Thread { + private static final String TAG = JobConsumer.class.getSimpleName(); + enum JobResult { SUCCESS, FAILURE, @@ -69,6 +73,7 @@ public class JobConsumer extends Thread { job.onRun(); return JobResult.SUCCESS; } catch (Throwable throwable) { + Log.w(TAG, throwable); if (!job.onShouldRetry(throwable)) { return JobResult.FAILURE; } else if (!job.isRequirementsMet()) { diff --git a/jobqueue/src/main/java/org/whispersystems/jobqueue/JobManager.java b/jobqueue/src/main/java/org/whispersystems/jobqueue/JobManager.java index 5c4b1e2e21..9a78907505 100644 --- a/jobqueue/src/main/java/org/whispersystems/jobqueue/JobManager.java +++ b/jobqueue/src/main/java/org/whispersystems/jobqueue/JobManager.java @@ -39,14 +39,16 @@ public class JobManager implements RequirementListener { private final PersistentStorage persistentStorage; public JobManager(Context context, String name, - RequirementProvider requirementProvider, + List requirementProviders, JobSerializer jobSerializer, int consumers) { this.persistentStorage = new PersistentStorage(context, name, jobSerializer); eventExecutor.execute(new LoadTask(null)); - if (requirementProvider != null) { - requirementProvider.setListener(this); + if (requirementProviders != null && !requirementProviders.isEmpty()) { + for (RequirementProvider provider : requirementProviders) { + provider.setListener(this); + } } for (int i=0;i attachments = new LinkedList<>(); + boolean endSession = ((content.getFlags() & PushMessageContent.Flags.END_SESSION_VALUE) != 0); + boolean secure = signal.isSecureMessage() || signal.isPreKeyBundle(); + + for (AttachmentPointer pointer : content.getAttachmentsList()) { + attachments.add(new TextSecureAttachmentPointer(pointer.getId(), + pointer.getContentType(), + pointer.getKey().toByteArray(), + signal.getRelay())); + } + + return new TextSecureMessage(signal.getTimestampMillis(), groupInfo, attachments, + content.getBody(), secure, endSession); + } + + private TextSecureGroup createGroupInfo(IncomingPushMessage signal, PushMessageContent content) { + if (!content.hasGroup()) return null; + + TextSecureGroup.Type type; + + switch (content.getGroup().getType()) { + case DELIVER: type = TextSecureGroup.Type.DELIVER; break; + case UPDATE: type = TextSecureGroup.Type.UPDATE; break; + case QUIT: type = TextSecureGroup.Type.QUIT; break; + default: type = TextSecureGroup.Type.UNKNOWN; break; + } + + if (content.getGroup().getType() != DELIVER) { + String name = null; + List members = null; + TextSecureAttachmentPointer avatar = null; + + if (content.getGroup().hasName()) { + name = content.getGroup().getName(); + } + + if (content.getGroup().getMembersCount() > 0) { + members = content.getGroup().getMembersList(); + } + + if (content.getGroup().hasAvatar()) { + avatar = new TextSecureAttachmentPointer(content.getGroup().getAvatar().getId(), + content.getGroup().getAvatar().getContentType(), + content.getGroup().getAvatar().getKey().toByteArray(), + signal.getRelay()); + } + + return new TextSecureGroup(type, content.getGroup().getId().toByteArray(), name, members, avatar); + } + + return new TextSecureGroup(content.getGroup().getId().toByteArray()); + } + +} diff --git a/library/src/org/whispersystems/textsecure/api/TextSecureMessageSender.java b/library/src/org/whispersystems/textsecure/api/TextSecureMessageSender.java new file mode 100644 index 0000000000..37515000a6 --- /dev/null +++ b/library/src/org/whispersystems/textsecure/api/TextSecureMessageSender.java @@ -0,0 +1,303 @@ +package org.whispersystems.textsecure.api; + +import android.content.Context; +import android.util.Log; + +import com.google.protobuf.ByteString; + +import org.whispersystems.libaxolotl.InvalidKeyException; +import org.whispersystems.libaxolotl.SessionBuilder; +import org.whispersystems.libaxolotl.protocol.CiphertextMessage; +import org.whispersystems.libaxolotl.state.AxolotlStore; +import org.whispersystems.libaxolotl.state.PreKeyBundle; +import org.whispersystems.libaxolotl.util.guava.Optional; +import org.whispersystems.textsecure.api.messages.TextSecureAttachment; +import org.whispersystems.textsecure.api.messages.TextSecureAttachmentStream; +import org.whispersystems.textsecure.api.messages.TextSecureGroup; +import org.whispersystems.textsecure.api.messages.TextSecureMessage; +import org.whispersystems.textsecure.crypto.TextSecureCipher; +import org.whispersystems.textsecure.crypto.UntrustedIdentityException; +import org.whispersystems.textsecure.push.MismatchedDevices; +import org.whispersystems.textsecure.push.OutgoingPushMessage; +import org.whispersystems.textsecure.push.OutgoingPushMessageList; +import org.whispersystems.textsecure.push.PushAddress; +import org.whispersystems.textsecure.push.PushAttachmentData; +import org.whispersystems.textsecure.push.PushBody; +import org.whispersystems.textsecure.push.PushServiceSocket; +import org.whispersystems.textsecure.push.StaleDevices; +import org.whispersystems.textsecure.push.UnregisteredUserException; +import org.whispersystems.textsecure.push.exceptions.EncapsulatedExceptions; +import org.whispersystems.textsecure.push.exceptions.MismatchedDevicesException; +import org.whispersystems.textsecure.push.exceptions.StaleDevicesException; +import org.whispersystems.textsecure.util.Util; + +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; + +import static org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.Type; +import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent; +import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer; +import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext; + +public class TextSecureMessageSender { + + private static final String TAG = TextSecureMessageSender.class.getSimpleName(); + + private final PushServiceSocket socket; + private final AxolotlStore store; + private final Optional eventListener; + + public TextSecureMessageSender(Context context, String url, + PushServiceSocket.TrustStore trustStore, + String user, String password, + AxolotlStore store, + Optional eventListener) + { + this.socket = new PushServiceSocket(context, url, trustStore, user, password); + this.store = store; + this.eventListener = eventListener; + } + + public void sendMessage(PushAddress recipient, TextSecureMessage message) + throws UntrustedIdentityException, IOException + { + byte[] content = createMessageContent(message); + sendMessage(recipient, message.getTimestamp(), content); + + if (message.isEndSession()) { + store.deleteAllSessions(recipient.getRecipientId()); + + if (eventListener.isPresent()) { + eventListener.get().onSecurityEvent(recipient.getRecipientId()); + } + } + } + + public void sendMessage(List recipients, TextSecureMessage message) + throws IOException, EncapsulatedExceptions + { + byte[] content = createMessageContent(message); + sendMessage(recipients, message.getTimestamp(), content); + } + + private byte[] createMessageContent(TextSecureMessage message) throws IOException { + PushMessageContent.Builder builder = PushMessageContent.newBuilder(); + List pointers = createAttachmentPointers(message.getAttachments()); + + if (!pointers.isEmpty()) { + builder.addAllAttachments(pointers); + } + + if (message.getBody().isPresent()) { + builder.setBody(message.getBody().get()); + } + + if (message.getGroupInfo().isPresent()) { + builder.setGroup(createGroupContent(message.getGroupInfo().get())); + } + + if (message.isEndSession()) { + builder.setFlags(PushMessageContent.Flags.END_SESSION_VALUE); + } + + return builder.build().toByteArray(); + } + + private GroupContext createGroupContent(TextSecureGroup group) throws IOException { + GroupContext.Builder builder = GroupContext.newBuilder(); + builder.setId(ByteString.copyFrom(group.getGroupId())); + + if (group.getType() != TextSecureGroup.Type.DELIVER) { + if (group.getType() == TextSecureGroup.Type.UPDATE) builder.setType(GroupContext.Type.UPDATE); + else if (group.getType() == TextSecureGroup.Type.QUIT) builder.setType(GroupContext.Type.QUIT); + else throw new AssertionError("Unknown type: " + group.getType()); + + if (group.getName().isPresent()) builder.setName(group.getName().get()); + if (group.getMembers().isPresent()) builder.addAllMembers(group.getMembers().get()); + + if (group.getAvatar().isPresent() && group.getAvatar().get().isStream()) { + AttachmentPointer pointer = createAttachmentPointer(group.getAvatar().get().asStream()); + builder.setAvatar(pointer); + } + } else { + builder.setType(GroupContext.Type.DELIVER); + } + + return builder.build(); + } + + private void sendMessage(List recipients, long timestamp, byte[] content) + throws IOException, EncapsulatedExceptions + { + List untrustedIdentities = new LinkedList<>(); + List unregisteredUsers = new LinkedList<>(); + + for (PushAddress recipient : recipients) { + try { + sendMessage(recipient, timestamp, content); + } catch (UntrustedIdentityException e) { + Log.w(TAG, e); + untrustedIdentities.add(e); + } catch (UnregisteredUserException e) { + Log.w(TAG, e); + unregisteredUsers.add(e); + } + } + + if (!untrustedIdentities.isEmpty() || !unregisteredUsers.isEmpty()) { + throw new EncapsulatedExceptions(untrustedIdentities, unregisteredUsers); + } + } + + private void sendMessage(PushAddress recipient, long timestamp, byte[] content) + throws UntrustedIdentityException, IOException + { + for (int i=0;i<3;i++) { + try { + OutgoingPushMessageList messages = getEncryptedMessages(socket, recipient, timestamp, content); + socket.sendMessage(messages); + + return; + } catch (MismatchedDevicesException mde) { + Log.w(TAG, mde); + handleMismatchedDevices(socket, recipient, mde.getMismatchedDevices()); + } catch (StaleDevicesException ste) { + Log.w(TAG, ste); + handleStaleDevices(recipient, ste.getStaleDevices()); + } + } + } + + private List createAttachmentPointers(Optional> attachments) throws IOException { + List pointers = new LinkedList<>(); + + if (!attachments.isPresent() || attachments.get().isEmpty()) { + return pointers; + } + + for (TextSecureAttachment attachment : attachments.get()) { + if (attachment.isStream()) { + pointers.add(createAttachmentPointer(attachment.asStream())); + } + } + + return pointers; + } + + private AttachmentPointer createAttachmentPointer(TextSecureAttachmentStream attachment) + throws IOException + { + byte[] attachmentKey = Util.getSecretBytes(64); + PushAttachmentData attachmentData = new PushAttachmentData(attachment.getContentType(), + attachment.getInputStream(), + attachment.getLength(), + attachmentKey); + + long attachmentId = socket.sendAttachment(attachmentData); + + return AttachmentPointer.newBuilder() + .setContentType(attachment.getContentType()) + .setId(attachmentId) + .setKey(ByteString.copyFrom(attachmentKey)) + .build(); + } + + + private OutgoingPushMessageList getEncryptedMessages(PushServiceSocket socket, + PushAddress recipient, + long timestamp, + byte[] plaintext) + throws IOException, UntrustedIdentityException + { + PushBody masterBody = getEncryptedMessage(socket, recipient, plaintext); + + List messages = new LinkedList<>(); + messages.add(new OutgoingPushMessage(recipient, masterBody)); + + for (int deviceId : store.getSubDeviceSessions(recipient.getRecipientId())) { + PushAddress device = new PushAddress(recipient.getRecipientId(), recipient.getNumber(), deviceId, recipient.getRelay()); + PushBody body = getEncryptedMessage(socket, device, plaintext); + + messages.add(new OutgoingPushMessage(device, body)); + } + + return new OutgoingPushMessageList(recipient.getNumber(), timestamp, recipient.getRelay(), messages); + } + + private PushBody getEncryptedMessage(PushServiceSocket socket, PushAddress recipient, byte[] plaintext) + throws IOException, UntrustedIdentityException + { + if (!store.containsSession(recipient.getRecipientId(), recipient.getDeviceId())) { + try { + List preKeys = socket.getPreKeys(recipient); + + for (PreKeyBundle preKey : preKeys) { + try { + SessionBuilder sessionBuilder = new SessionBuilder(store, recipient.getRecipientId(), recipient.getDeviceId()); + sessionBuilder.process(preKey); + } catch (org.whispersystems.libaxolotl.UntrustedIdentityException e) { + throw new UntrustedIdentityException("Untrusted identity key!", recipient.getNumber(), preKey.getIdentityKey()); + } + } + + if (eventListener.isPresent()) { + eventListener.get().onSecurityEvent(recipient.getRecipientId()); + } + } catch (InvalidKeyException e) { + throw new IOException(e); + } + } + + TextSecureCipher cipher = new TextSecureCipher(store, recipient); + CiphertextMessage message = cipher.encrypt(plaintext); + int remoteRegistrationId = cipher.getRemoteRegistrationId(); + + if (message.getType() == CiphertextMessage.PREKEY_TYPE) { + return new PushBody(Type.PREKEY_BUNDLE_VALUE, remoteRegistrationId, message.serialize()); + } else if (message.getType() == CiphertextMessage.WHISPER_TYPE) { + return new PushBody(Type.CIPHERTEXT_VALUE, remoteRegistrationId, message.serialize()); + } else { + throw new AssertionError("Unknown ciphertext type: " + message.getType()); + } + } + + private void handleMismatchedDevices(PushServiceSocket socket, PushAddress recipient, + MismatchedDevices mismatchedDevices) + throws IOException, UntrustedIdentityException + { + try { + for (int extraDeviceId : mismatchedDevices.getExtraDevices()) { + store.deleteSession(recipient.getRecipientId(), extraDeviceId); + } + + for (int missingDeviceId : mismatchedDevices.getMissingDevices()) { + PushAddress device = new PushAddress(recipient.getRecipientId(), recipient.getNumber(), + missingDeviceId, recipient.getRelay()); + PreKeyBundle preKey = socket.getPreKey(device); + + try { + SessionBuilder sessionBuilder = new SessionBuilder(store, device.getRecipientId(), device.getDeviceId()); + sessionBuilder.process(preKey); + } catch (org.whispersystems.libaxolotl.UntrustedIdentityException e) { + throw new UntrustedIdentityException("Untrusted identity key!", recipient.getNumber(), preKey.getIdentityKey()); + } + } + } catch (InvalidKeyException e) { + throw new IOException(e); + } + } + + private void handleStaleDevices(PushAddress recipient, StaleDevices staleDevices) { + long recipientId = recipient.getRecipientId(); + + for (int staleDeviceId : staleDevices.getStaleDevices()) { + store.deleteSession(recipientId, staleDeviceId); + } + } + + public static interface EventListener { + public void onSecurityEvent(long recipientId); + } + +} diff --git a/library/src/org/whispersystems/textsecure/api/messages/TextSecureAttachment.java b/library/src/org/whispersystems/textsecure/api/messages/TextSecureAttachment.java new file mode 100644 index 0000000000..138314c6d8 --- /dev/null +++ b/library/src/org/whispersystems/textsecure/api/messages/TextSecureAttachment.java @@ -0,0 +1,27 @@ +package org.whispersystems.textsecure.api.messages; + +import java.io.InputStream; + +public abstract class TextSecureAttachment { + + private final String contentType; + + protected TextSecureAttachment(String contentType) { + this.contentType = contentType; + } + + public String getContentType() { + return contentType; + } + + public abstract boolean isStream(); + public abstract boolean isPointer(); + + public TextSecureAttachmentStream asStream() { + return (TextSecureAttachmentStream)this; + } + + public TextSecureAttachmentPointer asPointer() { + return (TextSecureAttachmentPointer)this; + } +} diff --git a/library/src/org/whispersystems/textsecure/api/messages/TextSecureAttachmentPointer.java b/library/src/org/whispersystems/textsecure/api/messages/TextSecureAttachmentPointer.java new file mode 100644 index 0000000000..a1d403c7fd --- /dev/null +++ b/library/src/org/whispersystems/textsecure/api/messages/TextSecureAttachmentPointer.java @@ -0,0 +1,39 @@ +package org.whispersystems.textsecure.api.messages; + +import org.whispersystems.libaxolotl.util.guava.Optional; + +public class TextSecureAttachmentPointer extends TextSecureAttachment { + + private final long id; + private final byte[] key; + private final Optional relay; + + public TextSecureAttachmentPointer(long id, String contentType, byte[] key, String relay) { + super(contentType); + this.id = id; + this.key = key; + this.relay = Optional.fromNullable(relay); + } + + public long getId() { + return id; + } + + public byte[] getKey() { + return key; + } + + @Override + public boolean isStream() { + return false; + } + + @Override + public boolean isPointer() { + return true; + } + + public Optional getRelay() { + return relay; + } +} diff --git a/library/src/org/whispersystems/textsecure/api/messages/TextSecureAttachmentStream.java b/library/src/org/whispersystems/textsecure/api/messages/TextSecureAttachmentStream.java new file mode 100644 index 0000000000..1e54494fd5 --- /dev/null +++ b/library/src/org/whispersystems/textsecure/api/messages/TextSecureAttachmentStream.java @@ -0,0 +1,33 @@ +package org.whispersystems.textsecure.api.messages; + +import java.io.InputStream; + +public class TextSecureAttachmentStream extends TextSecureAttachment { + + private final InputStream inputStream; + private final long length; + + public TextSecureAttachmentStream(InputStream inputStream, String contentType, long length) { + super(contentType); + this.inputStream = inputStream; + this.length = length; + } + + @Override + public boolean isStream() { + return false; + } + + @Override + public boolean isPointer() { + return true; + } + + public InputStream getInputStream() { + return inputStream; + } + + public long getLength() { + return length; + } +} diff --git a/library/src/org/whispersystems/textsecure/api/messages/TextSecureGroup.java b/library/src/org/whispersystems/textsecure/api/messages/TextSecureGroup.java new file mode 100644 index 0000000000..40895b51de --- /dev/null +++ b/library/src/org/whispersystems/textsecure/api/messages/TextSecureGroup.java @@ -0,0 +1,58 @@ +package org.whispersystems.textsecure.api.messages; + +import org.whispersystems.libaxolotl.util.guava.Optional; + +import java.util.List; + +public class TextSecureGroup { + + public enum Type { + UNKNOWN, + UPDATE, + DELIVER, + QUIT + } + + private final byte[] groupId; + private final Type type; + private final Optional name; + private final Optional> members; + private final Optional avatar; + + + public TextSecureGroup(byte[] groupId) { + this(Type.DELIVER, groupId, null, null, null); + } + + public TextSecureGroup(Type type, byte[] groupId, String name, + List members, + TextSecureAttachment avatar) + { + this.type = type; + this.groupId = groupId; + this.name = Optional.fromNullable(name); + this.members = Optional.fromNullable(members); + this.avatar = Optional.fromNullable(avatar); + } + + public byte[] getGroupId() { + return groupId; + } + + public Type getType() { + return type; + } + + public Optional getName() { + return name; + } + + public Optional> getMembers() { + return members; + } + + public Optional getAvatar() { + return avatar; + } + +} diff --git a/library/src/org/whispersystems/textsecure/api/messages/TextSecureMessage.java b/library/src/org/whispersystems/textsecure/api/messages/TextSecureMessage.java new file mode 100644 index 0000000000..124556c6fd --- /dev/null +++ b/library/src/org/whispersystems/textsecure/api/messages/TextSecureMessage.java @@ -0,0 +1,64 @@ +package org.whispersystems.textsecure.api.messages; + +import org.whispersystems.libaxolotl.util.guava.Optional; + +import java.util.List; + +public class TextSecureMessage { + + private final long timestamp; + private final Optional> attachments; + private final Optional body; + private final Optional group; + private final boolean secure; + private final boolean endSession; + + public TextSecureMessage(long timestamp, String body) { + this(timestamp, null, body); + } + + public TextSecureMessage(long timestamp, List attachments, String body) { + this(timestamp, null, attachments, body); + } + + public TextSecureMessage(long timestamp, TextSecureGroup group, List attachments, String body) { + this(timestamp, group, attachments, body, true, false); + } + + public TextSecureMessage(long timestamp, TextSecureGroup group, List attachments, String body, boolean secure, boolean endSession) { + this.timestamp = timestamp; + this.attachments = Optional.fromNullable(attachments); + this.body = Optional.fromNullable(body); + this.group = Optional.fromNullable(group); + this.secure = secure; + this.endSession = endSession; + } + + public long getTimestamp() { + return timestamp; + } + + public Optional> getAttachments() { + return attachments; + } + + public Optional getBody() { + return body; + } + + public Optional getGroupInfo() { + return group; + } + + public boolean isSecure() { + return secure; + } + + public boolean isEndSession() { + return endSession; + } + + public boolean isGroupUpdate() { + return group.isPresent() && group.get().getType() != TextSecureGroup.Type.DELIVER; + } +} diff --git a/library/src/org/whispersystems/textsecure/crypto/AttachmentCipher.java b/library/src/org/whispersystems/textsecure/crypto/AttachmentCipher.java deleted file mode 100644 index 9ad4f3e98b..0000000000 --- a/library/src/org/whispersystems/textsecure/crypto/AttachmentCipher.java +++ /dev/null @@ -1,161 +0,0 @@ -/** - * Copyright (C) 2013 Open Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.whispersystems.textsecure.crypto; - -import android.util.Log; - -import org.whispersystems.libaxolotl.InvalidMacException; -import org.whispersystems.libaxolotl.InvalidMessageException; -import org.whispersystems.textsecure.util.Hex; -import org.whispersystems.textsecure.util.Util; - -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.Mac; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.text.ParseException; -import java.util.Arrays; - -/** - * Encrypts push attachments. - * - * @author Moxie Marlinspike - */ -public class AttachmentCipher { - - static final int CIPHER_KEY_SIZE = 32; - static final int MAC_KEY_SIZE = 32; - - private final SecretKeySpec cipherKey; - private final SecretKeySpec macKey; - private final Cipher cipher; - private final Mac mac; - - public AttachmentCipher() { - this.cipherKey = initializeRandomCipherKey(); - this.macKey = initializeRandomMacKey(); - this.cipher = initializeCipher(); - this.mac = initializeMac(); - } - - public AttachmentCipher(byte[] combinedKeyMaterial) { - byte[][] parts = Util.split(combinedKeyMaterial, CIPHER_KEY_SIZE, MAC_KEY_SIZE); - this.cipherKey = new SecretKeySpec(parts[0], "AES"); - this.macKey = new SecretKeySpec(parts[1], "HmacSHA256"); - this.cipher = initializeCipher(); - this.mac = initializeMac(); - } - - public byte[] getCombinedKeyMaterial() { - return Util.combine(this.cipherKey.getEncoded(), this.macKey.getEncoded()); - } - - public byte[] encrypt(byte[] plaintext) { - try { - this.cipher.init(Cipher.ENCRYPT_MODE, this.cipherKey); - this.mac.init(this.macKey); - - byte[] ciphertext = this.cipher.doFinal(plaintext); - byte[] iv = this.cipher.getIV(); - byte[] mac = this.mac.doFinal(Util.combine(iv, ciphertext)); - - return Util.combine(iv, ciphertext, mac); - } catch (IllegalBlockSizeException e) { - throw new AssertionError(e); - } catch (BadPaddingException e) { - throw new AssertionError(e); - } catch (InvalidKeyException e) { - throw new AssertionError(e); - } - } - - public byte[] decrypt(byte[] ciphertext) - throws InvalidMacException, InvalidMessageException - { - try { - if (ciphertext.length <= cipher.getBlockSize() + mac.getMacLength()) { - throw new InvalidMessageException("Message too short!"); - } - - byte[][] ciphertextParts = Util.split(ciphertext, - this.cipher.getBlockSize(), - ciphertext.length - this.cipher.getBlockSize() - this.mac.getMacLength(), - this.mac.getMacLength()); - - this.mac.update(ciphertext, 0, ciphertext.length - mac.getMacLength()); - byte[] ourMac = this.mac.doFinal(); - - if (!Arrays.equals(ourMac, ciphertextParts[2])) { - throw new InvalidMacException("Mac doesn't match!"); - } - - this.cipher.init(Cipher.DECRYPT_MODE, this.cipherKey, - new IvParameterSpec(ciphertextParts[0])); - - return cipher.doFinal(ciphertextParts[1]); - } catch (InvalidKeyException e) { - throw new AssertionError(e); - } catch (InvalidAlgorithmParameterException e) { - throw new AssertionError(e); - } catch (IllegalBlockSizeException e) { - throw new AssertionError(e); - } catch (BadPaddingException e) { - throw new InvalidMessageException(e); - } catch (ParseException e) { - throw new InvalidMessageException(e); - } - } - - private Mac initializeMac() { - try { - Mac mac = Mac.getInstance("HmacSHA256"); - return mac; - } catch (NoSuchAlgorithmException e) { - throw new AssertionError(e); - } - } - - private Cipher initializeCipher() { - try { - Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); - return cipher; - } catch (NoSuchAlgorithmException e) { - throw new AssertionError(e); - } catch (NoSuchPaddingException e) { - throw new AssertionError(e); - } - } - - private SecretKeySpec initializeRandomCipherKey() { - byte[] key = new byte[CIPHER_KEY_SIZE]; - Util.getSecureRandom().nextBytes(key); - return new SecretKeySpec(key, "AES"); - } - - private SecretKeySpec initializeRandomMacKey() { - byte[] key = new byte[MAC_KEY_SIZE]; - Util.getSecureRandom().nextBytes(key); - return new SecretKeySpec(key, "HmacSHA256"); - } - -} diff --git a/library/src/org/whispersystems/textsecure/crypto/AttachmentCipherInputStream.java b/library/src/org/whispersystems/textsecure/crypto/AttachmentCipherInputStream.java index c28554656b..2d72a17bdd 100644 --- a/library/src/org/whispersystems/textsecure/crypto/AttachmentCipherInputStream.java +++ b/library/src/org/whispersystems/textsecure/crypto/AttachmentCipherInputStream.java @@ -48,13 +48,15 @@ import javax.crypto.spec.SecretKeySpec; public class AttachmentCipherInputStream extends FileInputStream { - private static final int BLOCK_SIZE = 16; + private static final int BLOCK_SIZE = 16; + private static final int CIPHER_KEY_SIZE = 32; + private static final int MAC_KEY_SIZE = 32; private Cipher cipher; private boolean done; private long totalDataSize; private long totalRead; - private byte[] overflowBuffer; + private byte[] overflowBuffer; public AttachmentCipherInputStream(File file, byte[] combinedKeyMaterial) throws IOException, InvalidMessageException @@ -62,11 +64,9 @@ public class AttachmentCipherInputStream extends FileInputStream { super(file); try { - byte[][] parts = Util.split(combinedKeyMaterial, - AttachmentCipher.CIPHER_KEY_SIZE, - AttachmentCipher.MAC_KEY_SIZE); + byte[][] parts = Util.split(combinedKeyMaterial, CIPHER_KEY_SIZE, MAC_KEY_SIZE); + Mac mac = Mac.getInstance("HmacSHA256"); - Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(parts[1], "HmacSHA256")); if (file.length() <= BLOCK_SIZE + mac.getMacLength()) { @@ -84,16 +84,10 @@ public class AttachmentCipherInputStream extends FileInputStream { this.done = false; this.totalRead = 0; this.totalDataSize = file.length() - cipher.getBlockSize() - mac.getMacLength(); - } catch (NoSuchAlgorithmException e) { - throw new AssertionError(e); - } catch (InvalidKeyException e) { + } catch (NoSuchAlgorithmException | InvalidKeyException | NoSuchPaddingException | InvalidAlgorithmParameterException e) { throw new AssertionError(e); } catch (InvalidMacException e) { throw new InvalidMessageException(e); - } catch (NoSuchPaddingException e) { - throw new AssertionError(e); - } catch (InvalidAlgorithmParameterException e) { - throw new AssertionError(e); } } diff --git a/library/src/org/whispersystems/textsecure/crypto/AttachmentCipherOutputStream.java b/library/src/org/whispersystems/textsecure/crypto/AttachmentCipherOutputStream.java new file mode 100644 index 0000000000..08be47fbed --- /dev/null +++ b/library/src/org/whispersystems/textsecure/crypto/AttachmentCipherOutputStream.java @@ -0,0 +1,105 @@ +package org.whispersystems.textsecure.crypto; + +import org.whispersystems.textsecure.util.Util; + +import java.io.IOException; +import java.io.OutputStream; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.Mac; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.SecretKeySpec; + +public class AttachmentCipherOutputStream extends OutputStream { + + private final Cipher cipher; + private final Mac mac; + private final OutputStream outputStream; + + private long ciphertextLength = 0; + + public AttachmentCipherOutputStream(byte[] combinedKeyMaterial, + OutputStream outputStream) + throws IOException + { + try { + this.outputStream = outputStream; + this.cipher = initializeCipher(); + this.mac = initializeMac(); + + byte[][] keyParts = Util.split(combinedKeyMaterial, 32, 32); + + this.cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyParts[0], "AES")); + this.mac.init(new SecretKeySpec(keyParts[1], "HmacSHA256")); + + mac.update(cipher.getIV()); + outputStream.write(cipher.getIV()); + ciphertextLength += cipher.getIV().length; + } catch (InvalidKeyException e) { + throw new AssertionError(e); + } + } + + @Override + public void write(byte[] buffer) throws IOException { + write(buffer, 0, buffer.length); + } + + @Override + public void write(byte[] buffer, int offset, int length) throws IOException { + byte[] ciphertext = cipher.update(buffer, offset, length); + + if (ciphertext != null) { + mac.update(ciphertext); + outputStream.write(ciphertext); + ciphertextLength += ciphertext.length; + } + } + + @Override + public void write(int b) { + throw new AssertionError("NYI"); + } + + @Override + public void flush() throws IOException { + try { + byte[] ciphertext = cipher.doFinal(); + byte[] auth = mac.doFinal(ciphertext); + + outputStream.write(ciphertext); + outputStream.write(auth); + + ciphertextLength += ciphertext.length; + ciphertextLength += auth.length; + + outputStream.flush(); + } catch (IllegalBlockSizeException | BadPaddingException e) { + throw new AssertionError(e); + } + } + + public static long getCiphertextLength(long plaintextLength) { + return 16 + (((plaintextLength / 16) +1) * 16) + 32; + } + + private Mac initializeMac() { + try { + return Mac.getInstance("HmacSHA256"); + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(e); + } + } + + private Cipher initializeCipher() { + try { + return Cipher.getInstance("AES/CBC/PKCS5Padding"); + } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { + throw new AssertionError(e); + } + } +} diff --git a/src/org/thoughtcrime/securesms/crypto/TextSecureCipher.java b/library/src/org/whispersystems/textsecure/crypto/TextSecureCipher.java similarity index 51% rename from src/org/thoughtcrime/securesms/crypto/TextSecureCipher.java rename to library/src/org/whispersystems/textsecure/crypto/TextSecureCipher.java index 67b52cf696..1c4109397a 100644 --- a/src/org/thoughtcrime/securesms/crypto/TextSecureCipher.java +++ b/library/src/org/whispersystems/textsecure/crypto/TextSecureCipher.java @@ -1,6 +1,4 @@ -package org.thoughtcrime.securesms.crypto; - -import android.content.Context; +package org.whispersystems.textsecure.crypto; import org.whispersystems.libaxolotl.DuplicateMessageException; import org.whispersystems.libaxolotl.InvalidKeyException; @@ -13,32 +11,23 @@ import org.whispersystems.libaxolotl.UntrustedIdentityException; import org.whispersystems.libaxolotl.protocol.CiphertextMessage; import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage; import org.whispersystems.libaxolotl.protocol.WhisperMessage; -import org.whispersystems.libaxolotl.state.IdentityKeyStore; -import org.whispersystems.libaxolotl.state.PreKeyStore; -import org.whispersystems.libaxolotl.state.SessionStore; -import org.whispersystems.libaxolotl.state.SignedPreKeyStore; -import org.whispersystems.textsecure.crypto.MasterSecret; -import org.whispersystems.textsecure.crypto.TransportDetails; -import org.whispersystems.textsecure.storage.RecipientDevice; -import org.whispersystems.textsecure.storage.TextSecurePreKeyStore; -import org.whispersystems.textsecure.storage.TextSecureSessionStore; +import org.whispersystems.libaxolotl.state.AxolotlStore; +import org.whispersystems.textsecure.push.PushAddress; +import org.whispersystems.textsecure.push.PushTransportDetails; public class TextSecureCipher { private final SessionCipher sessionCipher; private final TransportDetails transportDetails; - public TextSecureCipher(Context context, MasterSecret masterSecret, - RecipientDevice recipient, TransportDetails transportDetails) - { - SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret); - PreKeyStore preKeyStore = new TextSecurePreKeyStore(context, masterSecret); - SignedPreKeyStore signedPreKeyStore = new TextSecurePreKeyStore(context, masterSecret); - IdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(context, masterSecret); + public TextSecureCipher(AxolotlStore axolotlStore, PushAddress pushAddress) { + int sessionVersion = axolotlStore.loadSession(pushAddress.getRecipientId(), + pushAddress.getDeviceId()) + .getSessionState().getSessionVersion(); - this.transportDetails = transportDetails; - this.sessionCipher = new SessionCipher(sessionStore, preKeyStore, signedPreKeyStore, identityKeyStore, - recipient.getRecipientId(), recipient.getDeviceId()); + this.transportDetails = new PushTransportDetails(sessionVersion); + this.sessionCipher = new SessionCipher(axolotlStore, pushAddress.getRecipientId(), + pushAddress.getDeviceId()); } public CiphertextMessage encrypt(byte[] unpaddedMessage) { @@ -54,7 +43,7 @@ public class TextSecureCipher { public byte[] decrypt(PreKeyWhisperMessage message) throws InvalidKeyException, LegacyMessageException, InvalidMessageException, - DuplicateMessageException, InvalidKeyIdException, UntrustedIdentityException, NoSessionException + DuplicateMessageException, InvalidKeyIdException, UntrustedIdentityException, NoSessionException { byte[] paddedMessage = sessionCipher.decrypt(message); return transportDetails.getStrippedPaddingMessageBody(paddedMessage); @@ -65,3 +54,4 @@ public class TextSecureCipher { } } + diff --git a/src/org/thoughtcrime/securesms/transport/UntrustedIdentityException.java b/library/src/org/whispersystems/textsecure/crypto/UntrustedIdentityException.java similarity index 93% rename from src/org/thoughtcrime/securesms/transport/UntrustedIdentityException.java rename to library/src/org/whispersystems/textsecure/crypto/UntrustedIdentityException.java index 195bcd14c9..51b86d325c 100644 --- a/src/org/thoughtcrime/securesms/transport/UntrustedIdentityException.java +++ b/library/src/org/whispersystems/textsecure/crypto/UntrustedIdentityException.java @@ -1,4 +1,4 @@ -package org.thoughtcrime.securesms.transport; +package org.whispersystems.textsecure.crypto; import org.whispersystems.libaxolotl.IdentityKey; diff --git a/library/src/org/whispersystems/textsecure/directory/BloomFilter.java b/library/src/org/whispersystems/textsecure/directory/BloomFilter.java deleted file mode 100644 index 808047ce2d..0000000000 --- a/library/src/org/whispersystems/textsecure/directory/BloomFilter.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2011 Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.whispersystems.textsecure.directory; - -import org.whispersystems.textsecure.util.Conversions; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; - -/** - * A simple bloom filter implementation that backs the RedPhone directory. - * - * @author Moxie Marlinspike - * - */ - -public class BloomFilter { - - private final MappedByteBuffer buffer; - private final long length; - private final int hashCount; - - public BloomFilter(File bloomFilter, int hashCount) - throws IOException - { - this.length = bloomFilter.length(); - this.buffer = new FileInputStream(bloomFilter).getChannel() - .map(FileChannel.MapMode.READ_ONLY, 0, length); - this.hashCount = hashCount; - } - - public int getHashCount() { - return hashCount; - } - - private boolean isBitSet(long bitIndex) { - int byteInQuestion = this.buffer.get((int)(bitIndex / 8)); - int bitOffset = (0x01 << (bitIndex % 8)); - - return (byteInQuestion & bitOffset) > 0; - } - - public boolean contains(String entity) { - try { - for (int i=0;i. - */ - -package org.whispersystems.textsecure.directory; - -import android.content.Context; -import android.util.Log; - -import com.google.thoughtcrimegson.Gson; -import com.google.thoughtcrimegson.JsonParseException; -import com.google.thoughtcrimegson.annotations.SerializedName; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.util.List; -import java.util.zip.GZIPInputStream; - -/** - * Handles providing lookups, serializing, and deserializing the RedPhone directory. - * - * @author Moxie Marlinspike - * - */ - -public class NumberFilter { - - private static NumberFilter instance; - - public synchronized static NumberFilter getInstance(Context context) { - if (instance == null) - instance = NumberFilter.deserializeFromFile(context); - - return instance; - } - - private static final String DIRECTORY_META_FILE = "directory.stat"; - - private File bloomFilter; - private String version; - private long capacity; - private int hashCount; - private Context context; - - private NumberFilter(Context context, File bloomFilter, long capacity, - int hashCount, String version) - { - this.context = context.getApplicationContext(); - this.bloomFilter = bloomFilter; - this.capacity = capacity; - this.hashCount = hashCount; - this.version = version; - } - - public synchronized boolean containsNumber(String number) { - try { - if (bloomFilter == null) return false; - else if (number == null || number.length() == 0) return false; - - return new BloomFilter(bloomFilter, hashCount).contains(number); - } catch (IOException ioe) { - Log.w("NumberFilter", ioe); - return false; - } - } - - public synchronized boolean containsNumbers(List numbers) { - try { - if (bloomFilter == null) return false; - if (numbers == null || numbers.size() == 0) return false; - - BloomFilter filter = new BloomFilter(bloomFilter, hashCount); - - for (String number : numbers) { - if (!filter.contains(number)) { - return false; - } - } - - return true; - } catch (IOException ioe) { - Log.w("NumberFilter", ioe); - return false; - } - } - - public synchronized void update(DirectoryDescriptor descriptor, File compressedData) { - try { - File uncompressed = File.createTempFile("directory", ".dat", context.getFilesDir()); - FileInputStream fin = new FileInputStream (compressedData); - GZIPInputStream gin = new GZIPInputStream(fin); - FileOutputStream out = new FileOutputStream(uncompressed); - - byte[] buffer = new byte[4096]; - int read; - - while ((read = gin.read(buffer)) != -1) { - out.write(buffer, 0, read); - } - - out.close(); - compressedData.delete(); - - update(uncompressed, descriptor.getCapacity(), descriptor.getHashCount(), descriptor.getVersion()); - } catch (IOException ioe) { - Log.w("NumberFilter", ioe); - } - } - - private synchronized void update(File bloomFilter, long capacity, int hashCount, String version) - { - if (this.bloomFilter != null) - this.bloomFilter.delete(); - - this.bloomFilter = bloomFilter; - this.capacity = capacity; - this.hashCount = hashCount; - this.version = version; - - serializeToFile(context); - } - - private void serializeToFile(Context context) { - if (this.bloomFilter == null) - return; - - try { - FileOutputStream fout = context.openFileOutput(DIRECTORY_META_FILE, 0); - NumberFilterStorage storage = new NumberFilterStorage(bloomFilter.getAbsolutePath(), - capacity, hashCount, version); - - storage.serializeToStream(fout); - fout.close(); - } catch (IOException ioe) { - Log.w("NumberFilter", ioe); - } - } - - private static NumberFilter deserializeFromFile(Context context) { - try { - FileInputStream fis = context.openFileInput(DIRECTORY_META_FILE); - NumberFilterStorage storage = NumberFilterStorage.fromStream(fis); - - if (storage == null) return new NumberFilter(context, null, 0, 0, "0"); - else return new NumberFilter(context, - new File(storage.getDataPath()), - storage.getCapacity(), - storage.getHashCount(), - storage.getVersion()); - } catch (IOException ioe) { - Log.w("NumberFilter", ioe); - return new NumberFilter(context, null, 0, 0, "0"); - } - } - - private static class NumberFilterStorage { - @SerializedName("data_path") - private String dataPath; - - @SerializedName("capacity") - private long capacity; - - @SerializedName("hash_count") - private int hashCount; - - @SerializedName("version") - private String version; - - public NumberFilterStorage(String dataPath, long capacity, int hashCount, String version) { - this.dataPath = dataPath; - this.capacity = capacity; - this.hashCount = hashCount; - this.version = version; - } - - public String getDataPath() { - return dataPath; - } - - public long getCapacity() { - return capacity; - } - - public int getHashCount() { - return hashCount; - } - - public String getVersion() { - return version; - } - - public void serializeToStream(OutputStream out) throws IOException { - out.write(new Gson().toJson(this).getBytes()); - } - - public static NumberFilterStorage fromStream(InputStream in) throws IOException { - try { - return new Gson().fromJson(new BufferedReader(new InputStreamReader(in)), - NumberFilterStorage.class); - } catch (JsonParseException jpe) { - Log.w("NumberFilter", jpe); - throw new IOException("JSON Parse Exception"); - } - } - } -} \ No newline at end of file diff --git a/library/src/org/whispersystems/textsecure/push/IncomingEncryptedPushMessage.java b/library/src/org/whispersystems/textsecure/push/IncomingEncryptedPushMessage.java index 3e34128cab..bd3df0fc5e 100644 --- a/library/src/org/whispersystems/textsecure/push/IncomingEncryptedPushMessage.java +++ b/library/src/org/whispersystems/textsecure/push/IncomingEncryptedPushMessage.java @@ -69,15 +69,7 @@ public class IncomingEncryptedPushMessage { return cipher.doFinal(ciphertext, CIPHERTEXT_OFFSET, ciphertext.length - VERSION_LENGTH - IV_LENGTH - MAC_SIZE); - } catch (NoSuchAlgorithmException e) { - throw new AssertionError(e); - } catch (NoSuchPaddingException e) { - throw new AssertionError(e); - } catch (InvalidKeyException e) { - throw new AssertionError(e); - } catch (InvalidAlgorithmParameterException e) { - throw new AssertionError(e); - } catch (IllegalBlockSizeException e) { + } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException e) { throw new AssertionError(e); } catch (BadPaddingException e) { Log.w("IncomingEncryptedPushMessage", e); diff --git a/library/src/org/whispersystems/textsecure/push/PushAddress.java b/library/src/org/whispersystems/textsecure/push/PushAddress.java index 0522cec920..fe864e86b4 100644 --- a/library/src/org/whispersystems/textsecure/push/PushAddress.java +++ b/library/src/org/whispersystems/textsecure/push/PushAddress.java @@ -10,7 +10,7 @@ public class PushAddress extends RecipientDevice { private final String e164number; private final String relay; - private PushAddress(long recipientId, String e164number, int deviceId, String relay) { + public PushAddress(long recipientId, String e164number, int deviceId, String relay) { super(recipientId, deviceId); this.e164number = e164number; this.relay = relay; diff --git a/library/src/org/whispersystems/textsecure/push/PushAttachmentData.java b/library/src/org/whispersystems/textsecure/push/PushAttachmentData.java index e7daa01d8a..d644e8ee89 100644 --- a/library/src/org/whispersystems/textsecure/push/PushAttachmentData.java +++ b/library/src/org/whispersystems/textsecure/push/PushAttachmentData.java @@ -1,21 +1,34 @@ package org.whispersystems.textsecure.push; +import java.io.InputStream; + public class PushAttachmentData { - private final String contentType; - private final byte[] data; + private final String contentType; + private final InputStream data; + private final long dataSize; + private final byte[] key; - public PushAttachmentData(String contentType, byte[] data) { + public PushAttachmentData(String contentType, InputStream data, long dataSize, byte[] key) { this.contentType = contentType; this.data = data; + this.dataSize = dataSize; + this.key = key; } public String getContentType() { return contentType; } - public byte[] getData() { + public InputStream getData() { return data; } + public long getDataSize() { + return dataSize; + } + + public byte[] getKey() { + return key; + } } diff --git a/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java b/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java index 6af77c1603..e7cb9899d1 100644 --- a/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java +++ b/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java @@ -28,6 +28,7 @@ import org.whispersystems.libaxolotl.ecc.ECPublicKey; import org.whispersystems.libaxolotl.state.PreKeyBundle; import org.whispersystems.libaxolotl.state.PreKeyRecord; import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; +import org.whispersystems.textsecure.crypto.AttachmentCipherOutputStream; import org.whispersystems.textsecure.push.exceptions.AuthorizationFailedException; import org.whispersystems.textsecure.push.exceptions.ExpectationFailedException; import org.whispersystems.textsecure.push.exceptions.MismatchedDevicesException; @@ -47,7 +48,6 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; -import java.net.MalformedURLException; import java.net.URL; import java.security.KeyManagementException; import java.security.KeyStore; @@ -298,12 +298,13 @@ public class PushServiceSocket { Log.w("PushServiceSocket", "Got attachment content location: " + attachmentKey.getLocation()); - uploadExternalFile("PUT", attachmentKey.getLocation(), attachment.getData()); + uploadAttachment("PUT", attachmentKey.getLocation(), attachment.getData(), + attachment.getDataSize(), attachment.getKey()); return attachmentKey.getId(); } - public File retrieveAttachment(String relay, long attachmentId) throws IOException { + public void retrieveAttachment(String relay, long attachmentId, File destination) throws IOException { String path = String.format(ATTACHMENT_PATH, String.valueOf(attachmentId)); if (!Util.isEmpty(relay)) { @@ -315,12 +316,7 @@ public class PushServiceSocket { Log.w("PushServiceSocket", "Attachment: " + attachmentId + " is at: " + descriptor.getLocation()); - File attachment = File.createTempFile("attachment", ".tmp", context.getFilesDir()); - attachment.deleteOnExit(); - - downloadExternalFile(descriptor.getLocation(), attachment); - - return attachment; + downloadExternalFile(descriptor.getLocation(), destination); } public List retrieveDirectory(Set contactTokens) { @@ -356,7 +352,7 @@ public class PushServiceSocket { try { if (connection.getResponseCode() != 200) { - throw new IOException("Bad response: " + connection.getResponseCode()); + throw new NonSuccessfulResponseCodeException("Bad response: " + connection.getResponseCode()); } OutputStream output = new FileOutputStream(localDestination); @@ -375,20 +371,23 @@ public class PushServiceSocket { } } - private void uploadExternalFile(String method, String url, byte[] data) + private void uploadAttachment(String method, String url, InputStream data, long dataSize, byte[] key) throws IOException { URL uploadUrl = new URL(url); HttpsURLConnection connection = (HttpsURLConnection) uploadUrl.openConnection(); connection.setDoOutput(true); + connection.setFixedLengthStreamingMode((int) AttachmentCipherOutputStream.getCiphertextLength(dataSize)); connection.setRequestMethod(method); connection.setRequestProperty("Content-Type", "application/octet-stream"); connection.connect(); try { - OutputStream out = connection.getOutputStream(); - out.write(data); - out.close(); + OutputStream stream = connection.getOutputStream(); + AttachmentCipherOutputStream out = new AttachmentCipherOutputStream(key, stream); + + Util.copy(data, out); + out.flush(); if (connection.getResponseCode() != 200) { throw new IOException("Bad response: " + connection.getResponseCode() + " " + connection.getResponseMessage()); @@ -532,14 +531,8 @@ public class PushServiceSocket { trustManagerFactory.init(keyStore); return BlacklistingTrustManager.createFor(trustManagerFactory.getTrustManagers()); - } catch (KeyStoreException kse) { + } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException kse) { throw new AssertionError(kse); - } catch (CertificateException e) { - throw new AssertionError(e); - } catch (NoSuchAlgorithmException e) { - throw new AssertionError(e); - } catch (IOException ioe) { - throw new AssertionError(ioe); } } diff --git a/library/src/org/whispersystems/textsecure/push/RawTransportDetails.java b/library/src/org/whispersystems/textsecure/push/RawTransportDetails.java deleted file mode 100644 index 6384ad9a32..0000000000 --- a/library/src/org/whispersystems/textsecure/push/RawTransportDetails.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.whispersystems.textsecure.push; - -import org.whispersystems.textsecure.crypto.TransportDetails; - -import java.io.IOException; - -public class RawTransportDetails implements TransportDetails { - @Override - public byte[] getStrippedPaddingMessageBody(byte[] messageWithPadding) { - return messageWithPadding; - } - - @Override - public byte[] getPaddedMessageBody(byte[] messageBody) { - return messageBody; - } - - @Override - public byte[] getEncodedMessage(byte[] messageWithMac) { - return messageWithMac; - } - - @Override - public byte[] getDecodedMessage(byte[] encodedMessageBytes) throws IOException { - return encodedMessageBytes; - } -} diff --git a/src/org/thoughtcrime/securesms/transport/EncapsulatedExceptions.java b/library/src/org/whispersystems/textsecure/push/exceptions/EncapsulatedExceptions.java similarity index 86% rename from src/org/thoughtcrime/securesms/transport/EncapsulatedExceptions.java rename to library/src/org/whispersystems/textsecure/push/exceptions/EncapsulatedExceptions.java index 5122ef4a03..80732db1fb 100644 --- a/src/org/thoughtcrime/securesms/transport/EncapsulatedExceptions.java +++ b/library/src/org/whispersystems/textsecure/push/exceptions/EncapsulatedExceptions.java @@ -1,5 +1,6 @@ -package org.thoughtcrime.securesms.transport; +package org.whispersystems.textsecure.push.exceptions; +import org.whispersystems.textsecure.crypto.UntrustedIdentityException; import org.whispersystems.textsecure.push.UnregisteredUserException; import java.util.List; diff --git a/library/src/org/whispersystems/textsecure/storage/SessionUtil.java b/library/src/org/whispersystems/textsecure/storage/SessionUtil.java deleted file mode 100644 index 40f673dd44..0000000000 --- a/library/src/org/whispersystems/textsecure/storage/SessionUtil.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.whispersystems.textsecure.storage; - -import android.content.Context; - -import org.whispersystems.libaxolotl.state.SessionStore; -import org.whispersystems.textsecure.crypto.MasterSecret; - -public class SessionUtil { - - public static int getSessionVersion(Context context, - MasterSecret masterSecret, - RecipientDevice recipient) - { - return - new TextSecureSessionStore(context, masterSecret) - .loadSession(recipient.getRecipientId(), recipient.getDeviceId()) - .getSessionState() - .getSessionVersion(); - } - - public static boolean hasEncryptCapableSession(Context context, - MasterSecret masterSecret, - CanonicalRecipient recipient) - { - return hasEncryptCapableSession(context, masterSecret, - new RecipientDevice(recipient.getRecipientId(), - RecipientDevice.DEFAULT_DEVICE_ID)); - } - - public static boolean hasEncryptCapableSession(Context context, - MasterSecret masterSecret, - RecipientDevice recipientDevice) - { - long recipientId = recipientDevice.getRecipientId(); - int deviceId = recipientDevice.getDeviceId(); - SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret); - - return sessionStore.containsSession(recipientId, deviceId); - } - -} diff --git a/library/src/org/whispersystems/textsecure/util/Util.java b/library/src/org/whispersystems/textsecure/util/Util.java index 79263143fa..38f358bee7 100644 --- a/library/src/org/whispersystems/textsecure/util/Util.java +++ b/library/src/org/whispersystems/textsecure/util/Util.java @@ -94,12 +94,17 @@ public class Util { } public static String getSecret(int size) { + byte[] secret = getSecretBytes(size); + return Base64.encodeBytes(secret); + } + + public static byte[] getSecretBytes(int size) { try { byte[] secret = new byte[size]; SecureRandom.getInstance("SHA1PRNG").nextBytes(secret); - return Base64.encodeBytes(secret); - } catch (NoSuchAlgorithmException nsae) { - throw new AssertionError(nsae); + return secret; + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(e); } } diff --git a/src/org/thoughtcrime/securesms/ApplicationContext.java b/src/org/thoughtcrime/securesms/ApplicationContext.java index 2df9848caa..c077a0c956 100644 --- a/src/org/thoughtcrime/securesms/ApplicationContext.java +++ b/src/org/thoughtcrime/securesms/ApplicationContext.java @@ -20,11 +20,16 @@ import android.app.Application; import android.content.Context; import org.thoughtcrime.securesms.crypto.PRNGFixes; -import org.thoughtcrime.securesms.jobs.EncryptingJobSerializer; +import org.thoughtcrime.securesms.jobs.persistence.EncryptingJobSerializer; import org.thoughtcrime.securesms.jobs.GcmRefreshJob; +import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirementProvider; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.jobqueue.JobManager; import org.whispersystems.jobqueue.requirements.NetworkRequirementProvider; +import org.whispersystems.jobqueue.requirements.RequirementProvider; + +import java.util.LinkedList; +import java.util.List; /** * Will be called once when the TextSecure process is created. @@ -58,8 +63,12 @@ public class ApplicationContext extends Application { } private void initializeJobManager() { - this.jobManager = new JobManager(this, "TextSecureJobs", - new NetworkRequirementProvider(this), + List providers = new LinkedList() {{ + add(new NetworkRequirementProvider(ApplicationContext.this)); + add(new MasterSecretRequirementProvider(ApplicationContext.this)); + }}; + + this.jobManager = new JobManager(this, "TextSecureJobs", providers, new EncryptingJobSerializer(this), 5); } diff --git a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java index 5bef72d456..318099d7fa 100644 --- a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java +++ b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java @@ -64,7 +64,7 @@ import org.thoughtcrime.securesms.util.MemoryCleaner; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Trimmer; import org.thoughtcrime.securesms.util.Util; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.whispersystems.textsecure.push.exceptions.AuthorizationFailedException; import org.whispersystems.textsecure.push.PushServiceSocket; diff --git a/src/org/thoughtcrime/securesms/AutoInitiateActivity.java b/src/org/thoughtcrime/securesms/AutoInitiateActivity.java index 7048f5c8ad..e5637f6b2a 100644 --- a/src/org/thoughtcrime/securesms/AutoInitiateActivity.java +++ b/src/org/thoughtcrime/securesms/AutoInitiateActivity.java @@ -31,9 +31,9 @@ import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.MemoryCleaner; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.libaxolotl.state.SessionStore; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.whispersystems.textsecure.storage.RecipientDevice; -import org.whispersystems.textsecure.storage.TextSecureSessionStore; +import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore; /** * Activity which prompts the user to initiate a secure diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java index 46c4859218..c25a0afd76 100644 --- a/src/org/thoughtcrime/securesms/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationActivity.java @@ -64,7 +64,7 @@ import org.thoughtcrime.securesms.components.EmojiToggle; import org.thoughtcrime.securesms.contacts.ContactAccessor; import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData; import org.thoughtcrime.securesms.crypto.KeyExchangeInitiator; -import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor; +import org.thoughtcrime.securesms.crypto.SecurityEvent; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DraftDatabase; import org.thoughtcrime.securesms.database.DraftDatabase.Draft; @@ -104,10 +104,10 @@ import org.thoughtcrime.securesms.util.MemoryCleaner; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.libaxolotl.InvalidMessageException; import org.whispersystems.libaxolotl.state.SessionStore; -import org.whispersystems.textsecure.crypto.MasterCipher; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterCipher; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.whispersystems.textsecure.storage.RecipientDevice; -import org.whispersystems.textsecure.storage.TextSecureSessionStore; +import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore; import org.whispersystems.textsecure.util.Util; import java.io.IOException; @@ -818,7 +818,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity }; registerReceiver(securityUpdateReceiver, - new IntentFilter(KeyExchangeProcessor.SECURITY_UPDATE_EVENT), + new IntentFilter(SecurityEvent.SECURITY_UPDATE_EVENT), KeyCachingService.KEY_PERMISSION, null); registerReceiver(groupUpdateReceiver, diff --git a/src/org/thoughtcrime/securesms/ConversationAdapter.java b/src/org/thoughtcrime/securesms/ConversationAdapter.java index 064b472bf2..de7792b2a0 100644 --- a/src/org/thoughtcrime/securesms/ConversationAdapter.java +++ b/src/org/thoughtcrime/securesms/ConversationAdapter.java @@ -25,7 +25,7 @@ import android.view.ViewGroup; import android.widget.AbsListView; import android.support.v4.widget.CursorAdapter; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MmsSmsColumns; import org.thoughtcrime.securesms.database.MmsSmsDatabase; diff --git a/src/org/thoughtcrime/securesms/ConversationFragment.java b/src/org/thoughtcrime/securesms/ConversationFragment.java index d60de305a8..ec368a9ede 100644 --- a/src/org/thoughtcrime/securesms/ConversationFragment.java +++ b/src/org/thoughtcrime/securesms/ConversationFragment.java @@ -38,9 +38,17 @@ import org.thoughtcrime.securesms.util.Dialogs; import org.thoughtcrime.securesms.util.DirectoryHelper; import org.thoughtcrime.securesms.util.SaveAttachmentTask; import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment; -import org.whispersystems.textsecure.crypto.MasterSecret; -import org.whispersystems.textsecure.util.FutureTaskListener; +import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.whispersystems.textsecure.util.FutureTaskListener; +import org.whispersystems.textsecure.util.Util; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.ref.WeakReference; import java.sql.Date; import java.text.SimpleDateFormat; diff --git a/src/org/thoughtcrime/securesms/ConversationItem.java b/src/org/thoughtcrime/securesms/ConversationItem.java index fe0aed0263..33ade9222d 100644 --- a/src/org/thoughtcrime/securesms/ConversationItem.java +++ b/src/org/thoughtcrime/securesms/ConversationItem.java @@ -38,12 +38,14 @@ import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord; +import org.thoughtcrime.securesms.jobs.MmsDownloadJob; import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.recipients.Recipient; @@ -51,7 +53,6 @@ import org.thoughtcrime.securesms.service.SendReceiveService; import org.thoughtcrime.securesms.util.DateUtils; import org.thoughtcrime.securesms.util.Dialogs; import org.thoughtcrime.securesms.util.Emoji; -import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.util.FutureTaskListener; import org.whispersystems.textsecure.util.ListenableFutureTask; @@ -490,13 +491,10 @@ public class ConversationItem extends LinearLayout { mmsDownloadButton.setVisibility(View.GONE); mmsDownloadingLabel.setVisibility(View.VISIBLE); - Intent intent = new Intent(context, SendReceiveService.class); - intent.putExtra("content_location", new String(notificationRecord.getContentLocation())); - intent.putExtra("message_id", notificationRecord.getId()); - intent.putExtra("transaction_id", notificationRecord.getTransactionId()); - intent.putExtra("thread_id", notificationRecord.getThreadId()); - intent.setAction(SendReceiveService.DOWNLOAD_MMS_ACTION); - context.startService(intent); + ApplicationContext.getInstance(context) + .getJobManager() + .add(new MmsDownloadJob(context, messageRecord.getId(), + messageRecord.getThreadId(), false)); } } diff --git a/src/org/thoughtcrime/securesms/ConversationListActivity.java b/src/org/thoughtcrime/securesms/ConversationListActivity.java index 3184805819..91bf261731 100644 --- a/src/org/thoughtcrime/securesms/ConversationListActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationListActivity.java @@ -49,7 +49,7 @@ import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.MemoryCleaner; import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; public class ConversationListActivity extends PassphraseRequiredActionBarActivity diff --git a/src/org/thoughtcrime/securesms/ConversationListAdapter.java b/src/org/thoughtcrime/securesms/ConversationListAdapter.java index 291ef72949..aac8240bcd 100644 --- a/src/org/thoughtcrime/securesms/ConversationListAdapter.java +++ b/src/org/thoughtcrime/securesms/ConversationListAdapter.java @@ -24,8 +24,8 @@ import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.CursorAdapter; -import org.whispersystems.textsecure.crypto.MasterCipher; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterCipher; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.model.ThreadRecord; diff --git a/src/org/thoughtcrime/securesms/ConversationListFragment.java b/src/org/thoughtcrime/securesms/ConversationListFragment.java index 7f7eb38a5b..9e1c8ff1c5 100644 --- a/src/org/thoughtcrime/securesms/ConversationListFragment.java +++ b/src/org/thoughtcrime/securesms/ConversationListFragment.java @@ -51,7 +51,7 @@ import org.thoughtcrime.securesms.database.loaders.ConversationListLoader; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.util.Dialogs; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; import java.util.Set; diff --git a/src/org/thoughtcrime/securesms/DatabaseUpgradeActivity.java b/src/org/thoughtcrime/securesms/DatabaseUpgradeActivity.java index 5aa30ed865..18d9c395bb 100644 --- a/src/org/thoughtcrime/securesms/DatabaseUpgradeActivity.java +++ b/src/org/thoughtcrime/securesms/DatabaseUpgradeActivity.java @@ -27,14 +27,18 @@ import android.util.Log; import android.view.View; import android.widget.ProgressBar; -import org.thoughtcrime.securesms.crypto.DecryptingQueue; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; -import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob; -import org.thoughtcrime.securesms.notifications.MessageNotifier; -import org.thoughtcrime.securesms.util.Util; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.SmsDatabase; +import org.thoughtcrime.securesms.database.model.SmsMessageRecord; +import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob; +import org.thoughtcrime.securesms.jobs.SmsDecryptJob; +import org.thoughtcrime.securesms.notifications.MessageNotifier; +import org.thoughtcrime.securesms.util.ParcelUtil; +import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.VersionTracker; +import org.whispersystems.jobqueue.EncryptionKeys; import java.io.File; import java.util.SortedSet; @@ -49,6 +53,7 @@ public class DatabaseUpgradeActivity extends Activity { public static final int ASYMMETRIC_MASTER_SECRET_FIX_VERSION = 73; public static final int NO_V1_VERSION = 83; public static final int SIGNED_PREKEY_VERSION = 83; + public static final int NO_DECRYPT_QUEUE_VERSION = 84; private static final SortedSet UPGRADE_VERSIONS = new TreeSet() {{ add(NO_MORE_KEY_EXCHANGE_PREFIX_VERSION); @@ -57,6 +62,7 @@ public class DatabaseUpgradeActivity extends Activity { add(ASYMMETRIC_MASTER_SECRET_FIX_VERSION); add(NO_V1_VERSION); add(SIGNED_PREKEY_VERSION); + add(NO_DECRYPT_QUEUE_VERSION); }}; private MasterSecret masterSecret; @@ -77,7 +83,10 @@ public class DatabaseUpgradeActivity extends Activity { .execute(VersionTracker.getLastSeenVersion(this)); } else { VersionTracker.updateLastSeenVersion(this); - DecryptingQueue.schedulePendingDecrypts(DatabaseUpgradeActivity.this, masterSecret); + ApplicationContext.getInstance(this) + .getJobManager() + .setEncryptionKeys(new EncryptionKeys(ParcelUtil.serialize(masterSecret))); +// DecryptingQueue.schedulePendingDecrypts(DatabaseUpgradeActivity.this, masterSecret); MessageNotifier.updateNotification(DatabaseUpgradeActivity.this, masterSecret); startActivity((Intent)getIntent().getParcelableExtra("next_intent")); finish(); @@ -165,6 +174,25 @@ public class DatabaseUpgradeActivity extends Activity { .add(new CreateSignedPreKeyJob(context, masterSecret)); } + if (params[0] < NO_DECRYPT_QUEUE_VERSION) { + SmsDatabase.Reader reader = null; + SmsMessageRecord record; + + try { + reader = DatabaseFactory.getEncryptingSmsDatabase(getApplicationContext()) + .getDecryptInProgressMessages(masterSecret); + + while ((record = reader.getNext()) != null) { + ApplicationContext.getInstance(getApplicationContext()) + .getJobManager() + .add(new SmsDecryptJob(getApplicationContext(), record.getId())); + } + } finally { + if (reader != null) + reader.close(); + } + } + return null; } @@ -180,7 +208,11 @@ public class DatabaseUpgradeActivity extends Activity { @Override protected void onPostExecute(Void result) { VersionTracker.updateLastSeenVersion(DatabaseUpgradeActivity.this); - DecryptingQueue.schedulePendingDecrypts(DatabaseUpgradeActivity.this, masterSecret); +// DecryptingQueue.schedulePendingDecrypts(DatabaseUpgradeActivity.this, masterSecret); + ApplicationContext.getInstance(DatabaseUpgradeActivity.this) + .getJobManager() + .setEncryptionKeys(new EncryptionKeys(ParcelUtil.serialize(masterSecret))); + MessageNotifier.updateNotification(DatabaseUpgradeActivity.this, masterSecret); startActivity((Intent)getIntent().getParcelableExtra("next_intent")); diff --git a/src/org/thoughtcrime/securesms/ExportFragment.java b/src/org/thoughtcrime/securesms/ExportFragment.java index b91650384e..f6d353fff9 100644 --- a/src/org/thoughtcrime/securesms/ExportFragment.java +++ b/src/org/thoughtcrime/securesms/ExportFragment.java @@ -14,11 +14,10 @@ import android.view.View; import android.view.ViewGroup; import android.widget.Toast; -import org.whispersystems.textsecure.crypto.MasterSecret; -import org.thoughtcrime.securesms.util.Dialogs; -//import org.thoughtcrime.securesms.database.EncryptedBackupExporter; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.NoExternalStorageException; import org.thoughtcrime.securesms.database.PlaintextBackupExporter; +import org.thoughtcrime.securesms.util.Dialogs; import java.io.IOException; diff --git a/src/org/thoughtcrime/securesms/GroupCreateActivity.java b/src/org/thoughtcrime/securesms/GroupCreateActivity.java index 0cd165644a..b734386168 100644 --- a/src/org/thoughtcrime/securesms/GroupCreateActivity.java +++ b/src/org/thoughtcrime/securesms/GroupCreateActivity.java @@ -27,7 +27,6 @@ import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.text.Editable; -import android.text.TextUtils; import android.text.TextWatcher; import android.util.Log; import android.util.Pair; @@ -62,7 +61,7 @@ import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.SelectedRecipientsAdapter; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.whispersystems.textsecure.directory.Directory; import org.whispersystems.textsecure.directory.NotInDirectoryException; import org.whispersystems.textsecure.util.InvalidNumberException; diff --git a/src/org/thoughtcrime/securesms/ImportExportActivity.java b/src/org/thoughtcrime/securesms/ImportExportActivity.java index f8039d72b3..7c96655b1d 100644 --- a/src/org/thoughtcrime/securesms/ImportExportActivity.java +++ b/src/org/thoughtcrime/securesms/ImportExportActivity.java @@ -9,7 +9,7 @@ import android.support.v4.view.ViewPager; import android.support.v7.app.ActionBar; import android.view.MenuItem; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; public class ImportExportActivity extends PassphraseRequiredActionBarActivity { diff --git a/src/org/thoughtcrime/securesms/ImportFragment.java b/src/org/thoughtcrime/securesms/ImportFragment.java index 5caacbb410..560fd61049 100644 --- a/src/org/thoughtcrime/securesms/ImportFragment.java +++ b/src/org/thoughtcrime/securesms/ImportFragment.java @@ -14,14 +14,14 @@ import android.view.View; import android.view.ViewGroup; import android.widget.Toast; -import org.whispersystems.textsecure.crypto.MasterSecret; -import org.thoughtcrime.securesms.util.Dialogs; -import org.thoughtcrime.securesms.database.EncryptedBackupExporter; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.EncryptedBackupExporter; import org.thoughtcrime.securesms.database.NoExternalStorageException; import org.thoughtcrime.securesms.database.PlaintextBackupImporter; import org.thoughtcrime.securesms.service.ApplicationMigrationService; import org.thoughtcrime.securesms.service.KeyCachingService; +import org.thoughtcrime.securesms.util.Dialogs; import java.io.IOException; diff --git a/src/org/thoughtcrime/securesms/MediaPreviewActivity.java b/src/org/thoughtcrime/securesms/MediaPreviewActivity.java index f62810d539..d1e331c3cd 100644 --- a/src/org/thoughtcrime/securesms/MediaPreviewActivity.java +++ b/src/org/thoughtcrime/securesms/MediaPreviewActivity.java @@ -33,6 +33,7 @@ import android.view.WindowManager; import android.widget.ImageView; import android.widget.Toast; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.providers.PartProvider; import org.thoughtcrime.securesms.recipients.Recipient; @@ -40,7 +41,6 @@ import org.thoughtcrime.securesms.util.DateUtils; import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.SaveAttachmentTask; import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment; -import org.whispersystems.textsecure.crypto.MasterSecret; import java.io.IOException; import java.io.InputStream; diff --git a/src/org/thoughtcrime/securesms/MmsPreferencesActivity.java b/src/org/thoughtcrime/securesms/MmsPreferencesActivity.java index e2e9abd886..d3f7682728 100644 --- a/src/org/thoughtcrime/securesms/MmsPreferencesActivity.java +++ b/src/org/thoughtcrime/securesms/MmsPreferencesActivity.java @@ -16,18 +16,16 @@ */ package org.thoughtcrime.securesms; -import android.content.Intent; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.view.MenuItem; -import org.thoughtcrime.securesms.service.SendReceiveService; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.MemoryCleaner; -import org.whispersystems.textsecure.crypto.MasterSecret; public class MmsPreferencesActivity extends PassphraseRequiredActionBarActivity { @@ -72,7 +70,6 @@ public class MmsPreferencesActivity extends PassphraseRequiredActionBarActivity public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: - handleDownloadMmsPendingApn(); finish(); return true; } @@ -82,13 +79,7 @@ public class MmsPreferencesActivity extends PassphraseRequiredActionBarActivity @Override public void onBackPressed() { - handleDownloadMmsPendingApn(); super.onBackPressed(); } - private void handleDownloadMmsPendingApn() { - Intent intent = new Intent(this, SendReceiveService.class); - intent.setAction(SendReceiveService.DOWNLOAD_MMS_PENDING_APN_ACTION); - startService(intent); - } } diff --git a/src/org/thoughtcrime/securesms/NewConversationActivity.java b/src/org/thoughtcrime/securesms/NewConversationActivity.java index 03ff6ff107..b5c933da4d 100644 --- a/src/org/thoughtcrime/securesms/NewConversationActivity.java +++ b/src/org/thoughtcrime/securesms/NewConversationActivity.java @@ -35,7 +35,7 @@ import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.NumberUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; import java.util.ArrayList; import java.util.LinkedList; diff --git a/src/org/thoughtcrime/securesms/PassphraseActivity.java b/src/org/thoughtcrime/securesms/PassphraseActivity.java index d629d73808..465b4b2759 100644 --- a/src/org/thoughtcrime/securesms/PassphraseActivity.java +++ b/src/org/thoughtcrime/securesms/PassphraseActivity.java @@ -23,7 +23,7 @@ import android.content.ServiceConnection; import android.os.IBinder; import android.support.v7.app.ActionBarActivity; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.util.MemoryCleaner; @@ -53,7 +53,7 @@ public abstract class PassphraseActivity extends ActionBarActivity { private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { - keyCachingService = ((KeyCachingService.KeyCachingBinder)service).getService(); + keyCachingService = ((KeyCachingService.KeySetBinder)service).getService(); keyCachingService.setMasterSecret(masterSecret); PassphraseActivity.this.unbindService(PassphraseActivity.this.serviceConnection); diff --git a/src/org/thoughtcrime/securesms/PassphraseChangeActivity.java b/src/org/thoughtcrime/securesms/PassphraseChangeActivity.java index 182b37a9c5..2c7333a5b9 100644 --- a/src/org/thoughtcrime/securesms/PassphraseChangeActivity.java +++ b/src/org/thoughtcrime/securesms/PassphraseChangeActivity.java @@ -26,7 +26,7 @@ import android.widget.TextView; import android.widget.Toast; import org.thoughtcrime.securesms.crypto.InvalidPassphraseException; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecretUtil; import org.thoughtcrime.securesms.util.MemoryCleaner; import org.thoughtcrime.securesms.util.TextSecurePreferences; diff --git a/src/org/thoughtcrime/securesms/PassphraseCreateActivity.java b/src/org/thoughtcrime/securesms/PassphraseCreateActivity.java index a0535eab8b..cbf1758127 100644 --- a/src/org/thoughtcrime/securesms/PassphraseCreateActivity.java +++ b/src/org/thoughtcrime/securesms/PassphraseCreateActivity.java @@ -23,10 +23,10 @@ import android.os.Bundle; import android.support.v7.app.ActionBar; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; -import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecretUtil; import org.thoughtcrime.securesms.util.MemoryCleaner; +import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.VersionTracker; import org.whispersystems.textsecure.util.Util; diff --git a/src/org/thoughtcrime/securesms/PassphrasePromptActivity.java b/src/org/thoughtcrime/securesms/PassphrasePromptActivity.java index 13e75ba2f8..d6cc328835 100644 --- a/src/org/thoughtcrime/securesms/PassphrasePromptActivity.java +++ b/src/org/thoughtcrime/securesms/PassphrasePromptActivity.java @@ -42,7 +42,7 @@ import android.widget.Toast; import org.thoughtcrime.securesms.crypto.InvalidPassphraseException; import org.thoughtcrime.securesms.crypto.MasterSecretUtil; import org.thoughtcrime.securesms.util.MemoryCleaner; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.whispersystems.textsecure.util.Util; /** diff --git a/src/org/thoughtcrime/securesms/PassphraseRequiredActionBarActivity.java b/src/org/thoughtcrime/securesms/PassphraseRequiredActionBarActivity.java index bf9cbb83bf..a7b1e9ebcc 100644 --- a/src/org/thoughtcrime/securesms/PassphraseRequiredActionBarActivity.java +++ b/src/org/thoughtcrime/securesms/PassphraseRequiredActionBarActivity.java @@ -3,7 +3,7 @@ package org.thoughtcrime.securesms; import android.os.Bundle; import android.support.v7.app.ActionBarActivity; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; public class PassphraseRequiredActionBarActivity extends ActionBarActivity implements PassphraseRequiredActivity { diff --git a/src/org/thoughtcrime/securesms/PassphraseRequiredActivity.java b/src/org/thoughtcrime/securesms/PassphraseRequiredActivity.java index ef79955f04..45fd862676 100644 --- a/src/org/thoughtcrime/securesms/PassphraseRequiredActivity.java +++ b/src/org/thoughtcrime/securesms/PassphraseRequiredActivity.java @@ -1,6 +1,6 @@ package org.thoughtcrime.securesms; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; public interface PassphraseRequiredActivity { public void onMasterSecretCleared(); diff --git a/src/org/thoughtcrime/securesms/PassphraseRequiredMixin.java b/src/org/thoughtcrime/securesms/PassphraseRequiredMixin.java index ec8f70603d..67cf63713d 100644 --- a/src/org/thoughtcrime/securesms/PassphraseRequiredMixin.java +++ b/src/org/thoughtcrime/securesms/PassphraseRequiredMixin.java @@ -2,23 +2,19 @@ package org.thoughtcrime.securesms; import android.app.Activity; import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.ServiceConnection; import android.os.Build; -import android.os.IBinder; import android.view.WindowManager; -import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.service.KeyCachingService; +import org.thoughtcrime.securesms.util.TextSecurePreferences; public class PassphraseRequiredMixin { - private KeyCachingServiceConnection serviceConnection; private BroadcastReceiver clearKeyReceiver; private BroadcastReceiver newKeyReceiver; @@ -29,13 +25,12 @@ public class PassphraseRequiredMixin { public void onResume(T activity) { initializeScreenshotSecurity(activity); initializeNewKeyReceiver(activity); - initializeServiceConnection(activity); + initializeFromMasterSecret(activity); KeyCachingService.registerPassphraseActivityStarted(activity); } public void onPause(T activity) { removeNewKeyReceiver(activity); - removeServiceConnection(activity); KeyCachingService.registerPassphraseActivityStopped(activity); } @@ -78,14 +73,14 @@ public class PassphraseRequiredMixin { activity.registerReceiver(newKeyReceiver, filter, KeyCachingService.KEY_PERMISSION, null); } - private void initializeServiceConnection(T activity) { - Intent cachingIntent = new Intent(activity, KeyCachingService.class); - activity.startService(cachingIntent); + private void initializeFromMasterSecret(T activity) { + MasterSecret masterSecret = KeyCachingService.getMasterSecret(activity); - this.serviceConnection = new KeyCachingServiceConnection(activity); - - Intent bindIntent = new Intent(activity, KeyCachingService.class); - activity.bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE); + if (masterSecret == null) { + activity.onMasterSecretCleared(); + } else { + activity.onNewMasterSecret(masterSecret); + } } private void removeClearKeyReceiver(Context context) { @@ -101,36 +96,4 @@ public class PassphraseRequiredMixin { newKeyReceiver = null; } } - - private void removeServiceConnection(Context context) { - if (this.serviceConnection != null) { - context.unbindService(this.serviceConnection); - } - } - - private static class KeyCachingServiceConnection implements ServiceConnection { - private final PassphraseRequiredActivity activity; - - public KeyCachingServiceConnection(PassphraseRequiredActivity activity) { - this.activity = activity; - } - - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - KeyCachingService keyCachingService = ((KeyCachingService.KeyCachingBinder)service).getService(); - MasterSecret masterSecret = keyCachingService.getMasterSecret(); - - if (masterSecret == null) { - activity.onMasterSecretCleared(); - } else { - activity.onNewMasterSecret(masterSecret); - } - } - - @Override - public void onServiceDisconnected(ComponentName name) { - } - - } - } diff --git a/src/org/thoughtcrime/securesms/PassphraseRequiredSherlockListActivity.java b/src/org/thoughtcrime/securesms/PassphraseRequiredSherlockListActivity.java index 4ca17d28e1..fcf0dd49d4 100644 --- a/src/org/thoughtcrime/securesms/PassphraseRequiredSherlockListActivity.java +++ b/src/org/thoughtcrime/securesms/PassphraseRequiredSherlockListActivity.java @@ -3,7 +3,7 @@ package org.thoughtcrime.securesms; import android.os.Bundle; import android.support.v7.app.ActionBarActivity; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; public class PassphraseRequiredSherlockListActivity extends ActionBarListActivity implements PassphraseRequiredActivity { diff --git a/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java b/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java index 41797d7d61..8e1d227279 100644 --- a/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java +++ b/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java @@ -30,48 +30,34 @@ import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; -import android.widget.Toast; -import org.thoughtcrime.securesms.crypto.DecryptingQueue; -import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor; -import org.thoughtcrime.securesms.crypto.TextSecureCipher; -import org.thoughtcrime.securesms.crypto.TextSecureIdentityKeyStore; +import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable; +import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; +import org.thoughtcrime.securesms.database.IdentityDatabase; +import org.thoughtcrime.securesms.jobs.SmsDecryptJob; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.service.SendReceiveService; -import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage; +import org.thoughtcrime.securesms.sms.IncomingIdentityUpdateMessage; +import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage; +import org.thoughtcrime.securesms.sms.IncomingPreKeyBundleMessage; import org.thoughtcrime.securesms.sms.IncomingTextMessage; -import org.thoughtcrime.securesms.sms.SmsTransportDetails; import org.thoughtcrime.securesms.util.MemoryCleaner; -import org.thoughtcrime.securesms.util.Util; -import org.whispersystems.libaxolotl.DuplicateMessageException; import org.whispersystems.libaxolotl.IdentityKey; import org.whispersystems.libaxolotl.InvalidKeyException; import org.whispersystems.libaxolotl.InvalidMessageException; import org.whispersystems.libaxolotl.InvalidVersionException; import org.whispersystems.libaxolotl.LegacyMessageException; -import org.whispersystems.libaxolotl.NoSessionException; -import org.whispersystems.libaxolotl.StaleKeyExchangeException; -import org.whispersystems.libaxolotl.UntrustedIdentityException; -import org.whispersystems.libaxolotl.protocol.CiphertextMessage; import org.whispersystems.libaxolotl.protocol.KeyExchangeMessage; import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage; import org.whispersystems.libaxolotl.state.IdentityKeyStore; -import org.whispersystems.textsecure.crypto.IdentityKeyParcelable; -import org.whispersystems.textsecure.crypto.MasterSecret; -import org.whispersystems.textsecure.crypto.TransportDetails; -import org.whispersystems.textsecure.push.IncomingPushMessage; -import org.whispersystems.libaxolotl.InvalidKeyIdException; -import org.whispersystems.textsecure.push.PushTransportDetails; -import org.whispersystems.textsecure.storage.RecipientDevice; +import org.whispersystems.libaxolotl.util.guava.Optional; +import org.whispersystems.textsecure.api.messages.TextSecureGroup; import org.whispersystems.textsecure.util.Base64; -import org.whispersystems.textsecure.util.InvalidNumberException; import java.io.IOException; -import static org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.Type; - /** * Activity for displaying sent/received session keys. * @@ -87,13 +73,11 @@ public class ReceiveKeyActivity extends Activity { private Recipient recipient; private int recipientDeviceId; - private long threadId; private long messageId; - private MasterSecret masterSecret; - private PreKeyWhisperMessage keyExchangeMessageBundle; - private KeyExchangeMessage keyExchangeMessage; - private IdentityKey identityUpdateMessage; + private MasterSecret masterSecret; + private IncomingKeyExchangeMessage message; + private IdentityKey identityKey; @Override protected void onCreate(Bundle state) { @@ -118,7 +102,7 @@ public class ReceiveKeyActivity extends Activity { } private void initializeText() { - if (isTrusted(keyExchangeMessage, keyExchangeMessageBundle, identityUpdateMessage)) { + if (isTrusted(this.identityKey)) { initializeTrustedText(); } else { initializeUntrustedText(); @@ -135,16 +119,10 @@ public class ReceiveKeyActivity extends Activity { spannableString.setSpan(new ClickableSpan() { @Override public void onClick(View widget) { - IdentityKey remoteIdentity; - - if (identityUpdateMessage != null) remoteIdentity = identityUpdateMessage; - else if (keyExchangeMessageBundle != null) remoteIdentity = keyExchangeMessageBundle.getIdentityKey(); - else remoteIdentity = keyExchangeMessage.getIdentityKey(); - Intent intent = new Intent(ReceiveKeyActivity.this, VerifyIdentityActivity.class); intent.putExtra("recipient", recipient); intent.putExtra("master_secret", masterSecret); - intent.putExtra("remote_identity", new IdentityKeyParcelable(remoteIdentity)); + intent.putExtra("remote_identity", new IdentityKeyParcelable(identityKey)); startActivity(intent); } }, getString(R.string.ReceiveKeyActivity_the_signature_on_this_key_exchange_is_different).length() +1, @@ -154,43 +132,32 @@ public class ReceiveKeyActivity extends Activity { descriptionText.setMovementMethod(LinkMovementMethod.getInstance()); } - private boolean isTrusted(KeyExchangeMessage message, PreKeyWhisperMessage messageBundle, IdentityKey identityUpdateMessage) { + private boolean isTrusted(IdentityKey identityKey) { long recipientId = recipient.getRecipientId(); IdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(this, masterSecret); - if (message != null) return identityKeyStore.isTrustedIdentity(recipientId, message.getIdentityKey()); - else if (messageBundle != null) return identityKeyStore.isTrustedIdentity(recipientId, messageBundle.getIdentityKey()); - else if (identityUpdateMessage != null) return identityKeyStore.isTrustedIdentity(recipientId, identityUpdateMessage); - - return false; + return identityKeyStore.isTrustedIdentity(recipientId, identityKey); } private void initializeKey() throws InvalidKeyException, InvalidVersionException, - InvalidMessageException, LegacyMessageException + InvalidMessageException, LegacyMessageException { - try { - String messageBody = getIntent().getStringExtra("body"); + IncomingTextMessage message = new IncomingTextMessage(recipient.getNumber(), + recipientDeviceId, + System.currentTimeMillis(), + getIntent().getStringExtra("body"), + Optional.absent()); - if (getIntent().getBooleanExtra("is_bundle", false)) { - boolean isPush = getIntent().getBooleanExtra("is_push", false); - byte[] body; - - if (isPush) { - body = Base64.decode(messageBody.getBytes()); - } else { - body = new SmsTransportDetails().getDecodedMessage(messageBody.getBytes()); - } - - this.keyExchangeMessageBundle = new PreKeyWhisperMessage(body); - } else if (getIntent().getBooleanExtra("is_identity_update", false)) { - this.identityUpdateMessage = new IdentityKey(Base64.decodeWithoutPadding(messageBody), 0); - } else { - this.keyExchangeMessage = new KeyExchangeMessage(Base64.decodeWithoutPadding(messageBody)); - } - } catch (IOException e) { - throw new AssertionError(e); + if (getIntent().getBooleanExtra("is_bundle", false)) { + this.message = new IncomingPreKeyBundleMessage(message, message.getMessageBody()); + } else if (getIntent().getBooleanExtra("is_identity_update", false)) { + this.message = new IncomingIdentityUpdateMessage(message, message.getMessageBody()); + } else { + this.message = new IncomingKeyExchangeMessage(message, message.getMessageBody()); } + + this.identityKey = getIdentityKey(this.message); } private void initializeResources() { @@ -199,7 +166,6 @@ public class ReceiveKeyActivity extends Activity { this.cancelButton = (Button) findViewById(R.id.cancel_button); this.recipient = getIntent().getParcelableExtra("recipient"); this.recipientDeviceId = getIntent().getIntExtra("recipient_device_id", -1); - this.threadId = getIntent().getLongExtra("thread_id", -1); this.messageId = getIntent().getLongExtra("message_id", -1); this.masterSecret = getIntent().getParcelableExtra("master_secret"); } @@ -209,6 +175,26 @@ public class ReceiveKeyActivity extends Activity { this.cancelButton.setOnClickListener(new CancelListener()); } + private IdentityKey getIdentityKey(IncomingKeyExchangeMessage message) + throws InvalidKeyException, InvalidVersionException, + InvalidMessageException, LegacyMessageException + { + try { + if (message.isIdentityUpdate()) { + return new IdentityKey(Base64.decodeWithoutPadding(message.getMessageBody()), 0); + } else if (message.isPreKeyBundle()) { + boolean isPush = getIntent().getBooleanExtra("is_push", false); + + if (isPush) return new PreKeyWhisperMessage(Base64.decode(message.getMessageBody())).getIdentityKey(); + else return new PreKeyWhisperMessage(Base64.decodeWithoutPadding(message.getMessageBody())).getIdentityKey(); + } else { + return new KeyExchangeMessage(Base64.decodeWithoutPadding(message.getMessageBody())).getIdentityKey(); + } + } catch (IOException e) { + throw new AssertionError(e); + } + } + private class OkListener implements View.OnClickListener { @Override public void onClick(View v) { @@ -225,75 +211,20 @@ public class ReceiveKeyActivity extends Activity { @Override protected Void doInBackground(Void... params) { - if (keyExchangeMessage != null) { - try { - RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), - recipientDeviceId); - KeyExchangeProcessor processor = new KeyExchangeProcessor(ReceiveKeyActivity.this, - masterSecret, recipientDevice); + IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(ReceiveKeyActivity.this); + EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this); + Context context = ReceiveKeyActivity.this; - IdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(ReceiveKeyActivity.this, - masterSecret); - identityKeyStore.saveIdentity(recipient.getRecipientId(), keyExchangeMessage.getIdentityKey()); + identityDatabase.saveIdentity(masterSecret, recipient.getRecipientId(), identityKey); - processor.processKeyExchangeMessage(keyExchangeMessage, threadId); - - DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this) - .markAsProcessedKeyExchange(messageId); - } catch (InvalidKeyException e) { - Log.w("ReceiveKeyActivity", e); - DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this) - .markAsCorruptKeyExchange(messageId); - } catch (StaleKeyExchangeException e) { - DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this) - .markAsStaleKeyExchange(messageId); - } catch (UntrustedIdentityException e) { - throw new AssertionError(e); - } - } else if (keyExchangeMessageBundle != null) { - try { - Context context = ReceiveKeyActivity.this; - EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); - RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), recipientDeviceId); - - TransportDetails transportDetails = getIntent().getBooleanExtra("is_push", false) ? - new PushTransportDetails(keyExchangeMessageBundle.getMessageVersion()) : - new SmsTransportDetails(); - - TextSecureCipher cipher = new TextSecureCipher(ReceiveKeyActivity.this, masterSecret, recipientDevice, transportDetails); - IdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(ReceiveKeyActivity.this, - masterSecret); - - identityKeyStore.saveIdentity(recipient.getRecipientId(), keyExchangeMessageBundle.getIdentityKey()); - byte[] plaintext = cipher.decrypt(keyExchangeMessageBundle); - - database.updateBundleMessageBody(masterSecret, messageId, ""); - database.updateMessageBody(masterSecret, messageId, new String(plaintext)); - - } catch (InvalidKeyIdException | InvalidKeyException | LegacyMessageException | NoSessionException e) { - Log.w("ReceiveKeyActivity", e); - DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this) - .markAsCorruptKeyExchange(messageId); - } catch (InvalidMessageException e) { - Log.w("ReceiveKeyActivity", e); - DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this) - .markAsDecryptFailed(messageId); - } catch (DuplicateMessageException e) { - Log.w("ReceiveKeyActivity", e); - DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this) - .markAsDecryptDuplicate(messageId); - } catch (UntrustedIdentityException e) { - Log.w("ReceiveKeyActivity", e); - Toast.makeText(ReceiveKeyActivity.this, "Untrusted!", Toast.LENGTH_LONG).show(); - } - } else if (identityUpdateMessage != null) { - DatabaseFactory.getIdentityDatabase(ReceiveKeyActivity.this) - .saveIdentity(masterSecret, recipient.getRecipientId(), identityUpdateMessage); - - DatabaseFactory.getSmsDatabase(ReceiveKeyActivity.this).markAsProcessedKeyExchange(messageId); + if (message.isIdentityUpdate()) { + smsDatabase.markAsProcessedKeyExchange(messageId); + } else { + ApplicationContext.getInstance(context) + .getJobManager() + .add(new SmsDecryptJob(context, messageId)); } - return null; } diff --git a/src/org/thoughtcrime/securesms/RegistrationActivity.java b/src/org/thoughtcrime/securesms/RegistrationActivity.java index a7c75d1729..e5a8bab242 100644 --- a/src/org/thoughtcrime/securesms/RegistrationActivity.java +++ b/src/org/thoughtcrime/securesms/RegistrationActivity.java @@ -27,7 +27,7 @@ import com.google.i18n.phonenumbers.Phonenumber; import org.thoughtcrime.securesms.util.Dialogs; import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.whispersystems.textsecure.util.PhoneNumberFormatter; import org.whispersystems.textsecure.util.Util; diff --git a/src/org/thoughtcrime/securesms/RegistrationProgressActivity.java b/src/org/thoughtcrime/securesms/RegistrationProgressActivity.java index 02fe792950..eb28f51693 100644 --- a/src/org/thoughtcrime/securesms/RegistrationProgressActivity.java +++ b/src/org/thoughtcrime/securesms/RegistrationProgressActivity.java @@ -34,7 +34,7 @@ import org.thoughtcrime.securesms.push.PushServiceSocketFactory; import org.thoughtcrime.securesms.service.RegistrationService; import org.thoughtcrime.securesms.util.Dialogs; import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.whispersystems.textsecure.push.exceptions.ExpectationFailedException; import org.whispersystems.textsecure.push.PushServiceSocket; import org.whispersystems.textsecure.push.exceptions.RateLimitException; diff --git a/src/org/thoughtcrime/securesms/RoutingActivity.java b/src/org/thoughtcrime/securesms/RoutingActivity.java index 5943048013..532532e56e 100644 --- a/src/org/thoughtcrime/securesms/RoutingActivity.java +++ b/src/org/thoughtcrime/securesms/RoutingActivity.java @@ -10,7 +10,7 @@ import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.RecipientFormattingException; import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; public class RoutingActivity extends PassphraseRequiredActionBarActivity { diff --git a/src/org/thoughtcrime/securesms/ShareActivity.java b/src/org/thoughtcrime/securesms/ShareActivity.java index 8631dc8c7a..a7ea0db223 100644 --- a/src/org/thoughtcrime/securesms/ShareActivity.java +++ b/src/org/thoughtcrime/securesms/ShareActivity.java @@ -19,19 +19,16 @@ package org.thoughtcrime.securesms; import android.content.Intent; import android.net.Uri; -import android.os.Build; import android.os.Bundle; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; -import android.view.WindowManager; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.MemoryCleaner; -import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.whispersystems.textsecure.crypto.MasterSecret; /** * An activity to quickly share content with contacts diff --git a/src/org/thoughtcrime/securesms/ShareFragment.java b/src/org/thoughtcrime/securesms/ShareFragment.java index da76f123bd..d537b6eecd 100644 --- a/src/org/thoughtcrime/securesms/ShareFragment.java +++ b/src/org/thoughtcrime/securesms/ShareFragment.java @@ -31,7 +31,7 @@ import android.widget.ListView; import org.thoughtcrime.securesms.database.loaders.ConversationListLoader; import org.thoughtcrime.securesms.recipients.Recipients; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; /** * A fragment to select and share to open conversations diff --git a/src/org/thoughtcrime/securesms/ShareListAdapter.java b/src/org/thoughtcrime/securesms/ShareListAdapter.java index 100a67b2cc..a5ed412233 100644 --- a/src/org/thoughtcrime/securesms/ShareListAdapter.java +++ b/src/org/thoughtcrime/securesms/ShareListAdapter.java @@ -27,8 +27,8 @@ import android.widget.AbsListView; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.model.ThreadRecord; -import org.whispersystems.textsecure.crypto.MasterCipher; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterCipher; +import org.thoughtcrime.securesms.crypto.MasterSecret; /** * A CursorAdapter for building a list of open conversations diff --git a/src/org/thoughtcrime/securesms/VerifyIdentityActivity.java b/src/org/thoughtcrime/securesms/VerifyIdentityActivity.java index b97e944d3e..5f4e47922c 100644 --- a/src/org/thoughtcrime/securesms/VerifyIdentityActivity.java +++ b/src/org/thoughtcrime/securesms/VerifyIdentityActivity.java @@ -29,10 +29,10 @@ import org.thoughtcrime.securesms.util.MemoryCleaner; import org.whispersystems.libaxolotl.IdentityKey; import org.whispersystems.libaxolotl.state.SessionRecord; import org.whispersystems.libaxolotl.state.SessionStore; -import org.whispersystems.textsecure.crypto.IdentityKeyParcelable; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.whispersystems.textsecure.storage.RecipientDevice; -import org.whispersystems.textsecure.storage.TextSecureSessionStore; +import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore; /** * Activity for verifying identity keys. diff --git a/src/org/thoughtcrime/securesms/ViewIdentityActivity.java b/src/org/thoughtcrime/securesms/ViewIdentityActivity.java index a919f1190c..97853ed0cf 100644 --- a/src/org/thoughtcrime/securesms/ViewIdentityActivity.java +++ b/src/org/thoughtcrime/securesms/ViewIdentityActivity.java @@ -20,7 +20,7 @@ import android.os.Bundle; import android.widget.TextView; import org.whispersystems.libaxolotl.IdentityKey; -import org.whispersystems.textsecure.crypto.IdentityKeyParcelable; +import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable; /** * Activity for displaying an identity key. diff --git a/src/org/thoughtcrime/securesms/ViewLocalIdentityActivity.java b/src/org/thoughtcrime/securesms/ViewLocalIdentityActivity.java index dab1bbd45b..15faa29cb7 100644 --- a/src/org/thoughtcrime/securesms/ViewLocalIdentityActivity.java +++ b/src/org/thoughtcrime/securesms/ViewLocalIdentityActivity.java @@ -24,7 +24,7 @@ import android.view.MenuItem; import android.widget.Toast; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; -import org.whispersystems.textsecure.crypto.IdentityKeyParcelable; +import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable; /** * Activity that displays the local identity key and offers the option to regenerate it. diff --git a/src/org/thoughtcrime/securesms/components/PushRegistrationReminder.java b/src/org/thoughtcrime/securesms/components/PushRegistrationReminder.java index 910e545b27..d98e4e2d2a 100644 --- a/src/org/thoughtcrime/securesms/components/PushRegistrationReminder.java +++ b/src/org/thoughtcrime/securesms/components/PushRegistrationReminder.java @@ -8,7 +8,7 @@ import android.view.View.OnClickListener; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.RegistrationActivity; import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; public class PushRegistrationReminder extends Reminder { public static final long REMINDER_INTERVAL_MS = 3 * 24 * 60 * 60 * 1000; diff --git a/src/org/thoughtcrime/securesms/components/SystemSmsImportReminder.java b/src/org/thoughtcrime/securesms/components/SystemSmsImportReminder.java index 6badeabec4..6377162141 100644 --- a/src/org/thoughtcrime/securesms/components/SystemSmsImportReminder.java +++ b/src/org/thoughtcrime/securesms/components/SystemSmsImportReminder.java @@ -9,7 +9,7 @@ import org.thoughtcrime.securesms.ConversationListActivity; import org.thoughtcrime.securesms.DatabaseMigrationActivity; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.service.ApplicationMigrationService; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; public class SystemSmsImportReminder extends Reminder { diff --git a/src/org/thoughtcrime/securesms/crypto/AsymmetricMasterCipher.java b/src/org/thoughtcrime/securesms/crypto/AsymmetricMasterCipher.java index d57c63d7ad..e7f1bcd8e5 100644 --- a/src/org/thoughtcrime/securesms/crypto/AsymmetricMasterCipher.java +++ b/src/org/thoughtcrime/securesms/crypto/AsymmetricMasterCipher.java @@ -23,9 +23,6 @@ import org.whispersystems.libaxolotl.ecc.Curve; import org.whispersystems.libaxolotl.ecc.ECKeyPair; import org.whispersystems.libaxolotl.ecc.ECPrivateKey; import org.whispersystems.libaxolotl.ecc.ECPublicKey; -import org.whispersystems.textsecure.crypto.MasterCipher; -import org.whispersystems.textsecure.crypto.MasterSecret; -import org.whispersystems.textsecure.crypto.PublicKey; import org.whispersystems.textsecure.util.Base64; import org.whispersystems.textsecure.util.Conversions; import org.whispersystems.textsecure.util.Util; diff --git a/src/org/thoughtcrime/securesms/crypto/DecryptingPartInputStream.java b/src/org/thoughtcrime/securesms/crypto/DecryptingPartInputStream.java index b81ffe23e3..ea5989a223 100644 --- a/src/org/thoughtcrime/securesms/crypto/DecryptingPartInputStream.java +++ b/src/org/thoughtcrime/securesms/crypto/DecryptingPartInputStream.java @@ -37,8 +37,6 @@ import javax.crypto.spec.SecretKeySpec; import android.util.Log; -import org.whispersystems.textsecure.crypto.MasterSecret; - /** * Class for streaming an encrypted MMS "part" off the disk. * diff --git a/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java b/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java deleted file mode 100644 index 520b119c24..0000000000 --- a/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java +++ /dev/null @@ -1,481 +0,0 @@ -/** - * Copyright (C) 2011 Whisper Systems - * Copyright (C) 2013 Open Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.thoughtcrime.securesms.crypto; - -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.util.Log; - -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; -import org.thoughtcrime.securesms.database.MmsDatabase; -import org.thoughtcrime.securesms.database.PushDatabase; -import org.thoughtcrime.securesms.database.SmsDatabase; -import org.thoughtcrime.securesms.database.model.SmsMessageRecord; -import org.thoughtcrime.securesms.mms.IncomingMediaMessage; -import org.thoughtcrime.securesms.mms.TextTransport; -import org.thoughtcrime.securesms.notifications.MessageNotifier; -import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientFactory; -import org.thoughtcrime.securesms.recipients.RecipientFormattingException; -import org.thoughtcrime.securesms.recipients.Recipients; -import org.thoughtcrime.securesms.service.PushReceiver; -import org.thoughtcrime.securesms.service.SendReceiveService; -import org.thoughtcrime.securesms.sms.SmsTransportDetails; -import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.whispersystems.libaxolotl.DuplicateMessageException; -import org.whispersystems.libaxolotl.InvalidKeyException; -import org.whispersystems.libaxolotl.InvalidMessageException; -import org.whispersystems.libaxolotl.InvalidVersionException; -import org.whispersystems.libaxolotl.LegacyMessageException; -import org.whispersystems.libaxolotl.NoSessionException; -import org.whispersystems.libaxolotl.StaleKeyExchangeException; -import org.whispersystems.libaxolotl.UntrustedIdentityException; -import org.whispersystems.libaxolotl.protocol.KeyExchangeMessage; -import org.whispersystems.libaxolotl.protocol.WhisperMessage; -import org.whispersystems.libaxolotl.state.SessionStore; -import org.whispersystems.textsecure.crypto.MasterSecret; -import org.whispersystems.textsecure.crypto.TransportDetails; -import org.whispersystems.textsecure.push.IncomingPushMessage; -import org.whispersystems.textsecure.push.PushTransportDetails; -import org.whispersystems.textsecure.storage.RecipientDevice; -import org.whispersystems.textsecure.storage.SessionUtil; -import org.whispersystems.textsecure.storage.TextSecureSessionStore; -import org.whispersystems.textsecure.util.Base64; -import org.whispersystems.textsecure.util.Hex; -import org.whispersystems.textsecure.util.Util; - -import java.io.IOException; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; - -import ws.com.google.android.mms.ContentType; -import ws.com.google.android.mms.MmsException; -import ws.com.google.android.mms.pdu.MultimediaMessagePdu; -import ws.com.google.android.mms.pdu.PduParser; -import ws.com.google.android.mms.pdu.RetrieveConf; - -/** - * A work queue for processing a number of encryption operations. - * - * @author Moxie Marlinspike - */ - -public class DecryptingQueue { - - private static final Executor executor = Executors.newSingleThreadExecutor(); - - public static void scheduleDecryption(Context context, MasterSecret masterSecret, - long messageId, long threadId, MultimediaMessagePdu mms) - { - MmsDecryptionItem runnable = new MmsDecryptionItem(context, masterSecret, messageId, threadId, mms); - executor.execute(runnable); - } - - public static void scheduleDecryption(Context context, MasterSecret masterSecret, - long messageId, long threadId, String originator, int deviceId, - String body, boolean isSecureMessage, boolean isKeyExchange, - boolean isEndSession) - { - DecryptionWorkItem runnable = new DecryptionWorkItem(context, masterSecret, messageId, threadId, - originator, deviceId, body, - isSecureMessage, isKeyExchange, isEndSession); - executor.execute(runnable); - } - - public static void scheduleDecryption(Context context, MasterSecret masterSecret, - long messageId, IncomingPushMessage message) - { - PushDecryptionWorkItem runnable = new PushDecryptionWorkItem(context, masterSecret, - messageId, message); - executor.execute(runnable); - } - - public static void schedulePendingDecrypts(Context context, MasterSecret masterSecret) { - Log.w("DecryptingQueue", "Processing pending decrypts..."); - - EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context); - PushDatabase pushDatabase = DatabaseFactory.getPushDatabase(context); - - EncryptingSmsDatabase.Reader smsReader = null; - PushDatabase.Reader pushReader = null; - - SmsMessageRecord record; - IncomingPushMessage message; - - try { - smsReader = smsDatabase.getDecryptInProgressMessages(masterSecret); - pushReader = pushDatabase.readerFor(pushDatabase.getPending()); - - while ((record = smsReader.getNext()) != null) { - scheduleDecryptFromCursor(context, masterSecret, record); - } - - while ((message = pushReader.getNext()) != null) { - if (message.isPreKeyBundle()) { - Intent intent = new Intent(context, SendReceiveService.class); - intent.setAction(SendReceiveService.RECEIVE_PUSH_ACTION); - intent.putExtra("message", message); - context.startService(intent); - - pushDatabase.delete(pushReader.getCurrentId()); - } else { - scheduleDecryption(context, masterSecret, pushReader.getCurrentId(), message); - } - } - - } finally { - if (smsReader != null) - smsReader.close(); - - if (pushReader != null) - pushReader.close(); - } - } - - public static void scheduleRogueMessages(Context context, MasterSecret masterSecret, Recipient recipient) { - SmsDatabase.Reader reader = null; - SmsMessageRecord record; - - try { - Cursor cursor = DatabaseFactory.getSmsDatabase(context).getEncryptedRogueMessages(recipient); - reader = DatabaseFactory.getEncryptingSmsDatabase(context).readerFor(masterSecret, cursor); - - while ((record = reader.getNext()) != null) { - DatabaseFactory.getSmsDatabase(context).markAsDecrypting(record.getId()); - scheduleDecryptFromCursor(context, masterSecret, record); - } - } finally { - if (reader != null) - reader.close(); - } - } - - private static void scheduleDecryptFromCursor(Context context, MasterSecret masterSecret, - SmsMessageRecord record) - { - long messageId = record.getId(); - long threadId = record.getThreadId(); - String body = record.getBody().getBody(); - String originator = record.getIndividualRecipient().getNumber(); - int originatorDeviceId = record.getRecipientDeviceId(); - boolean isSecureMessage = record.isSecure(); - boolean isKeyExchange = record.isKeyExchange(); - boolean isEndSession = record.isEndSession(); - - scheduleDecryption(context, masterSecret, messageId, threadId, - originator, originatorDeviceId, body, - isSecureMessage, isKeyExchange, isEndSession); - } - - private static class PushDecryptionWorkItem implements Runnable { - - private Context context; - private MasterSecret masterSecret; - private long messageId; - private IncomingPushMessage message; - - public PushDecryptionWorkItem(Context context, MasterSecret masterSecret, - long messageId, IncomingPushMessage message) - { - this.context = context; - this.masterSecret = masterSecret; - this.messageId = messageId; - this.message = message; - } - - public void run() { - try { - SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret); - Recipients recipients = RecipientFactory.getRecipientsFromString(context, message.getSource(), false); - Recipient recipient = recipients.getPrimaryRecipient(); - RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), message.getSourceDevice()); - - if (!sessionStore.containsSession(recipientDevice.getRecipientId(), recipientDevice.getDeviceId())) { - sendResult(PushReceiver.RESULT_NO_SESSION); - return; - } - - int sessionVersion = SessionUtil.getSessionVersion(context, masterSecret, recipientDevice); - TransportDetails transportDetails = new PushTransportDetails(sessionVersion); - TextSecureCipher textSecureCipher = new TextSecureCipher(context, masterSecret, recipientDevice, transportDetails); - byte[] plaintextBody = textSecureCipher.decrypt(new WhisperMessage(message.getBody())); - - message = message.withBody(plaintextBody); - sendResult(PushReceiver.RESULT_OK); - } catch (InvalidMessageException | LegacyMessageException | RecipientFormattingException e) { - Log.w("DecryptionQueue", e); - sendResult(PushReceiver.RESULT_DECRYPT_FAILED); - } catch (DuplicateMessageException e) { - Log.w("DecryptingQueue", e); - sendResult(PushReceiver.RESULT_DECRYPT_DUPLICATE); - } catch (NoSessionException e) { - Log.w("DecryptingQueue", e); - sendResult(PushReceiver.RESULT_NO_SESSION); - } - } - - private void sendResult(int result) { - Intent intent = new Intent(context, SendReceiveService.class); - intent.setAction(SendReceiveService.DECRYPTED_PUSH_ACTION); - intent.putExtra("message", message); - intent.putExtra("message_id", messageId); - intent.putExtra("result", result); - context.startService(intent); - } - } - - private static class MmsDecryptionItem implements Runnable { - private long messageId; - private long threadId; - private Context context; - private MasterSecret masterSecret; - private MultimediaMessagePdu pdu; - - public MmsDecryptionItem(Context context, MasterSecret masterSecret, - long messageId, long threadId, MultimediaMessagePdu pdu) - { - this.context = context; - this.masterSecret = masterSecret; - this.messageId = messageId; - this.threadId = threadId; - this.pdu = pdu; - } - - private byte[] getEncryptedData() { - for (int i=0;i 2) { - Log.w("DecryptingQueue", "Attempting truncated decrypt..."); - byte[] truncated = Util.trim(ciphertextPduBytes, ciphertextPduBytes.length - 1); - decodedCiphertext = transportDetails.getDecodedMessage(truncated); - plaintextPduBytes = cipher.decrypt(new WhisperMessage(decodedCiphertext)); - } else { - throw ime; - } - } - - MultimediaMessagePdu plaintextGenericPdu = (MultimediaMessagePdu)new PduParser(plaintextPduBytes).parse(); - RetrieveConf plaintextPdu = new RetrieveConf(plaintextGenericPdu.getPduHeaders(), - plaintextGenericPdu.getBody()); - Log.w("DecryptingQueue", "Successfully decrypted MMS!"); - database.insertSecureDecryptedMessageInbox(masterSecret, new IncomingMediaMessage(plaintextPdu), threadId); - database.delete(messageId); - } catch (RecipientFormattingException | IOException | MmsException | InvalidMessageException rfe) { - Log.w("DecryptingQueue", rfe); - database.markAsDecryptFailed(messageId, threadId); - } catch (DuplicateMessageException dme) { - Log.w("DecryptingQueue", dme); - database.markAsDecryptDuplicate(messageId, threadId); - } catch (LegacyMessageException lme) { - Log.w("DecryptingQueue", lme); - database.markAsLegacyVersion(messageId, threadId); - } catch (NoSessionException nse) { - Log.w("DecryptingQueue", nse); - database.markAsNoSession(messageId, threadId); - } - } - } - - - private static class DecryptionWorkItem implements Runnable { - - private final long messageId; - private final long threadId; - private final Context context; - private final MasterSecret masterSecret; - private final String body; - private final String originator; - private final int deviceId; - private final boolean isSecureMessage; - private final boolean isKeyExchange; - private final boolean isEndSession; - - public DecryptionWorkItem(Context context, MasterSecret masterSecret, long messageId, long threadId, - String originator, int deviceId, String body, boolean isSecureMessage, - boolean isKeyExchange, boolean isEndSession) - { - this.context = context; - this.messageId = messageId; - this.threadId = threadId; - this.masterSecret = masterSecret; - this.body = body; - this.originator = originator; - this.deviceId = deviceId; - this.isSecureMessage = isSecureMessage; - this.isKeyExchange = isKeyExchange; - this.isEndSession = isEndSession; - } - - private void handleRemoteAsymmetricEncrypt() { - EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); - String plaintextBody; - - try { - SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret); - Recipients recipients = RecipientFactory.getRecipientsFromString(context, originator, false); - Recipient recipient = recipients.getPrimaryRecipient(); - RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), deviceId); - - SmsTransportDetails transportDetails = new SmsTransportDetails(); - byte[] decodedCiphertext = transportDetails.getDecodedMessage(body.getBytes()); - - if (!sessionStore.containsSession(recipientDevice.getRecipientId(), recipientDevice.getDeviceId())) { - if (WhisperMessage.isLegacy(decodedCiphertext)) database.markAsLegacyVersion(messageId); - else database.markAsNoSession(messageId); - return; - } - - TextSecureCipher cipher = new TextSecureCipher(context, masterSecret, recipientDevice, transportDetails); - byte[] paddedPlaintext = cipher.decrypt(new WhisperMessage(decodedCiphertext)); - - plaintextBody = new String(transportDetails.getStrippedPaddingMessageBody(paddedPlaintext)); - - if (isEndSession && - "TERMINATE".equals(plaintextBody) && - sessionStore.containsSession(recipientDevice.getRecipientId(), recipientDevice.getDeviceId())) - { - sessionStore.deleteSession(recipientDevice.getRecipientId(), recipientDevice.getDeviceId()); - } - } catch (InvalidMessageException | IOException | RecipientFormattingException e) { - Log.w("DecryptionQueue", e); - database.markAsDecryptFailed(messageId); - return; - } catch (LegacyMessageException lme) { - Log.w("DecryptionQueue", lme); - database.markAsLegacyVersion(messageId); - return; - } catch (DuplicateMessageException e) { - Log.w("DecryptionQueue", e); - database.markAsDecryptDuplicate(messageId); - return; - } catch (NoSessionException e) { - Log.w("DecryptingQueue", e); - database.markAsNoSession(messageId); - return; - } - - database.updateMessageBody(masterSecret, messageId, plaintextBody); - MessageNotifier.updateNotification(context, masterSecret); - } - - private void handleLocalAsymmetricEncrypt() { - EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); - String plaintextBody; - - try { - AsymmetricMasterCipher asymmetricMasterCipher = new AsymmetricMasterCipher(MasterSecretUtil.getAsymmetricMasterSecret(context, masterSecret)); - plaintextBody = asymmetricMasterCipher.decryptBody(body); - - if (isKeyExchange) { - handleKeyExchangeProcessing(plaintextBody); - } - - database.updateMessageBody(masterSecret, messageId, plaintextBody); - MessageNotifier.updateNotification(context, masterSecret); - } catch (InvalidMessageException | IOException ime) { - Log.w("DecryptionQueue", ime); - database.markAsDecryptFailed(messageId); - } - } - - private void handleKeyExchangeProcessing(String plaintextBody) { - if (TextSecurePreferences.isAutoRespondKeyExchangeEnabled(context)) { - try { - Recipient recipient = RecipientFactory.getRecipientsFromString(context, originator, false) - .getPrimaryRecipient(); - RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), deviceId); - KeyExchangeMessage message = new KeyExchangeMessage(Base64.decodeWithoutPadding(plaintextBody)); - KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipientDevice); - - processor.processKeyExchangeMessage(message, threadId); - DatabaseFactory.getEncryptingSmsDatabase(context).markAsProcessedKeyExchange(messageId); - } catch (InvalidVersionException e) { - Log.w("DecryptingQueue", e); - DatabaseFactory.getEncryptingSmsDatabase(context).markAsInvalidVersionKeyExchange(messageId); - } catch (InvalidMessageException | IOException | InvalidKeyException | RecipientFormattingException e) { - Log.w("DecryptingQueue", e); - DatabaseFactory.getEncryptingSmsDatabase(context).markAsCorruptKeyExchange(messageId); - } catch (LegacyMessageException e) { - Log.w("DecryptingQueue", e); - DatabaseFactory.getEncryptingSmsDatabase(context).markAsLegacyVersion(messageId); - } catch (StaleKeyExchangeException e) { - Log.w("DecryptingQueue", e); - DatabaseFactory.getEncryptingSmsDatabase(context).markAsStaleKeyExchange(messageId); - } catch (UntrustedIdentityException e) { - Log.w("DecryptingQueue", e); - } - } - } - - @Override - public void run() { - if (isSecureMessage || isEndSession) { - handleRemoteAsymmetricEncrypt(); - } else { - handleLocalAsymmetricEncrypt(); - } - } - } -} diff --git a/src/org/thoughtcrime/securesms/crypto/EncryptingPartOutputStream.java b/src/org/thoughtcrime/securesms/crypto/EncryptingPartOutputStream.java index 2ae2039cdd..7a1856f7a2 100644 --- a/src/org/thoughtcrime/securesms/crypto/EncryptingPartOutputStream.java +++ b/src/org/thoughtcrime/securesms/crypto/EncryptingPartOutputStream.java @@ -32,8 +32,6 @@ import javax.crypto.spec.SecretKeySpec; import android.util.Log; -import org.whispersystems.textsecure.crypto.MasterSecret; - /** * A class for streaming an encrypted MMS "part" to disk. * diff --git a/library/src/org/whispersystems/textsecure/crypto/IdentityKeyParcelable.java b/src/org/thoughtcrime/securesms/crypto/IdentityKeyParcelable.java similarity index 97% rename from library/src/org/whispersystems/textsecure/crypto/IdentityKeyParcelable.java rename to src/org/thoughtcrime/securesms/crypto/IdentityKeyParcelable.java index c646609bc1..4969e8f3e8 100644 --- a/library/src/org/whispersystems/textsecure/crypto/IdentityKeyParcelable.java +++ b/src/org/thoughtcrime/securesms/crypto/IdentityKeyParcelable.java @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.whispersystems.textsecure.crypto; +package org.thoughtcrime.securesms.crypto; import android.os.Parcel; import android.os.Parcelable; diff --git a/src/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java b/src/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java index f302261fcc..fd23058724 100644 --- a/src/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java +++ b/src/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java @@ -28,8 +28,6 @@ import org.whispersystems.libaxolotl.InvalidKeyException; import org.whispersystems.libaxolotl.ecc.Curve; import org.whispersystems.libaxolotl.ecc.ECKeyPair; import org.whispersystems.libaxolotl.ecc.ECPrivateKey; -import org.whispersystems.textsecure.crypto.MasterCipher; -import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.util.Base64; import java.io.IOException; diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeInitiator.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeInitiator.java index 8128465513..02bfa47b88 100644 --- a/src/org/thoughtcrime/securesms/crypto/KeyExchangeInitiator.java +++ b/src/org/thoughtcrime/securesms/crypto/KeyExchangeInitiator.java @@ -22,6 +22,9 @@ import android.content.Context; import android.content.DialogInterface; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore; +import org.thoughtcrime.securesms.crypto.storage.TextSecurePreKeyStore; +import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage; @@ -33,10 +36,7 @@ import org.whispersystems.libaxolotl.state.IdentityKeyStore; import org.whispersystems.libaxolotl.state.PreKeyStore; import org.whispersystems.libaxolotl.state.SessionRecord; import org.whispersystems.libaxolotl.state.SessionStore; -import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.storage.RecipientDevice; -import org.whispersystems.textsecure.storage.TextSecurePreKeyStore; -import org.whispersystems.textsecure.storage.TextSecureSessionStore; import org.whispersystems.textsecure.util.Base64; public class KeyExchangeInitiator { diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java deleted file mode 100644 index 9eed9697fd..0000000000 --- a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java +++ /dev/null @@ -1,98 +0,0 @@ -package org.thoughtcrime.securesms.crypto; - -import android.content.Context; -import android.content.Intent; - -import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientFactory; -import org.thoughtcrime.securesms.service.KeyCachingService; -import org.thoughtcrime.securesms.service.PreKeyService; -import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage; -import org.whispersystems.libaxolotl.InvalidKeyException; -import org.whispersystems.libaxolotl.InvalidKeyIdException; -import org.whispersystems.libaxolotl.SessionBuilder; -import org.whispersystems.libaxolotl.StaleKeyExchangeException; -import org.whispersystems.libaxolotl.UntrustedIdentityException; -import org.whispersystems.libaxolotl.protocol.KeyExchangeMessage; -import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage; -import org.whispersystems.libaxolotl.state.SignedPreKeyStore; -import org.whispersystems.libaxolotl.state.IdentityKeyStore; -import org.whispersystems.libaxolotl.state.PreKeyBundle; -import org.whispersystems.libaxolotl.state.PreKeyStore; -import org.whispersystems.libaxolotl.state.SessionStore; -import org.whispersystems.textsecure.crypto.MasterSecret; -import org.whispersystems.textsecure.storage.RecipientDevice; -import org.whispersystems.textsecure.storage.TextSecurePreKeyStore; -import org.whispersystems.textsecure.storage.TextSecureSessionStore; -import org.whispersystems.textsecure.util.Base64; - -/** - * This class processes key exchange interactions. - * - * @author Moxie Marlinspike - */ - -public class KeyExchangeProcessor { - - public static final String SECURITY_UPDATE_EVENT = "org.thoughtcrime.securesms.KEY_EXCHANGE_UPDATE"; - - private Context context; - private RecipientDevice recipientDevice; - private MasterSecret masterSecret; - private SessionBuilder sessionBuilder; - - public KeyExchangeProcessor(Context context, MasterSecret masterSecret, RecipientDevice recipientDevice) - { - this.context = context; - this.recipientDevice = recipientDevice; - this.masterSecret = masterSecret; - - IdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(context, masterSecret); - PreKeyStore preKeyStore = new TextSecurePreKeyStore(context, masterSecret); - SignedPreKeyStore signedPreKeyStore = new TextSecurePreKeyStore(context, masterSecret); - SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret); - - this.sessionBuilder = new SessionBuilder(sessionStore, preKeyStore, signedPreKeyStore, - identityKeyStore, recipientDevice.getRecipientId(), - recipientDevice.getDeviceId()); - } - - public void processKeyExchangeMessage(PreKeyBundle bundle, long threadId) - throws InvalidKeyException, UntrustedIdentityException - { - sessionBuilder.process(bundle); - - if (threadId != -1) { - broadcastSecurityUpdateEvent(context, threadId); - } - } - - public OutgoingKeyExchangeMessage processKeyExchangeMessage(KeyExchangeMessage message, long threadId) - throws InvalidKeyException, UntrustedIdentityException, StaleKeyExchangeException - { - KeyExchangeMessage responseMessage = sessionBuilder.process(message); - Recipient recipient = RecipientFactory.getRecipientsForIds(context, - String.valueOf(recipientDevice.getRecipientId()), - false) - .getPrimaryRecipient(); - - DecryptingQueue.scheduleRogueMessages(context, masterSecret, recipient); - - broadcastSecurityUpdateEvent(context, threadId); - - if (responseMessage != null) { - String serializedResponse = Base64.encodeBytesWithoutPadding(responseMessage.serialize()); - return new OutgoingKeyExchangeMessage(recipient, serializedResponse); - } else { - return null; - } - } - - public static void broadcastSecurityUpdateEvent(Context context, long threadId) { - Intent intent = new Intent(SECURITY_UPDATE_EVENT); - intent.putExtra("thread_id", threadId); - intent.setPackage(context.getPackageName()); - context.sendBroadcast(intent, KeyCachingService.KEY_PERMISSION); - } - -} diff --git a/library/src/org/whispersystems/textsecure/crypto/MasterCipher.java b/src/org/thoughtcrime/securesms/crypto/MasterCipher.java similarity index 99% rename from library/src/org/whispersystems/textsecure/crypto/MasterCipher.java rename to src/org/thoughtcrime/securesms/crypto/MasterCipher.java index 80d682754a..7252b2ab6f 100644 --- a/library/src/org/whispersystems/textsecure/crypto/MasterCipher.java +++ b/src/org/thoughtcrime/securesms/crypto/MasterCipher.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.whispersystems.textsecure.crypto; +package org.thoughtcrime.securesms.crypto; import android.util.Log; diff --git a/library/src/org/whispersystems/textsecure/crypto/MasterSecret.java b/src/org/thoughtcrime/securesms/crypto/MasterSecret.java similarity index 98% rename from library/src/org/whispersystems/textsecure/crypto/MasterSecret.java rename to src/org/thoughtcrime/securesms/crypto/MasterSecret.java index 495454429b..e849aae617 100644 --- a/library/src/org/whispersystems/textsecure/crypto/MasterSecret.java +++ b/src/org/thoughtcrime/securesms/crypto/MasterSecret.java @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.whispersystems.textsecure.crypto; +package org.thoughtcrime.securesms.crypto; import android.os.Parcel; import android.os.Parcelable; diff --git a/src/org/thoughtcrime/securesms/crypto/MasterSecretUtil.java b/src/org/thoughtcrime/securesms/crypto/MasterSecretUtil.java index e034684cea..e35116f7e8 100644 --- a/src/org/thoughtcrime/securesms/crypto/MasterSecretUtil.java +++ b/src/org/thoughtcrime/securesms/crypto/MasterSecretUtil.java @@ -26,8 +26,6 @@ import org.whispersystems.libaxolotl.ecc.Curve; import org.whispersystems.libaxolotl.ecc.ECKeyPair; import org.whispersystems.libaxolotl.ecc.ECPrivateKey; import org.whispersystems.libaxolotl.ecc.ECPublicKey; -import org.whispersystems.textsecure.crypto.MasterCipher; -import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.util.Base64; import org.whispersystems.textsecure.util.Util; diff --git a/src/org/thoughtcrime/securesms/crypto/MmsCipher.java b/src/org/thoughtcrime/securesms/crypto/MmsCipher.java new file mode 100644 index 0000000000..e90e2351e7 --- /dev/null +++ b/src/org/thoughtcrime/securesms/crypto/MmsCipher.java @@ -0,0 +1,134 @@ +package org.thoughtcrime.securesms.crypto; + +import android.content.Context; +import android.util.Log; + +import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore; +import org.thoughtcrime.securesms.mms.TextTransport; +import org.thoughtcrime.securesms.protocol.WirePrefix; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientFactory; +import org.thoughtcrime.securesms.recipients.RecipientFormattingException; +import org.thoughtcrime.securesms.recipients.Recipients; +import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException; +import org.whispersystems.libaxolotl.DuplicateMessageException; +import org.whispersystems.libaxolotl.InvalidMessageException; +import org.whispersystems.libaxolotl.LegacyMessageException; +import org.whispersystems.libaxolotl.NoSessionException; +import org.whispersystems.libaxolotl.SessionCipher; +import org.whispersystems.libaxolotl.protocol.CiphertextMessage; +import org.whispersystems.libaxolotl.protocol.WhisperMessage; +import org.whispersystems.libaxolotl.state.AxolotlStore; +import org.whispersystems.libaxolotl.util.guava.Optional; +import org.whispersystems.textsecure.crypto.TextSecureCipher; +import org.whispersystems.textsecure.storage.RecipientDevice; +import org.whispersystems.textsecure.util.Util; + +import java.io.IOException; + +import ws.com.google.android.mms.ContentType; +import ws.com.google.android.mms.pdu.EncodedStringValue; +import ws.com.google.android.mms.pdu.MultimediaMessagePdu; +import ws.com.google.android.mms.pdu.PduBody; +import ws.com.google.android.mms.pdu.PduComposer; +import ws.com.google.android.mms.pdu.PduParser; +import ws.com.google.android.mms.pdu.PduPart; +import ws.com.google.android.mms.pdu.RetrieveConf; +import ws.com.google.android.mms.pdu.SendReq; + +public class MmsCipher { + + private static final String TAG = MmsCipher.class.getSimpleName(); + + private final TextTransport textTransport = new TextTransport(); + private final AxolotlStore axolotlStore; + + public MmsCipher(AxolotlStore axolotlStore) { + this.axolotlStore = axolotlStore; + } + + public MultimediaMessagePdu decrypt(Context context, MultimediaMessagePdu pdu) + throws InvalidMessageException, LegacyMessageException, DuplicateMessageException, + NoSessionException + { + try { + Recipients recipients = RecipientFactory.getRecipientsFromString(context, pdu.getFrom().getString(), false); + long recipientId = recipients.getPrimaryRecipient().getRecipientId(); + SessionCipher sessionCipher = new SessionCipher(axolotlStore, recipientId, 1); + Optional ciphertext = getEncryptedData(pdu); + + if (!ciphertext.isPresent()) { + throw new InvalidMessageException("No ciphertext present!"); + } + + byte[] decodedCiphertext = textTransport.getDecodedMessage(ciphertext.get()); + byte[] plaintext; + + try { + plaintext = sessionCipher.decrypt(new WhisperMessage(decodedCiphertext)); + } catch (InvalidMessageException e) { + // NOTE - For some reason, Sprint seems to append a single character to the + // end of message text segments. I don't know why, so here we just try + // truncating the message by one if the MAC fails. + if (ciphertext.get().length > 2) { + Log.w(TAG, "Attempting truncated decrypt..."); + byte[] truncated = Util.trim(ciphertext.get(), ciphertext.get().length - 1); + decodedCiphertext = textTransport.getDecodedMessage(truncated); + plaintext = sessionCipher.decrypt(new WhisperMessage(decodedCiphertext)); + } else { + throw e; + } + } + + MultimediaMessagePdu plaintextGenericPdu = (MultimediaMessagePdu) new PduParser(plaintext).parse(); + return new RetrieveConf(plaintextGenericPdu.getPduHeaders(), plaintextGenericPdu.getBody()); + } catch (RecipientFormattingException | IOException e) { + throw new InvalidMessageException(e); + } + } + + public SendReq encrypt(Context context, SendReq message) + throws NoSessionException, RecipientFormattingException + { + EncodedStringValue[] encodedRecipient = message.getTo(); + String recipientString = encodedRecipient[0].getString(); + Recipients recipients = RecipientFactory.getRecipientsFromString(context, recipientString, false); + long recipientId = recipients.getPrimaryRecipient().getRecipientId(); + byte[] pduBytes = new PduComposer(context, message).make(); + + if (!axolotlStore.containsSession(recipientId, RecipientDevice.DEFAULT_DEVICE_ID)) { + throw new NoSessionException("No session for: " + recipientId); + } + + SessionCipher cipher = new SessionCipher(axolotlStore, recipientId, RecipientDevice.DEFAULT_DEVICE_ID); + CiphertextMessage ciphertextMessage = cipher.encrypt(textTransport.getPaddedMessageBody(pduBytes)); + byte[] encryptedPduBytes = textTransport.getEncodedMessage(ciphertextMessage.serialize()); + + PduBody body = new PduBody(); + PduPart part = new PduPart(); + SendReq encryptedPdu = new SendReq(message.getPduHeaders(), body); + + part.setContentId((System.currentTimeMillis()+"").getBytes()); + part.setContentType(ContentType.TEXT_PLAIN.getBytes()); + part.setName((System.currentTimeMillis()+"").getBytes()); + part.setData(encryptedPduBytes); + body.addPart(part); + encryptedPdu.setSubject(new EncodedStringValue(WirePrefix.calculateEncryptedMmsSubject())); + encryptedPdu.setBody(body); + + return encryptedPdu; + } + + + private Optional getEncryptedData(MultimediaMessagePdu pdu) { + for (int i=0;i. */ -package org.whispersystems.textsecure.crypto; +package org.thoughtcrime.securesms.crypto; import android.content.Context; import android.util.Log; import com.google.thoughtcrimegson.Gson; +import org.thoughtcrime.securesms.crypto.storage.TextSecurePreKeyStore; import org.whispersystems.libaxolotl.IdentityKeyPair; import org.whispersystems.libaxolotl.InvalidKeyException; import org.whispersystems.libaxolotl.InvalidKeyIdException; @@ -33,7 +34,6 @@ import org.whispersystems.libaxolotl.state.SignedPreKeyStore; import org.whispersystems.libaxolotl.state.PreKeyRecord; import org.whispersystems.libaxolotl.state.PreKeyStore; import org.whispersystems.libaxolotl.util.Medium; -import org.whispersystems.textsecure.storage.TextSecurePreKeyStore; import org.whispersystems.textsecure.util.Util; import java.io.File; diff --git a/library/src/org/whispersystems/textsecure/crypto/PublicKey.java b/src/org/thoughtcrime/securesms/crypto/PublicKey.java similarity index 98% rename from library/src/org/whispersystems/textsecure/crypto/PublicKey.java rename to src/org/thoughtcrime/securesms/crypto/PublicKey.java index 2540877de6..4f563debea 100644 --- a/library/src/org/whispersystems/textsecure/crypto/PublicKey.java +++ b/src/org/thoughtcrime/securesms/crypto/PublicKey.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.whispersystems.textsecure.crypto; +package org.thoughtcrime.securesms.crypto; import android.util.Log; diff --git a/src/org/thoughtcrime/securesms/crypto/SecurityEvent.java b/src/org/thoughtcrime/securesms/crypto/SecurityEvent.java new file mode 100644 index 0000000000..e91746ca01 --- /dev/null +++ b/src/org/thoughtcrime/securesms/crypto/SecurityEvent.java @@ -0,0 +1,43 @@ +package org.thoughtcrime.securesms.crypto; + +import android.content.Context; +import android.content.Intent; + +import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore; +import org.thoughtcrime.securesms.crypto.storage.TextSecurePreKeyStore; +import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientFactory; +import org.thoughtcrime.securesms.service.KeyCachingService; +import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage; +import org.whispersystems.libaxolotl.InvalidKeyException; +import org.whispersystems.libaxolotl.SessionBuilder; +import org.whispersystems.libaxolotl.StaleKeyExchangeException; +import org.whispersystems.libaxolotl.UntrustedIdentityException; +import org.whispersystems.libaxolotl.protocol.KeyExchangeMessage; +import org.whispersystems.libaxolotl.state.SignedPreKeyStore; +import org.whispersystems.libaxolotl.state.IdentityKeyStore; +import org.whispersystems.libaxolotl.state.PreKeyBundle; +import org.whispersystems.libaxolotl.state.PreKeyStore; +import org.whispersystems.libaxolotl.state.SessionStore; +import org.whispersystems.textsecure.storage.RecipientDevice; +import org.whispersystems.textsecure.util.Base64; + +/** + * This class processes key exchange interactions. + * + * @author Moxie Marlinspike + */ + +public class SecurityEvent { + + public static final String SECURITY_UPDATE_EVENT = "org.thoughtcrime.securesms.KEY_EXCHANGE_UPDATE"; + + public static void broadcastSecurityUpdateEvent(Context context, long threadId) { + Intent intent = new Intent(SECURITY_UPDATE_EVENT); + intent.putExtra("thread_id", threadId); + intent.setPackage(context.getPackageName()); + context.sendBroadcast(intent, KeyCachingService.KEY_PERMISSION); + } + +} diff --git a/src/org/thoughtcrime/securesms/crypto/SmsCipher.java b/src/org/thoughtcrime/securesms/crypto/SmsCipher.java new file mode 100644 index 0000000000..3d1f1c450e --- /dev/null +++ b/src/org/thoughtcrime/securesms/crypto/SmsCipher.java @@ -0,0 +1,129 @@ +package org.thoughtcrime.securesms.crypto; + +import android.content.Context; + +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientFactory; +import org.thoughtcrime.securesms.recipients.RecipientFormattingException; +import org.thoughtcrime.securesms.recipients.Recipients; +import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage; +import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage; +import org.thoughtcrime.securesms.sms.IncomingPreKeyBundleMessage; +import org.thoughtcrime.securesms.sms.IncomingTextMessage; +import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage; +import org.thoughtcrime.securesms.sms.OutgoingPrekeyBundleMessage; +import org.thoughtcrime.securesms.sms.OutgoingTextMessage; +import org.thoughtcrime.securesms.sms.SmsTransportDetails; +import org.whispersystems.libaxolotl.DuplicateMessageException; +import org.whispersystems.libaxolotl.InvalidKeyException; +import org.whispersystems.libaxolotl.InvalidKeyIdException; +import org.whispersystems.libaxolotl.InvalidMessageException; +import org.whispersystems.libaxolotl.InvalidVersionException; +import org.whispersystems.libaxolotl.LegacyMessageException; +import org.whispersystems.libaxolotl.NoSessionException; +import org.whispersystems.libaxolotl.SessionBuilder; +import org.whispersystems.libaxolotl.SessionCipher; +import org.whispersystems.libaxolotl.StaleKeyExchangeException; +import org.whispersystems.libaxolotl.UntrustedIdentityException; +import org.whispersystems.libaxolotl.protocol.CiphertextMessage; +import org.whispersystems.libaxolotl.protocol.KeyExchangeMessage; +import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage; +import org.whispersystems.libaxolotl.protocol.WhisperMessage; +import org.whispersystems.libaxolotl.state.AxolotlStore; +import org.whispersystems.textsecure.storage.RecipientDevice; + +import java.io.IOException; + +public class SmsCipher { + + private final SmsTransportDetails transportDetails = new SmsTransportDetails(); + + private final AxolotlStore axolotlStore; + + public SmsCipher(AxolotlStore axolotlStore) { + this.axolotlStore = axolotlStore; + } + + public IncomingTextMessage decrypt(Context context, IncomingTextMessage message) + throws LegacyMessageException, InvalidMessageException, + DuplicateMessageException, NoSessionException + { + try { + Recipients recipients = RecipientFactory.getRecipientsFromString(context, message.getSender(), false); + long recipientId = recipients.getPrimaryRecipient().getRecipientId(); + byte[] decoded = transportDetails.getDecodedMessage(message.getMessageBody().getBytes()); + WhisperMessage whisperMessage = new WhisperMessage(decoded); + SessionCipher sessionCipher = new SessionCipher(axolotlStore, recipientId, 1); + byte[] padded = sessionCipher.decrypt(whisperMessage); + byte[] plaintext = transportDetails.getStrippedPaddingMessageBody(padded); + + if (message.isEndSession() && "TERMINATE".equals(new String(plaintext))) { + axolotlStore.deleteSession(recipientId, 1); + } + + return message.withMessageBody(new String(plaintext)); + } catch (RecipientFormattingException | IOException e) { + throw new InvalidMessageException(e); + } + } + + public IncomingEncryptedMessage decrypt(Context context, IncomingPreKeyBundleMessage message) + throws InvalidVersionException, InvalidMessageException, DuplicateMessageException, + UntrustedIdentityException, LegacyMessageException + { + try { + Recipients recipients = RecipientFactory.getRecipientsFromString(context, message.getSender(), false); + byte[] decoded = transportDetails.getDecodedMessage(message.getMessageBody().getBytes()); + PreKeyWhisperMessage preKeyMessage = new PreKeyWhisperMessage(decoded); + SessionCipher sessionCipher = new SessionCipher(axolotlStore, recipients.getPrimaryRecipient().getRecipientId(), 1); + byte[] padded = sessionCipher.decrypt(preKeyMessage); + byte[] plaintext = transportDetails.getStrippedPaddingMessageBody(padded); + + return new IncomingEncryptedMessage(message, new String(plaintext)); + } catch (RecipientFormattingException | IOException | InvalidKeyException | InvalidKeyIdException e) { + throw new InvalidMessageException(e); + } + } + + public OutgoingTextMessage encrypt(OutgoingTextMessage message) throws NoSessionException { + byte[] paddedBody = transportDetails.getPaddedMessageBody(message.getMessageBody().getBytes()); + long recipientId = message.getRecipients().getPrimaryRecipient().getRecipientId(); + + if (!axolotlStore.containsSession(recipientId, RecipientDevice.DEFAULT_DEVICE_ID)) { + throw new NoSessionException("No session for: " + recipientId); + } + + SessionCipher cipher = new SessionCipher(axolotlStore, recipientId, RecipientDevice.DEFAULT_DEVICE_ID); + CiphertextMessage ciphertextMessage = cipher.encrypt(paddedBody); + String encodedCiphertext = new String(transportDetails.getEncodedMessage(ciphertextMessage.serialize())); + + if (ciphertextMessage.getType() == CiphertextMessage.PREKEY_TYPE) { + return new OutgoingPrekeyBundleMessage(message, encodedCiphertext); + } else { + return message.withBody(encodedCiphertext); + } + } + + public OutgoingKeyExchangeMessage process(Context context, IncomingKeyExchangeMessage message) + throws UntrustedIdentityException, StaleKeyExchangeException, + InvalidVersionException, LegacyMessageException, InvalidMessageException + { + try { + Recipient recipient = RecipientFactory.getRecipientsFromString(context, message.getSender(), false).getPrimaryRecipient(); + KeyExchangeMessage exchangeMessage = new KeyExchangeMessage(transportDetails.getDecodedMessage(message.getMessageBody().getBytes())); + SessionBuilder sessionBuilder = new SessionBuilder(axolotlStore, recipient.getRecipientId(), 1); + + KeyExchangeMessage response = sessionBuilder.process(exchangeMessage); + + if (response != null) { + byte[] serializedResponse = transportDetails.getEncodedMessage(response.serialize()); + return new OutgoingKeyExchangeMessage(recipient, new String(serializedResponse)); + } else { + return null; + } + } catch (RecipientFormattingException | IOException | InvalidKeyException e) { + throw new InvalidMessageException(e); + } + } + +} diff --git a/src/org/thoughtcrime/securesms/crypto/storage/TextSecureAxolotlStore.java b/src/org/thoughtcrime/securesms/crypto/storage/TextSecureAxolotlStore.java new file mode 100644 index 0000000000..82322cc022 --- /dev/null +++ b/src/org/thoughtcrime/securesms/crypto/storage/TextSecureAxolotlStore.java @@ -0,0 +1,128 @@ +package org.thoughtcrime.securesms.crypto.storage; + +import android.content.Context; + +import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.whispersystems.libaxolotl.IdentityKey; +import org.whispersystems.libaxolotl.IdentityKeyPair; +import org.whispersystems.libaxolotl.InvalidKeyIdException; +import org.whispersystems.libaxolotl.state.AxolotlStore; +import org.whispersystems.libaxolotl.state.IdentityKeyStore; +import org.whispersystems.libaxolotl.state.PreKeyRecord; +import org.whispersystems.libaxolotl.state.PreKeyStore; +import org.whispersystems.libaxolotl.state.SessionRecord; +import org.whispersystems.libaxolotl.state.SessionStore; +import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; +import org.whispersystems.libaxolotl.state.SignedPreKeyStore; + +import java.util.List; + +public class TextSecureAxolotlStore implements AxolotlStore { + + private final PreKeyStore preKeyStore; + private final SignedPreKeyStore signedPreKeyStore; + private final IdentityKeyStore identityKeyStore; + private final SessionStore sessionStore; + + public TextSecureAxolotlStore(Context context, MasterSecret masterSecret) { + this.preKeyStore = new TextSecurePreKeyStore(context, masterSecret); + this.signedPreKeyStore = new TextSecurePreKeyStore(context, masterSecret); + this.identityKeyStore = new TextSecureIdentityKeyStore(context, masterSecret); + this.sessionStore = new TextSecureSessionStore(context, masterSecret); + } + + @Override + public IdentityKeyPair getIdentityKeyPair() { + return identityKeyStore.getIdentityKeyPair(); + } + + @Override + public int getLocalRegistrationId() { + return identityKeyStore.getLocalRegistrationId(); + } + + @Override + public void saveIdentity(long recipientId, IdentityKey identityKey) { + identityKeyStore.saveIdentity(recipientId, identityKey); + } + + @Override + public boolean isTrustedIdentity(long recipientId, IdentityKey identityKey) { + return identityKeyStore.isTrustedIdentity(recipientId, identityKey); + } + + @Override + public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException { + return preKeyStore.loadPreKey(preKeyId); + } + + @Override + public void storePreKey(int preKeyId, PreKeyRecord record) { + preKeyStore.storePreKey(preKeyId, record); + } + + @Override + public boolean containsPreKey(int preKeyId) { + return preKeyStore.containsPreKey(preKeyId); + } + + @Override + public void removePreKey(int preKeyId) { + preKeyStore.removePreKey(preKeyId); + } + + @Override + public SessionRecord loadSession(long recipientId, int deviceId) { + return sessionStore.loadSession(recipientId, deviceId); + } + + @Override + public List getSubDeviceSessions(long recipientId) { + return sessionStore.getSubDeviceSessions(recipientId); + } + + @Override + public void storeSession(long recipientId, int deviceId, SessionRecord record) { + sessionStore.storeSession(recipientId, deviceId, record); + } + + @Override + public boolean containsSession(long recipientId, int deviceId) { + return sessionStore.containsSession(recipientId, deviceId); + } + + @Override + public void deleteSession(long recipientId, int deviceId) { + sessionStore.deleteSession(recipientId, deviceId); + } + + @Override + public void deleteAllSessions(long recipientId) { + sessionStore.deleteAllSessions(recipientId); + } + + @Override + public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException { + return signedPreKeyStore.loadSignedPreKey(signedPreKeyId); + } + + @Override + public List loadSignedPreKeys() { + return signedPreKeyStore.loadSignedPreKeys(); + } + + @Override + public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) { + signedPreKeyStore.storeSignedPreKey(signedPreKeyId, record); + } + + @Override + public boolean containsSignedPreKey(int signedPreKeyId) { + return signedPreKeyStore.containsSignedPreKey(signedPreKeyId); + } + + @Override + public void removeSignedPreKey(int signedPreKeyId) { + signedPreKeyStore.removeSignedPreKey(signedPreKeyId); + } +} diff --git a/src/org/thoughtcrime/securesms/crypto/TextSecureIdentityKeyStore.java b/src/org/thoughtcrime/securesms/crypto/storage/TextSecureIdentityKeyStore.java similarity index 88% rename from src/org/thoughtcrime/securesms/crypto/TextSecureIdentityKeyStore.java rename to src/org/thoughtcrime/securesms/crypto/storage/TextSecureIdentityKeyStore.java index f2a5f18855..d033c35c90 100644 --- a/src/org/thoughtcrime/securesms/crypto/TextSecureIdentityKeyStore.java +++ b/src/org/thoughtcrime/securesms/crypto/storage/TextSecureIdentityKeyStore.java @@ -1,13 +1,14 @@ -package org.thoughtcrime.securesms.crypto; +package org.thoughtcrime.securesms.crypto.storage; import android.content.Context; +import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.libaxolotl.IdentityKey; import org.whispersystems.libaxolotl.IdentityKeyPair; import org.whispersystems.libaxolotl.state.IdentityKeyStore; -import org.whispersystems.textsecure.crypto.MasterSecret; public class TextSecureIdentityKeyStore implements IdentityKeyStore { diff --git a/library/src/org/whispersystems/textsecure/storage/TextSecurePreKeyStore.java b/src/org/thoughtcrime/securesms/crypto/storage/TextSecurePreKeyStore.java similarity index 96% rename from library/src/org/whispersystems/textsecure/storage/TextSecurePreKeyStore.java rename to src/org/thoughtcrime/securesms/crypto/storage/TextSecurePreKeyStore.java index a63401dc3d..94b1e403de 100644 --- a/library/src/org/whispersystems/textsecure/storage/TextSecurePreKeyStore.java +++ b/src/org/thoughtcrime/securesms/crypto/storage/TextSecurePreKeyStore.java @@ -1,16 +1,16 @@ -package org.whispersystems.textsecure.storage; +package org.thoughtcrime.securesms.crypto.storage; import android.content.Context; import android.util.Log; +import org.thoughtcrime.securesms.crypto.MasterCipher; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.whispersystems.libaxolotl.InvalidKeyIdException; import org.whispersystems.libaxolotl.InvalidMessageException; import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; import org.whispersystems.libaxolotl.state.SignedPreKeyStore; import org.whispersystems.libaxolotl.state.PreKeyRecord; import org.whispersystems.libaxolotl.state.PreKeyStore; -import org.whispersystems.textsecure.crypto.MasterCipher; -import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.util.Conversions; import java.io.File; @@ -132,7 +132,7 @@ public class TextSecurePreKeyStore implements PreKeyStore, SignedPreKeyStore { private byte[] loadSerializedRecord(File recordFile) throws IOException, InvalidMessageException { - MasterCipher masterCipher = new MasterCipher(masterSecret); + MasterCipher masterCipher = new MasterCipher(masterSecret); FileInputStream fin = new FileInputStream(recordFile); int recordVersion = readInteger(fin); diff --git a/library/src/org/whispersystems/textsecure/storage/TextSecureSessionStore.java b/src/org/thoughtcrime/securesms/crypto/storage/TextSecureSessionStore.java similarity index 95% rename from library/src/org/whispersystems/textsecure/storage/TextSecureSessionStore.java rename to src/org/thoughtcrime/securesms/crypto/storage/TextSecureSessionStore.java index b06a472384..15db4c2165 100644 --- a/library/src/org/whispersystems/textsecure/storage/TextSecureSessionStore.java +++ b/src/org/thoughtcrime/securesms/crypto/storage/TextSecureSessionStore.java @@ -1,14 +1,15 @@ -package org.whispersystems.textsecure.storage; +package org.thoughtcrime.securesms.crypto.storage; import android.content.Context; import android.util.Log; +import org.thoughtcrime.securesms.crypto.MasterCipher; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.whispersystems.libaxolotl.InvalidMessageException; import org.whispersystems.libaxolotl.state.SessionRecord; import org.whispersystems.libaxolotl.state.SessionState; import org.whispersystems.libaxolotl.state.SessionStore; -import org.whispersystems.textsecure.crypto.MasterCipher; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.whispersystems.textsecure.storage.RecipientDevice; import org.whispersystems.textsecure.util.Conversions; import java.io.File; @@ -44,7 +45,7 @@ public class TextSecureSessionStore implements SessionStore { public SessionRecord loadSession(long recipientId, int deviceId) { synchronized (FILE_LOCK) { try { - MasterCipher cipher = new MasterCipher(masterSecret); + MasterCipher cipher = new MasterCipher(masterSecret); FileInputStream in = new FileInputStream(getSessionFile(recipientId, deviceId)); int versionMarker = readInteger(in); diff --git a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java index dbeb13d68d..ddf2415265 100644 --- a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java +++ b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java @@ -26,13 +26,12 @@ import android.util.Log; import org.thoughtcrime.securesms.DatabaseUpgradeActivity; import org.thoughtcrime.securesms.crypto.DecryptingPartInputStream; -import org.thoughtcrime.securesms.crypto.DecryptingQueue; +import org.thoughtcrime.securesms.crypto.MasterCipher; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecretUtil; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.whispersystems.libaxolotl.IdentityKey; import org.whispersystems.libaxolotl.InvalidMessageException; -import org.whispersystems.textsecure.crypto.MasterCipher; -import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.util.Base64; import org.whispersystems.textsecure.util.Util; @@ -467,7 +466,7 @@ public class DatabaseFactory { db.setTransactionSuccessful(); db.endTransaction(); - DecryptingQueue.schedulePendingDecrypts(context, masterSecret); +// DecryptingQueue.schedulePendingDecrypts(context, masterSecret); MessageNotifier.updateNotification(context, masterSecret); } diff --git a/src/org/thoughtcrime/securesms/database/DraftDatabase.java b/src/org/thoughtcrime/securesms/database/DraftDatabase.java index 5c2fd1c8c5..9e2d6d6a2d 100644 --- a/src/org/thoughtcrime/securesms/database/DraftDatabase.java +++ b/src/org/thoughtcrime/securesms/database/DraftDatabase.java @@ -8,7 +8,7 @@ import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; import org.whispersystems.libaxolotl.InvalidMessageException; -import org.whispersystems.textsecure.crypto.MasterCipher; +import org.thoughtcrime.securesms.crypto.MasterCipher; import java.util.LinkedList; import java.util.List; diff --git a/src/org/thoughtcrime/securesms/database/EncryptingPartDatabase.java b/src/org/thoughtcrime/securesms/database/EncryptingPartDatabase.java index d668602b4a..948d66dde6 100644 --- a/src/org/thoughtcrime/securesms/database/EncryptingPartDatabase.java +++ b/src/org/thoughtcrime/securesms/database/EncryptingPartDatabase.java @@ -22,7 +22,7 @@ import android.util.Log; import org.thoughtcrime.securesms.crypto.DecryptingPartInputStream; import org.thoughtcrime.securesms.crypto.EncryptingPartOutputStream; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; import ws.com.google.android.mms.pdu.PduPart; diff --git a/src/org/thoughtcrime/securesms/database/EncryptingSmsDatabase.java b/src/org/thoughtcrime/securesms/database/EncryptingSmsDatabase.java index 53971238bf..c4285829ab 100644 --- a/src/org/thoughtcrime/securesms/database/EncryptingSmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/EncryptingSmsDatabase.java @@ -29,8 +29,8 @@ import org.thoughtcrime.securesms.sms.IncomingTextMessage; import org.thoughtcrime.securesms.sms.OutgoingTextMessage; import org.thoughtcrime.securesms.util.LRUCache; import org.whispersystems.libaxolotl.InvalidMessageException; -import org.whispersystems.textsecure.crypto.MasterCipher; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterCipher; +import org.thoughtcrime.securesms.crypto.MasterSecret; import java.lang.ref.SoftReference; import java.util.Collections; @@ -73,7 +73,9 @@ public class EncryptingSmsDatabase extends SmsDatabase { { long type = Types.BASE_INBOX_TYPE; - if (!message.isSecureMessage() && !message.isEndSession()) { + if (masterSecret == null && message.isSecureMessage()) { + type |= Types.ENCRYPTION_REMOTE_BIT; + } else { type |= Types.ENCRYPTION_SYMMETRIC_BIT; message = message.withMessageBody(getEncryptedBody(masterSecret, message.getMessageBody())); } @@ -97,8 +99,9 @@ public class EncryptingSmsDatabase extends SmsDatabase { } public void updateBundleMessageBody(MasterSecret masterSecret, long messageId, String body) { - updateMessageBodyAndType(messageId, body, Types.TOTAL_MASK, - Types.BASE_INBOX_TYPE | Types.ENCRYPTION_REMOTE_BIT | Types.SECURE_MESSAGE_BIT); + String encryptedBody = getEncryptedBody(masterSecret, body); + updateMessageBodyAndType(messageId, encryptedBody, Types.TOTAL_MASK, + Types.BASE_INBOX_TYPE | Types.SECURE_MESSAGE_BIT); } public void updateMessageBody(MasterSecret masterSecret, long messageId, String body) { diff --git a/src/org/thoughtcrime/securesms/database/GroupDatabase.java b/src/org/thoughtcrime/securesms/database/GroupDatabase.java index 60ba09e7b6..5853a3c60b 100644 --- a/src/org/thoughtcrime/securesms/database/GroupDatabase.java +++ b/src/org/thoughtcrime/securesms/database/GroupDatabase.java @@ -18,6 +18,7 @@ import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.util.BitmapUtil; import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.whispersystems.textsecure.api.messages.TextSecureAttachmentPointer; import org.whispersystems.textsecure.util.Util; import java.io.IOException; @@ -103,7 +104,7 @@ public class GroupDatabase extends Database { } public void create(byte[] groupId, String title, List members, - AttachmentPointer avatar, String relay) + TextSecureAttachmentPointer avatar, String relay) { ContentValues contentValues = new ContentValues(); contentValues.put(GROUP_ID, GroupUtil.getEncodedId(groupId)); @@ -112,7 +113,7 @@ public class GroupDatabase extends Database { if (avatar != null) { contentValues.put(AVATAR_ID, avatar.getId()); - contentValues.put(AVATAR_KEY, avatar.getKey().toByteArray()); + contentValues.put(AVATAR_KEY, avatar.getKey()); contentValues.put(AVATAR_CONTENT_TYPE, avatar.getContentType()); } @@ -123,14 +124,14 @@ public class GroupDatabase extends Database { databaseHelper.getWritableDatabase().insert(TABLE_NAME, null, contentValues); } - public void update(byte[] groupId, String title, AttachmentPointer avatar) { + public void update(byte[] groupId, String title, TextSecureAttachmentPointer avatar) { ContentValues contentValues = new ContentValues(); if (title != null) contentValues.put(TITLE, title); if (avatar != null) { contentValues.put(AVATAR_ID, avatar.getId()); contentValues.put(AVATAR_CONTENT_TYPE, avatar.getContentType()); - contentValues.put(AVATAR_KEY, avatar.getKey().toByteArray()); + contentValues.put(AVATAR_KEY, avatar.getKey()); } databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, diff --git a/src/org/thoughtcrime/securesms/database/IdentityDatabase.java b/src/org/thoughtcrime/securesms/database/IdentityDatabase.java index 1d47226cdd..6b16559765 100644 --- a/src/org/thoughtcrime/securesms/database/IdentityDatabase.java +++ b/src/org/thoughtcrime/securesms/database/IdentityDatabase.java @@ -28,8 +28,8 @@ import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.Recipients; import org.whispersystems.libaxolotl.IdentityKey; import org.whispersystems.libaxolotl.InvalidKeyException; -import org.whispersystems.textsecure.crypto.MasterCipher; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterCipher; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.whispersystems.textsecure.util.Base64; import java.io.IOException; diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java index 7fe6d0a241..f8271df5e8 100644 --- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -34,8 +34,8 @@ import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.libaxolotl.InvalidMessageException; -import org.whispersystems.textsecure.crypto.MasterCipher; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterCipher; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.model.DisplayRecord; import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord; @@ -49,6 +49,7 @@ import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.RecipientFormattingException; import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.util.LRUCache; +import org.whispersystems.libaxolotl.util.guava.Optional; import org.whispersystems.textsecure.util.InvalidNumberException; import org.whispersystems.textsecure.util.ListenableFutureTask; import org.thoughtcrime.securesms.util.Trimmer; @@ -429,6 +430,32 @@ public class MmsDatabase extends Database implements MmsSmsColumns { database.update(TABLE_NAME, contentValues, null, null); } + public Optional getNotification(long messageId) { + SQLiteDatabase db = databaseHelper.getReadableDatabase(); + MmsAddressDatabase addressDatabase = DatabaseFactory.getMmsAddressDatabase(context); + + Cursor cursor = null; + + try { + cursor = db.query(TABLE_NAME, MMS_PROJECTION, ID_WHERE, new String[] {String.valueOf(messageId)}, null, null, null); + + if (cursor != null && cursor.moveToNext()) { + PduHeaders headers = getHeadersFromCursor(cursor); + addressDatabase.getAddressesForId(messageId, headers); + + return Optional.of(new NotificationInd(headers)); + } else { + return Optional.absent(); + } + } catch (InvalidHeaderValueException e) { + Log.w("MmsDatabase", e); + return Optional.absent(); + } finally { + if (cursor != null) + cursor.close(); + } + } + public SendReq[] getOutgoingMessages(MasterSecret masterSecret, long messageId) throws MmsException { diff --git a/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java b/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java index a451b2441b..6df6772add 100644 --- a/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java +++ b/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java @@ -155,6 +155,10 @@ public interface MmsSmsColumns { return (type & ENCRYPTION_SYMMETRIC_BIT) != 0; } + public static boolean isAsymmetricEncryption(long type) { + return (type & ENCRYPTION_ASYMMETRIC_BIT) != 0; + } + public static boolean isFailedDecryptType(long type) { return (type & ENCRYPTION_REMOTE_FAILED_BIT) != 0; } diff --git a/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java index a05d8772ac..42255276ed 100644 --- a/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -24,7 +24,7 @@ import android.database.sqlite.SQLiteQueryBuilder; import android.util.Log; import org.thoughtcrime.securesms.database.model.MessageRecord; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; import java.util.HashSet; import java.util.Set; diff --git a/src/org/thoughtcrime/securesms/database/PlaintextBackupExporter.java b/src/org/thoughtcrime/securesms/database/PlaintextBackupExporter.java index 4721a86922..453dab4763 100644 --- a/src/org/thoughtcrime/securesms/database/PlaintextBackupExporter.java +++ b/src/org/thoughtcrime/securesms/database/PlaintextBackupExporter.java @@ -4,7 +4,7 @@ package org.thoughtcrime.securesms.database; import android.content.Context; import android.os.Environment; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import java.io.File; diff --git a/src/org/thoughtcrime/securesms/database/PlaintextBackupImporter.java b/src/org/thoughtcrime/securesms/database/PlaintextBackupImporter.java index 0ffde41a06..75346e846d 100644 --- a/src/org/thoughtcrime/securesms/database/PlaintextBackupImporter.java +++ b/src/org/thoughtcrime/securesms/database/PlaintextBackupImporter.java @@ -6,8 +6,8 @@ import android.database.sqlite.SQLiteStatement; import android.os.Environment; import android.util.Log; -import org.whispersystems.textsecure.crypto.MasterCipher; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterCipher; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.RecipientFormattingException; import org.thoughtcrime.securesms.recipients.Recipients; diff --git a/src/org/thoughtcrime/securesms/database/PushDatabase.java b/src/org/thoughtcrime/securesms/database/PushDatabase.java index 2db297828d..6056659141 100644 --- a/src/org/thoughtcrime/securesms/database/PushDatabase.java +++ b/src/org/thoughtcrime/securesms/database/PushDatabase.java @@ -4,6 +4,7 @@ import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; import org.whispersystems.textsecure.push.IncomingPushMessage; import org.whispersystems.textsecure.util.Base64; @@ -40,6 +41,32 @@ public class PushDatabase extends Database { return databaseHelper.getWritableDatabase().insert(TABLE_NAME, null, values); } + public IncomingPushMessage get(long id) throws NoSuchMessageException { + Cursor cursor = null; + + try { + cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, ID_WHERE, + new String[] {String.valueOf(id)}, + null, null, null); + + if (cursor != null && cursor.moveToNext()) { + return new IncomingPushMessage(cursor.getInt(cursor.getColumnIndexOrThrow(TYPE)), + cursor.getString(cursor.getColumnIndexOrThrow(SOURCE)), + cursor.getInt(cursor.getColumnIndexOrThrow(DEVICE_ID)), + Base64.decode(cursor.getString(cursor.getColumnIndexOrThrow(BODY))), + cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP))); + } + } catch (IOException e) { + Log.w("PushDatabase", e); + throw new NoSuchMessageException(e); + } finally { + if (cursor != null) + cursor.close(); + } + + throw new NoSuchMessageException("Not found"); + } + public Cursor getPending() { return databaseHelper.getReadableDatabase().query(TABLE_NAME, null, null, null, null, null, null); } @@ -85,4 +112,9 @@ public class PushDatabase extends Database { } } + public static class NoSuchMessageException extends Exception { + public NoSuchMessageException(String s) {super(s);} + public NoSuchMessageException(Exception e) {super(e);} + } + } diff --git a/src/org/thoughtcrime/securesms/database/SmsDatabase.java b/src/org/thoughtcrime/securesms/database/SmsDatabase.java index 3c98afc82e..5b0010cf39 100644 --- a/src/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -350,7 +350,7 @@ public class SmsDatabase extends Database implements MmsSmsColumns { else if (((IncomingKeyExchangeMessage)message).isPreKeyBundle()) type |= Types.KEY_EXCHANGE_BUNDLE_BIT; } else if (message.isSecureMessage()) { type |= Types.SECURE_MESSAGE_BIT; - type |= Types.ENCRYPTION_REMOTE_BIT; +// type |= Types.ENCRYPTION_REMOTE_BIT; } else if (message.isGroup()) { type |= Types.SECURE_MESSAGE_BIT; if (((IncomingGroupMessage)message).isUpdate()) type |= Types.GROUP_UPDATE_BIT; @@ -358,7 +358,7 @@ public class SmsDatabase extends Database implements MmsSmsColumns { } else if (message.isEndSession()) { type |= Types.SECURE_MESSAGE_BIT; type |= Types.END_SESSION_BIT; - type |= Types.ENCRYPTION_REMOTE_BIT; +// type |= Types.ENCRYPTION_REMOTE_BIT; } if (message.isPush()) type |= Types.PUSH_MESSAGE_BIT; diff --git a/src/org/thoughtcrime/securesms/database/SmsMigrator.java b/src/org/thoughtcrime/securesms/database/SmsMigrator.java index 3525884c5d..0e375337ea 100644 --- a/src/org/thoughtcrime/securesms/database/SmsMigrator.java +++ b/src/org/thoughtcrime/securesms/database/SmsMigrator.java @@ -23,8 +23,8 @@ import android.database.sqlite.SQLiteStatement; import android.net.Uri; import android.util.Log; -import org.whispersystems.textsecure.crypto.MasterCipher; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterCipher; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.RecipientFormattingException; import org.thoughtcrime.securesms.recipients.Recipients; diff --git a/src/org/thoughtcrime/securesms/database/ThreadDatabase.java b/src/org/thoughtcrime/securesms/database/ThreadDatabase.java index ff48730dce..7b2a90c112 100644 --- a/src/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/src/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -30,7 +30,7 @@ import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.Recipients; import org.whispersystems.libaxolotl.InvalidMessageException; -import org.whispersystems.textsecure.crypto.MasterCipher; +import org.thoughtcrime.securesms.crypto.MasterCipher; import org.whispersystems.textsecure.util.Util; import java.util.Arrays; diff --git a/src/org/thoughtcrime/securesms/database/model/MessageRecord.java b/src/org/thoughtcrime/securesms/database/model/MessageRecord.java index 8ad9eb782d..848860fb64 100644 --- a/src/org/thoughtcrime/securesms/database/model/MessageRecord.java +++ b/src/org/thoughtcrime/securesms/database/model/MessageRecord.java @@ -88,6 +88,10 @@ public abstract class MessageRecord extends DisplayRecord { return MmsSmsColumns.Types.isLegacyType(type); } + public boolean isAsymmetricEncryption() { + return MmsSmsColumns.Types.isAsymmetricEncryption(type); + } + @Override public SpannableString getDisplayBody() { if (isGroupUpdate() && isOutgoing()) { diff --git a/src/org/thoughtcrime/securesms/gcm/GcmBroadcastReceiver.java b/src/org/thoughtcrime/securesms/gcm/GcmBroadcastReceiver.java index f9bb4d3615..8f5ba97d7b 100644 --- a/src/org/thoughtcrime/securesms/gcm/GcmBroadcastReceiver.java +++ b/src/org/thoughtcrime/securesms/gcm/GcmBroadcastReceiver.java @@ -8,20 +8,10 @@ import android.util.Log; import com.google.android.gms.gcm.GoogleCloudMessaging; import org.thoughtcrime.securesms.ApplicationContext; -import org.thoughtcrime.securesms.jobs.DeliveryReceiptJob; -import org.thoughtcrime.securesms.service.SendReceiveService; +import org.thoughtcrime.securesms.jobs.PushReceiveJob; import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.whispersystems.jobqueue.JobManager; -import org.whispersystems.libaxolotl.InvalidVersionException; -import org.whispersystems.textsecure.directory.Directory; -import org.whispersystems.textsecure.directory.NotInDirectoryException; -import org.whispersystems.textsecure.push.ContactTokenDetails; -import org.whispersystems.textsecure.push.IncomingEncryptedPushMessage; -import org.whispersystems.textsecure.push.IncomingPushMessage; import org.whispersystems.textsecure.util.Util; -import java.io.IOException; - public class GcmBroadcastReceiver extends BroadcastReceiver { private static final String TAG = GcmBroadcastReceiver.class.getSimpleName(); @@ -48,44 +38,8 @@ public class GcmBroadcastReceiver extends BroadcastReceiver { } private void handleReceivedMessage(Context context, String data) { - try { - String sessionKey = TextSecurePreferences.getSignalingKey(context); - IncomingEncryptedPushMessage encrypted = new IncomingEncryptedPushMessage(data, sessionKey); - IncomingPushMessage message = encrypted.getIncomingPushMessage(); - - if (!isActiveNumber(context, message.getSource())) { - Directory directory = Directory.getInstance(context); - ContactTokenDetails contactTokenDetails = new ContactTokenDetails(); - contactTokenDetails.setNumber(message.getSource()); - - directory.setNumber(contactTokenDetails, true); - } - - Intent receiveService = new Intent(context, SendReceiveService.class); - receiveService.setAction(SendReceiveService.RECEIVE_PUSH_ACTION); - receiveService.putExtra("message", message); - context.startService(receiveService); - - if (!message.isReceipt()) { - JobManager jobManager = ApplicationContext.getInstance(context).getJobManager(); - jobManager.add(new DeliveryReceiptJob(context, message.getSource(), - message.getTimestampMillis(), - message.getRelay())); - } - } catch (IOException | InvalidVersionException e) { - Log.w(TAG, e); - } - } - - private boolean isActiveNumber(Context context, String e164number) { - boolean isActiveNumber; - - try { - isActiveNumber = Directory.getInstance(context).isActiveNumber(e164number); - } catch (NotInDirectoryException e) { - isActiveNumber = false; - } - - return isActiveNumber; + ApplicationContext.getInstance(context) + .getJobManager() + .add(new PushReceiveJob(context, data)); } } \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java b/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java new file mode 100644 index 0000000000..33d9e6207d --- /dev/null +++ b/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java @@ -0,0 +1,200 @@ +package org.thoughtcrime.securesms.groups; + + +import android.content.Context; +import android.util.Log; +import android.util.Pair; + +import com.google.protobuf.ByteString; + +import org.thoughtcrime.securesms.ApplicationContext; +import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; +import org.thoughtcrime.securesms.database.GroupDatabase; +import org.thoughtcrime.securesms.jobs.AvatarDownloadJob; +import org.thoughtcrime.securesms.notifications.MessageNotifier; +import org.thoughtcrime.securesms.sms.IncomingGroupMessage; +import org.thoughtcrime.securesms.sms.IncomingTextMessage; +import org.whispersystems.libaxolotl.util.guava.Optional; +import org.whispersystems.textsecure.api.messages.TextSecureAttachment; +import org.whispersystems.textsecure.api.messages.TextSecureGroup; +import org.whispersystems.textsecure.api.messages.TextSecureMessage; +import org.whispersystems.textsecure.push.IncomingPushMessage; +import org.whispersystems.textsecure.util.Base64; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord; +import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer; +import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext; + +public class GroupMessageProcessor { + + private static final String TAG = GroupMessageProcessor.class.getSimpleName(); + + public static void process(Context context, + MasterSecret masterSecret, + IncomingPushMessage push, + TextSecureMessage message) + { + if (!message.getGroupInfo().isPresent() || message.getGroupInfo().get().getGroupId() == null) { + Log.w(TAG, "Received group message with no id! Ignoring..."); + 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(); + GroupRecord record = database.getGroup(id); + + if (record != null && group.getType() == TextSecureGroup.Type.UPDATE) { + handleGroupUpdate(context, masterSecret, push, group, record); + } else if (record == null && group.getType() == TextSecureGroup.Type.UPDATE) { + handleGroupCreate(context, masterSecret, push, group); + } else if (record != null && group.getType() == TextSecureGroup.Type.QUIT) { + handleGroupLeave(context, masterSecret, push, group, record); + } else { + Log.w(TAG, "Received unknown type, ignoring..."); + } + } + + private static void handleGroupCreate(Context context, + MasterSecret masterSecret, + IncomingPushMessage message, + TextSecureGroup group) + { + GroupDatabase database = DatabaseFactory.getGroupDatabase(context); + byte[] id = group.getGroupId(); + GroupContext.Builder builder = createGroupContext(group); + builder.setType(GroupContext.Type.UPDATE); + + TextSecureAttachment avatar = group.getAvatar().orNull(); + + database.create(id, group.getName().orNull(), group.getMembers().orNull(), + avatar != null && avatar.isPointer() ? avatar.asPointer() : null, + message.getRelay()); + + storeMessage(context, masterSecret, message, group, builder.build()); + } + + private static void handleGroupUpdate(Context context, + MasterSecret masterSecret, + IncomingPushMessage push, + TextSecureGroup group, + GroupRecord groupRecord) + { + + GroupDatabase database = DatabaseFactory.getGroupDatabase(context); + byte[] id = group.getGroupId(); + + Set recordMembers = new HashSet<>(groupRecord.getMembers()); + Set messageMembers = new HashSet<>(group.getMembers().get()); + + Set addedMembers = new HashSet<>(messageMembers); + addedMembers.removeAll(recordMembers); + + Set missingMembers = new HashSet<>(recordMembers); + missingMembers.removeAll(messageMembers); + + GroupContext.Builder builder = createGroupContext(group); + builder.setType(GroupContext.Type.UPDATE); + + if (addedMembers.size() > 0) { + Set unionMembers = new HashSet<>(recordMembers); + unionMembers.addAll(messageMembers); + database.updateMembers(id, new LinkedList<>(unionMembers)); + + builder.clearMembers().addAllMembers(addedMembers); + } else { + builder.clearMembers(); + } + + if (missingMembers.size() > 0) { + // TODO We should tell added and missing about each-other. + } + + if (group.getName().isPresent() || group.getAvatar().isPresent()) { + TextSecureAttachment avatar = group.getAvatar().orNull(); + database.update(id, group.getName().orNull(), avatar != null ? avatar.asPointer() : null); + } + + if (group.getName().isPresent() && group.getName().get().equals(groupRecord.getTitle())) { + builder.clearName(); + } + + if (!groupRecord.isActive()) database.setActive(id, true); + + storeMessage(context, masterSecret, push, group, builder.build()); + } + + private static void handleGroupLeave(Context context, + MasterSecret masterSecret, + IncomingPushMessage message, + TextSecureGroup group, + GroupRecord record) + { + GroupDatabase database = DatabaseFactory.getGroupDatabase(context); + byte[] id = group.getGroupId(); + List members = record.getMembers(); + + GroupContext.Builder builder = createGroupContext(group); + builder.setType(GroupContext.Type.QUIT); + + if (members.contains(message.getSource())) { + database.remove(id, message.getSource()); + + storeMessage(context, masterSecret, message, group, builder.build()); + } + } + + + private static void storeMessage(Context context, MasterSecret masterSecret, + IncomingPushMessage push, TextSecureGroup group, + GroupContext storage) + { + if (group.getAvatar().isPresent()) { + ApplicationContext.getInstance(context).getJobManager() + .add(new AvatarDownloadJob(context, group.getGroupId())); + } + + EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context); + String body = Base64.encodeBytes(storage.toByteArray()); + IncomingTextMessage incoming = new IncomingTextMessage(push.getSource(), push.getSourceDevice(), push.getTimestampMillis(), body, Optional.of(group)); + IncomingGroupMessage groupMessage = new IncomingGroupMessage(incoming, storage, body); + + Pair messageAndThreadId = smsDatabase.insertMessageInbox(masterSecret, groupMessage); + MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); + } + + private static GroupContext.Builder createGroupContext(TextSecureGroup group) { + GroupContext.Builder builder = GroupContext.newBuilder(); + builder.setId(ByteString.copyFrom(group.getGroupId())); + + if (group.getAvatar().isPresent() && group.getAvatar().get().isPointer()) { + builder.setAvatar(AttachmentPointer.newBuilder() + .setId(group.getAvatar().get().asPointer().getId()) + .setKey(ByteString.copyFrom(group.getAvatar().get().asPointer().getKey())) + .setContentType(group.getAvatar().get().getContentType())); + } + + if (group.getName().isPresent()) { + builder.setName(group.getName().get()); + } + + if (group.getMembers().isPresent()) { + builder.addAllMembers(group.getMembers().get()); + } + + return builder; + } + +} diff --git a/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java b/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java new file mode 100644 index 0000000000..ab138a62e7 --- /dev/null +++ b/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java @@ -0,0 +1,154 @@ +package org.thoughtcrime.securesms.jobs; + +import android.content.Context; +import android.util.Log; +import android.util.Pair; + +import org.thoughtcrime.securesms.crypto.MasterCipher; +import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.EncryptingPartDatabase; +import org.thoughtcrime.securesms.database.PartDatabase; +import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; +import org.thoughtcrime.securesms.push.TextSecureMessageReceiverFactory; +import org.thoughtcrime.securesms.util.Util; +import org.whispersystems.jobqueue.JobParameters; +import org.whispersystems.jobqueue.requirements.NetworkRequirement; +import org.whispersystems.libaxolotl.InvalidMessageException; +import org.whispersystems.textsecure.api.TextSecureMessageReceiver; +import org.whispersystems.textsecure.api.messages.TextSecureAttachmentPointer; +import org.whispersystems.textsecure.push.exceptions.NonSuccessfulResponseCodeException; +import org.whispersystems.textsecure.push.exceptions.PushNetworkException; +import org.whispersystems.textsecure.util.Base64; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +import ws.com.google.android.mms.MmsException; +import ws.com.google.android.mms.pdu.PduPart; + +public class AttachmentDownloadJob extends MasterSecretJob { + + private static final String TAG = AttachmentDownloadJob.class.getSimpleName(); + + private final long messageId; + + public AttachmentDownloadJob(Context context, long messageId) { + super(context, JobParameters.newBuilder() + .withRequirement(new MasterSecretRequirement(context)) + .withRequirement(new NetworkRequirement(context)) + .withPersistence() + .create()); + + this.messageId = messageId; + } + + @Override + public void onAdded() {} + + @Override + public void onRun() throws RequirementNotMetException, IOException { + MasterSecret masterSecret = getMasterSecret(); + PartDatabase database = DatabaseFactory.getEncryptingPartDatabase(context, masterSecret); + + Log.w(TAG, "Downloading push parts for: " + messageId); + + List> parts = database.getParts(messageId, false); + + for (Pair partPair : parts) { + retrievePart(masterSecret, partPair.second, messageId, partPair.first); + Log.w(TAG, "Got part: " + partPair.first); + } + } + + @Override + public void onCanceled() { + try { + MasterSecret masterSecret = getMasterSecret(); + PartDatabase database = DatabaseFactory.getEncryptingPartDatabase(context, masterSecret); + List> parts = database.getParts(messageId, false); + + for (Pair partPair : parts) { + markFailed(masterSecret, messageId, partPair.second, partPair.first); + } + } catch (RequirementNotMetException e) { + Log.w(TAG, e); + } + } + + @Override + public boolean onShouldRetry(Throwable throwable) { + if (throwable instanceof PushNetworkException) return true; + if (throwable instanceof RequirementNotMetException) return true; + + return false; + } + + private void retrievePart(MasterSecret masterSecret, PduPart part, long messageId, long partId) + throws IOException + { + TextSecureMessageReceiver receiver = TextSecureMessageReceiverFactory.create(context, masterSecret); + EncryptingPartDatabase database = DatabaseFactory.getEncryptingPartDatabase(context, masterSecret); + File attachmentFile = null; + + try { + attachmentFile = createTempFile(); + TextSecureAttachmentPointer pointer = createAttachmentPointer(masterSecret, part); + InputStream attachment = receiver.retrieveAttachment(pointer, attachmentFile); + + database.updateDownloadedPart(messageId, partId, part, attachment); + } catch (InvalidPartException | NonSuccessfulResponseCodeException | InvalidMessageException | MmsException e) { + Log.w(TAG, e); + markFailed(masterSecret, messageId, part, partId); + } finally { + if (attachmentFile != null) + attachmentFile.delete(); + } + } + + private TextSecureAttachmentPointer createAttachmentPointer(MasterSecret masterSecret, PduPart part) + throws InvalidPartException + { + try { + MasterCipher masterCipher = new MasterCipher(masterSecret); + long id = Long.parseLong(Util.toIsoString(part.getContentLocation())); + byte[] key = masterCipher.decryptBytes(Base64.decode(Util.toIsoString(part.getContentDisposition()))); + String relay = null; + + if (part.getName() != null) { + relay = Util.toIsoString(part.getName()); + } + + return new TextSecureAttachmentPointer(id, null, key, relay); + } catch (InvalidMessageException | IOException e) { + Log.w(TAG, e); + throw new InvalidPartException(e); + } + } + + private File createTempFile() throws InvalidPartException { + try { + File file = File.createTempFile("push-attachment", "tmp"); + file.deleteOnExit(); + + return file; + } catch (IOException e) { + throw new InvalidPartException(e); + } + } + + private void markFailed(MasterSecret masterSecret, long messageId, PduPart part, long partId) { + try { + EncryptingPartDatabase database = DatabaseFactory.getEncryptingPartDatabase(context, masterSecret); + database.updateFailedDownloadedPart(messageId, partId, part); + } catch (MmsException e) { + Log.w(TAG, e); + } + } + + private class InvalidPartException extends Exception { + public InvalidPartException(Exception e) {super(e);} + } +} diff --git a/src/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java b/src/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java new file mode 100644 index 0000000000..b67c283bfe --- /dev/null +++ b/src/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java @@ -0,0 +1,107 @@ +package org.thoughtcrime.securesms.jobs; + +import android.content.Context; +import android.graphics.Bitmap; +import android.util.Log; + +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.GroupDatabase; +import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; +import org.thoughtcrime.securesms.push.PushServiceSocketFactory; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientFactory; +import org.thoughtcrime.securesms.recipients.RecipientFormattingException; +import org.thoughtcrime.securesms.util.BitmapDecodingException; +import org.thoughtcrime.securesms.util.BitmapUtil; +import org.thoughtcrime.securesms.util.GroupUtil; +import org.whispersystems.jobqueue.JobParameters; +import org.whispersystems.jobqueue.requirements.NetworkRequirement; +import org.whispersystems.libaxolotl.InvalidMessageException; +import org.whispersystems.textsecure.crypto.AttachmentCipherInputStream; +import org.whispersystems.textsecure.push.PushServiceSocket; +import org.whispersystems.textsecure.push.exceptions.NonSuccessfulResponseCodeException; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +public class AvatarDownloadJob extends MasterSecretJob { + + private static final String TAG = AvatarDownloadJob.class.getSimpleName(); + + private final byte[] groupId; + + public AvatarDownloadJob(Context context, byte[] groupId) { + super(context, JobParameters.newBuilder() + .withRequirement(new MasterSecretRequirement(context)) + .withRequirement(new NetworkRequirement(context)) + .withPersistence() + .create()); + + this.groupId = groupId; + } + + @Override + public void onAdded() {} + + @Override + public void onRun() throws IOException { + GroupDatabase database = DatabaseFactory.getGroupDatabase(context); + GroupDatabase.GroupRecord record = database.getGroup(groupId); + File attachment = null; + + try { + if (record != null) { + long avatarId = record.getAvatarId(); + byte[] key = record.getAvatarKey(); + String relay = record.getRelay(); + + if (avatarId == -1 || key == null) { + return; + } + + attachment = downloadAttachment(relay, avatarId); + + InputStream scaleInputStream = new AttachmentCipherInputStream(attachment, key); + InputStream measureInputStream = new AttachmentCipherInputStream(attachment, key); + Bitmap avatar = BitmapUtil.createScaledBitmap(measureInputStream, scaleInputStream, 500, 500); + + database.updateAvatar(groupId, avatar); + + try { + Recipient groupRecipient = RecipientFactory.getRecipientsFromString(context, GroupUtil.getEncodedId(groupId), true) + .getPrimaryRecipient(); + groupRecipient.setContactPhoto(avatar); + } catch (RecipientFormattingException e) { + Log.w("AvatarDownloader", e); + } + } + } catch (InvalidMessageException | BitmapDecodingException | NonSuccessfulResponseCodeException e) { + Log.w(TAG, e); + } finally { + if (attachment != null) + attachment.delete(); + } + } + + @Override + public void onCanceled() {} + + @Override + public boolean onShouldRetry(Throwable throwable) { + if (throwable instanceof IOException) return true; + return false; + } + + private File downloadAttachment(String relay, long contentLocation) throws IOException { + PushServiceSocket socket = PushServiceSocketFactory.create(context); + File destination = File.createTempFile("avatar", "tmp"); + + destination.deleteOnExit(); + + socket.retrieveAttachment(relay, contentLocation, destination); + + return destination; + } + +} diff --git a/src/org/thoughtcrime/securesms/jobs/CreateSignedPreKeyJob.java b/src/org/thoughtcrime/securesms/jobs/CreateSignedPreKeyJob.java index f5c90cc4fe..67c3c01e46 100644 --- a/src/org/thoughtcrime/securesms/jobs/CreateSignedPreKeyJob.java +++ b/src/org/thoughtcrime/securesms/jobs/CreateSignedPreKeyJob.java @@ -12,8 +12,8 @@ import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.jobqueue.requirements.NetworkRequirement; import org.whispersystems.libaxolotl.IdentityKeyPair; import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; -import org.whispersystems.textsecure.crypto.MasterSecret; -import org.whispersystems.textsecure.crypto.PreKeyUtil; +import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.PreKeyUtil; import org.whispersystems.textsecure.push.PushServiceSocket; import java.io.IOException; diff --git a/src/org/thoughtcrime/securesms/jobs/MasterSecretJob.java b/src/org/thoughtcrime/securesms/jobs/MasterSecretJob.java new file mode 100644 index 0000000000..061c7695ab --- /dev/null +++ b/src/org/thoughtcrime/securesms/jobs/MasterSecretJob.java @@ -0,0 +1,24 @@ +package org.thoughtcrime.securesms.jobs; + +import android.content.Context; + +import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.service.KeyCachingService; +import org.whispersystems.jobqueue.JobParameters; + +public abstract class MasterSecretJob extends ContextJob { + + public MasterSecretJob(Context context, JobParameters parameters) { + super(context, parameters); + } + + protected MasterSecret getMasterSecret() throws RequirementNotMetException { + MasterSecret masterSecret = KeyCachingService.getMasterSecret(context); + + if (masterSecret == null) throw new RequirementNotMetException(); + else return masterSecret; + } + + protected static class RequirementNotMetException extends Exception {} + +} diff --git a/src/org/thoughtcrime/securesms/service/MmsDownloader.java b/src/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java similarity index 51% rename from src/org/thoughtcrime/securesms/service/MmsDownloader.java rename to src/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java index e4a812a29d..66a00cbd47 100644 --- a/src/org/thoughtcrime/securesms/service/MmsDownloader.java +++ b/src/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java @@ -1,142 +1,135 @@ -/** - * Copyright (C) 2013 Open Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.thoughtcrime.securesms.service; +package org.thoughtcrime.securesms.jobs; import android.content.Context; -import android.content.Intent; import android.net.Uri; import android.telephony.TelephonyManager; import android.util.Log; import android.util.Pair; import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.crypto.DecryptingQueue; -import org.thoughtcrime.securesms.mms.IncomingMmsConnection; -import org.thoughtcrime.securesms.mms.MmsConnection; -import org.thoughtcrime.securesms.mms.MmsConnection.Apn; -import org.thoughtcrime.securesms.mms.OutgoingMmsConnection; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MmsCipher; +import org.thoughtcrime.securesms.crypto.storage.TextSecureAxolotlStore; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MmsDatabase; -import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord; +import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; import org.thoughtcrime.securesms.mms.ApnUnavailableException; import org.thoughtcrime.securesms.mms.IncomingMediaMessage; +import org.thoughtcrime.securesms.mms.IncomingMmsConnection; +import org.thoughtcrime.securesms.mms.MmsConnection; import org.thoughtcrime.securesms.mms.MmsRadio; import org.thoughtcrime.securesms.mms.MmsRadioException; +import org.thoughtcrime.securesms.mms.OutgoingMmsConnection; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.protocol.WirePrefix; +import org.thoughtcrime.securesms.service.KeyCachingService; +import org.whispersystems.jobqueue.JobParameters; +import org.whispersystems.jobqueue.requirements.NetworkRequirement; +import org.whispersystems.libaxolotl.DuplicateMessageException; +import org.whispersystems.libaxolotl.InvalidMessageException; +import org.whispersystems.libaxolotl.LegacyMessageException; +import org.whispersystems.libaxolotl.NoSessionException; +import org.whispersystems.libaxolotl.util.guava.Optional; import java.io.IOException; import ws.com.google.android.mms.InvalidHeaderValueException; import ws.com.google.android.mms.MmsException; +import ws.com.google.android.mms.pdu.MultimediaMessagePdu; +import ws.com.google.android.mms.pdu.NotificationInd; import ws.com.google.android.mms.pdu.NotifyRespInd; import ws.com.google.android.mms.pdu.PduComposer; import ws.com.google.android.mms.pdu.PduHeaders; import ws.com.google.android.mms.pdu.RetrieveConf; -public class MmsDownloader { +import static org.thoughtcrime.securesms.mms.MmsConnection.Apn; - private final Context context; - private final SendReceiveService.ToastHandler toastHandler; - private final MmsRadio radio; +public class MmsDownloadJob extends MasterSecretJob { - public MmsDownloader(Context context, SendReceiveService.ToastHandler toastHandler) { - this.context = context; - this.toastHandler = toastHandler; - this.radio = MmsRadio.getInstance(context); + private static final String TAG = MmsDownloadJob.class.getSimpleName(); + + private final long messageId; + private final long threadId; + private final boolean automatic; + + public MmsDownloadJob(Context context, long messageId, long threadId, boolean automatic) { + super(context, JobParameters.newBuilder() + .withPersistence() + .withRequirement(new MasterSecretRequirement(context)) + .withRequirement(new NetworkRequirement(context)) + .withGroupId("mms-download") + .create()); + + this.messageId = messageId; + this.threadId = threadId; + this.automatic = automatic; } - public void process(MasterSecret masterSecret, Intent intent) { - if (SendReceiveService.DOWNLOAD_MMS_ACTION.equals(intent.getAction())) { - handleDownloadMms(masterSecret, intent); - } else if (SendReceiveService.DOWNLOAD_MMS_PENDING_APN_ACTION.equals(intent.getAction())) { - handleMmsPendingApnDownloads(masterSecret); + @Override + public void onAdded() { + if (automatic && KeyCachingService.getMasterSecret(context) == null) { + DatabaseFactory.getMmsDatabase(context).markIncomingNotificationReceived(threadId); + MessageNotifier.updateNotification(context, null); } } - private void handleMmsPendingApnDownloads(MasterSecret masterSecret) { - if (!IncomingMmsConnection.isConnectionPossible(context, null)) + @Override + public void onRun() throws RequirementNotMetException { + Log.w(TAG, "MmsDownloadJob:onRun()"); + + MasterSecret masterSecret = getMasterSecret(); + MmsDatabase database = DatabaseFactory.getMmsDatabase(context); + Optional notification = database.getNotification(messageId); + + if (!notification.isPresent()) { + Log.w(TAG, "No notification for ID: " + messageId); return; - - MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context); - MmsDatabase.Reader stalledMmsReader = mmsDatabase.getNotificationsWithDownloadState(masterSecret, - MmsDatabase.Status.DOWNLOAD_APN_UNAVAILABLE); - while (stalledMmsReader.getNext() != null) { - NotificationMmsMessageRecord stalledMmsRecord = (NotificationMmsMessageRecord) stalledMmsReader.getCurrent(); - - Intent intent = new Intent(SendReceiveService.DOWNLOAD_MMS_ACTION, null, context, SendReceiveService.class); - intent.putExtra("content_location", new String(stalledMmsRecord.getContentLocation())); - intent.putExtra("message_id", stalledMmsRecord.getId()); - intent.putExtra("transaction_id", stalledMmsRecord.getTransactionId()); - intent.putExtra("thread_id", stalledMmsRecord.getThreadId()); - intent.putExtra("automatic", true); - context.startService(intent); } - stalledMmsReader.close(); - } - - private void handleDownloadMms(MasterSecret masterSecret, Intent intent) { - long messageId = intent.getLongExtra("message_id", -1); - long threadId = intent.getLongExtra("thread_id", -1); - byte[] transactionId = intent.getByteArrayExtra("transaction_id"); - String contentLocation = intent.getStringExtra("content_location"); - boolean automatic = intent.getBooleanExtra("automatic", false); - MmsDatabase database = DatabaseFactory.getMmsDatabase(context); - database.markDownloadState(messageId, MmsDatabase.Status.DOWNLOAD_CONNECTING); - Log.w("MmsDownloader", "Downloading mms at "+ Uri.parse(contentLocation).getHost()); + String contentLocation = new String(notification.get().getContentLocation()); + byte[] transactionId = notification.get().getTransactionId(); + MmsRadio radio = MmsRadio.getInstance(context); + + Log.w(TAG, "About to parse URL..."); + + Log.w(TAG, "Downloading mms at " + Uri.parse(contentLocation).getHost()); try { if (isCdmaNetwork()) { - Log.w("MmsDownloader", "Connecting directly..."); + Log.w(TAG, "Connecting directly..."); try { - retrieveAndStore(masterSecret, messageId, threadId, contentLocation, + retrieveAndStore(masterSecret, radio, messageId, threadId, contentLocation, transactionId, false, false); return; } catch (IOException e) { - Log.w("MmsDownloader", e); + Log.w(TAG, e); } } - Log.w("MmsDownloader", "Changing radio to MMS mode.."); + Log.w(TAG, "Changing radio to MMS mode.."); radio.connect(); - Log.w("MmsDownloader", "Downloading in MMS mode with proxy..."); + Log.w(TAG, "Downloading in MMS mode with proxy..."); try { - retrieveAndStore(masterSecret, messageId, threadId, contentLocation, + retrieveAndStore(masterSecret, radio, messageId, threadId, contentLocation, transactionId, true, true); radio.disconnect(); return; } catch (IOException e) { - Log.w("MmsDownloader", e); + Log.w(TAG, e); } - Log.w("MmsDownloader", "Downloading in MMS mode without proxy..."); + Log.w(TAG, "Downloading in MMS mode without proxy..."); try { - retrieveAndStore(masterSecret, messageId, threadId, + retrieveAndStore(masterSecret, radio, messageId, threadId, contentLocation, transactionId, true, false); radio.disconnect(); } catch (IOException e) { - Log.w("MmsDownloader", e); + Log.w(TAG, e); radio.disconnect(); handleDownloadError(masterSecret, messageId, threadId, MmsDatabase.Status.DOWNLOAD_SOFT_FAILURE, @@ -145,28 +138,53 @@ public class MmsDownloader { } } catch (ApnUnavailableException e) { - Log.w("MmsDownloader", e); + Log.w(TAG, e); handleDownloadError(masterSecret, messageId, threadId, MmsDatabase.Status.DOWNLOAD_APN_UNAVAILABLE, context.getString(R.string.MmsDownloader_error_reading_mms_settings), automatic); } catch (MmsException e) { - Log.w("MmsDownloader", e); + Log.w(TAG, e); handleDownloadError(masterSecret, messageId, threadId, MmsDatabase.Status.DOWNLOAD_HARD_FAILURE, context.getString(R.string.MmsDownloader_error_storing_mms), automatic); } catch (MmsRadioException e) { - Log.w("MmsDownloader", e); + Log.w(TAG, e); handleDownloadError(masterSecret, messageId, threadId, MmsDatabase.Status.DOWNLOAD_SOFT_FAILURE, context.getString(R.string.MmsDownloader_error_connecting_to_mms_provider), automatic); + } catch (DuplicateMessageException e) { + Log.w(TAG, e); + database.markAsDecryptDuplicate(messageId, threadId); + } catch (LegacyMessageException e) { + Log.w(TAG, e); + database.markAsLegacyVersion(messageId, threadId); + } catch (NoSessionException e) { + Log.w(TAG, e); + database.markAsNoSession(messageId, threadId); + } catch (InvalidMessageException e) { + Log.w(TAG, e); + database.markAsDecryptFailed(messageId, threadId); } } - private void retrieveAndStore(MasterSecret masterSecret, long messageId, long threadId, + @Override + public void onCanceled() { + // TODO + } + + @Override + public boolean onShouldRetry(Throwable throwable) { + return false; + } + + private void retrieveAndStore(MasterSecret masterSecret, MmsRadio radio, + long messageId, long threadId, String contentLocation, byte[] transactionId, boolean radioEnabled, boolean useProxy) - throws IOException, MmsException, ApnUnavailableException + throws IOException, MmsException, ApnUnavailableException, + DuplicateMessageException, NoSessionException, + InvalidMessageException, LegacyMessageException { Apn dbApn = MmsConnection.getApn(context, radio.getApnInformation()); Apn contentApn = new Apn(contentLocation, dbApn.getProxy(), Integer.toString(dbApn.getPort())); @@ -174,12 +192,13 @@ public class MmsDownloader { RetrieveConf retrieved = connection.retrieve(radioEnabled, useProxy); storeRetrievedMms(masterSecret, contentLocation, messageId, threadId, retrieved); - sendRetrievedAcknowledgement(transactionId, radioEnabled, useProxy); + sendRetrievedAcknowledgement(radio, transactionId, radioEnabled, useProxy); } private void storeRetrievedMms(MasterSecret masterSecret, String contentLocation, long messageId, long threadId, RetrieveConf retrieved) - throws MmsException + throws MmsException, NoSessionException, DuplicateMessageException, InvalidMessageException, + LegacyMessageException { MmsDatabase database = DatabaseFactory.getMmsDatabase(context); IncomingMediaMessage message = new IncomingMediaMessage(retrieved); @@ -187,12 +206,17 @@ public class MmsDownloader { Pair messageAndThreadId; if (retrieved.getSubject() != null && WirePrefix.isEncryptedMmsSubject(retrieved.getSubject().getString())) { - messageAndThreadId = database.insertSecureMessageInbox(masterSecret, message, - contentLocation, threadId); + MmsCipher mmsCipher = new MmsCipher(new TextSecureAxolotlStore(context, masterSecret)); + MultimediaMessagePdu plaintextPdu = mmsCipher.decrypt(context, retrieved); + RetrieveConf plaintextRetrieved = new RetrieveConf(plaintextPdu.getPduHeaders(), plaintextPdu.getBody()); + IncomingMediaMessage plaintextMessage = new IncomingMediaMessage(plaintextRetrieved); - if (masterSecret != null) - DecryptingQueue.scheduleDecryption(context, masterSecret, messageAndThreadId.first, - messageAndThreadId.second, retrieved); + messageAndThreadId = database.insertSecureDecryptedMessageInbox(masterSecret, plaintextMessage, + threadId); + +// if (masterSecret != null) +// DecryptingQueue.scheduleDecryption(context, masterSecret, messageAndThreadId.first, +// messageAndThreadId.second, retrieved); } else { messageAndThreadId = database.insertMessageInbox(masterSecret, message, @@ -203,7 +227,8 @@ public class MmsDownloader { MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); } - private void sendRetrievedAcknowledgement(byte[] transactionId, + private void sendRetrievedAcknowledgement(MmsRadio radio, + byte[] transactionId, boolean usingRadio, boolean useProxy) throws ApnUnavailableException @@ -215,14 +240,11 @@ public class MmsDownloader { OutgoingMmsConnection connection = new OutgoingMmsConnection(context, radio.getApnInformation(), new PduComposer(context, notifyResponse).make()); connection.sendNotificationReceived(usingRadio, useProxy); - } catch (InvalidHeaderValueException e) { - Log.w("MmsDownloader", e); - } catch (IOException e) { - Log.w("MmsDownloader", e); + } catch (InvalidHeaderValueException | IOException e) { + Log.w(TAG, e); } } - private void handleDownloadError(MasterSecret masterSecret, long messageId, long threadId, int downloadStatus, String error, boolean automatic) { @@ -234,8 +256,8 @@ public class MmsDownloader { db.markIncomingNotificationReceived(threadId); MessageNotifier.updateNotification(context, masterSecret, threadId); } - - toastHandler.makeToast(error); +// +// toastHandler.makeToast(error); } private boolean isCdmaNetwork() { @@ -243,4 +265,5 @@ public class MmsDownloader { .getSystemService(Context.TELEPHONY_SERVICE)) .getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA; } + } diff --git a/src/org/thoughtcrime/securesms/jobs/MmsReceiveJob.java b/src/org/thoughtcrime/securesms/jobs/MmsReceiveJob.java new file mode 100644 index 0000000000..af1401a0c7 --- /dev/null +++ b/src/org/thoughtcrime/securesms/jobs/MmsReceiveJob.java @@ -0,0 +1,69 @@ +package org.thoughtcrime.securesms.jobs; + +import android.content.Context; +import android.util.Log; +import android.util.Pair; + +import org.thoughtcrime.securesms.ApplicationContext; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.MmsDatabase; +import org.whispersystems.jobqueue.JobParameters; + +import ws.com.google.android.mms.pdu.GenericPdu; +import ws.com.google.android.mms.pdu.NotificationInd; +import ws.com.google.android.mms.pdu.PduHeaders; +import ws.com.google.android.mms.pdu.PduParser; + +public class MmsReceiveJob extends ContextJob { + + private static final String TAG = MmsReceiveJob.class.getSimpleName(); + + private final byte[] data; + + public MmsReceiveJob(Context context, byte[] data) { + super(context, JobParameters.newBuilder() + .withPersistence().create()); + + this.data = data; + } + + @Override + public void onAdded() { + + } + + @Override + public void onRun() { + if (data == null) { + Log.w(TAG, "Received NULL pdu, ignoring..."); + return; + } + + PduParser parser = new PduParser(data); + GenericPdu pdu = parser.parse(); + + if (pdu != null && pdu.getMessageType() == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND) { + MmsDatabase database = DatabaseFactory.getMmsDatabase(context); + Pair messageAndThreadId = database.insertMessageInbox((NotificationInd)pdu); + + Log.w(TAG, "Inserted received MMS notification..."); + + ApplicationContext.getInstance(context) + .getJobManager() + .add(new MmsDownloadJob(context, + messageAndThreadId.first, + messageAndThreadId.second, + true)); + } + } + + @Override + public void onCanceled() { + // TODO + } + + @Override + public boolean onShouldRetry(Throwable throwable) { + return false; + } +} diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java new file mode 100644 index 0000000000..68ff17faf4 --- /dev/null +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -0,0 +1,252 @@ +package org.thoughtcrime.securesms.jobs; + +import android.content.Context; +import android.util.Log; +import android.util.Pair; + +import org.thoughtcrime.securesms.ApplicationContext; +import org.thoughtcrime.securesms.crypto.SecurityEvent; +import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; +import org.thoughtcrime.securesms.database.MmsDatabase; +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.notifications.MessageNotifier; +import org.thoughtcrime.securesms.push.TextSecureMessageReceiverFactory; +import org.thoughtcrime.securesms.recipients.RecipientFactory; +import org.thoughtcrime.securesms.recipients.Recipients; +import org.thoughtcrime.securesms.service.KeyCachingService; +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.util.TextSecurePreferences; +import org.whispersystems.jobqueue.JobParameters; +import org.whispersystems.libaxolotl.DuplicateMessageException; +import org.whispersystems.libaxolotl.InvalidKeyException; +import org.whispersystems.libaxolotl.InvalidKeyIdException; +import org.whispersystems.libaxolotl.InvalidMessageException; +import org.whispersystems.libaxolotl.InvalidVersionException; +import org.whispersystems.libaxolotl.LegacyMessageException; +import org.whispersystems.libaxolotl.NoSessionException; +import org.whispersystems.libaxolotl.UntrustedIdentityException; +import org.whispersystems.libaxolotl.state.SessionStore; +import org.whispersystems.libaxolotl.util.guava.Optional; +import org.whispersystems.textsecure.api.TextSecureMessageReceiver; +import org.whispersystems.textsecure.api.messages.TextSecureGroup; +import org.whispersystems.textsecure.api.messages.TextSecureMessage; +import org.whispersystems.textsecure.push.IncomingPushMessage; +import org.whispersystems.textsecure.util.Base64; + +import ws.com.google.android.mms.MmsException; + +public class PushDecryptJob extends MasterSecretJob { + + public static final String TAG = PushDecryptJob.class.getSimpleName(); + + private final long messageId; + + public PushDecryptJob(Context context, long messageId) { + super(context, JobParameters.newBuilder() + .withPersistence() + .withRequirement(new MasterSecretRequirement(context)) + .create()); + this.messageId = messageId; + } + + @Override + public void onAdded() { + if (KeyCachingService.getMasterSecret(context) == null) { + MessageNotifier.updateNotification(context, null); + } + } + + @Override + public void onRun() throws RequirementNotMetException { + try { + MasterSecret masterSecret = getMasterSecret(); + PushDatabase database = DatabaseFactory.getPushDatabase(context); + IncomingPushMessage push = database.get(messageId); + + handleMessage(masterSecret, push); + database.delete(messageId); + + } catch (PushDatabase.NoSuchMessageException e) { + Log.w(TAG, e); + } + } + + @Override + public void onCanceled() { + + } + + @Override + public boolean onShouldRetry(Throwable throwable) { + if (throwable instanceof RequirementNotMetException) return true; + return false; + } + + private void handleMessage(MasterSecret masterSecret, IncomingPushMessage push) { + try { + Recipients recipients = RecipientFactory.getRecipientsFromMessage(context, push, false); + long recipientId = recipients.getPrimaryRecipient().getRecipientId(); + TextSecureMessageReceiver messageReceiver = TextSecureMessageReceiverFactory.create(context, masterSecret); + + TextSecureMessage message = messageReceiver.receiveMessage(recipientId, push); + + if (message.isEndSession()) handleEndSessionMessage(masterSecret, recipientId, push, message); + else if (message.isGroupUpdate()) handleGroupMessage(masterSecret, push, message); + else if (message.getAttachments().isPresent()) handleMediaMessage(masterSecret, push, message); + else handleTextMessage(masterSecret, push, message); + } catch (InvalidVersionException e) { + Log.w(TAG, e); + handleInvalidVersionMessage(masterSecret, push); + } catch (InvalidMessageException | InvalidKeyIdException | InvalidKeyException | MmsException e) { + Log.w(TAG, e); + handleCorruptMessage(masterSecret, push); + } catch (NoSessionException e) { + Log.w(TAG, e); + handleNoSessionMessage(masterSecret, push); + } catch (LegacyMessageException e) { + Log.w(TAG, e); + handleLegacyMessage(masterSecret, push); + } catch (DuplicateMessageException e) { + Log.w(TAG, e); + handleDuplicateMessage(masterSecret, push); + } catch (UntrustedIdentityException e) { + Log.w(TAG, e); + handleUntrustedIdentityMessage(masterSecret, push); + } + } + + private void handleEndSessionMessage(MasterSecret masterSecret, long recipientId, IncomingPushMessage push, TextSecureMessage message) { + IncomingTextMessage incomingTextMessage = new IncomingTextMessage(push.getSource(), + push.getSourceDevice(), + message.getTimestamp(), + "", Optional.absent()); + + IncomingEndSessionMessage incomingEndSessionMessage = new IncomingEndSessionMessage(incomingTextMessage); + EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); + Pair messageAndThreadId = database.insertMessageInbox(masterSecret, incomingEndSessionMessage); + + SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret); + sessionStore.deleteAllSessions(recipientId); + + SecurityEvent.broadcastSecurityUpdateEvent(context, messageAndThreadId.second); + MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); + } + + private void handleGroupMessage(MasterSecret masterSecret, IncomingPushMessage push, TextSecureMessage message) { + GroupMessageProcessor.process(context, masterSecret, push, message); + } + + private void handleMediaMessage(MasterSecret masterSecret, IncomingPushMessage signal, TextSecureMessage message) + throws MmsException + { + String localNumber = TextSecurePreferences.getLocalNumber(context); + MmsDatabase database = DatabaseFactory.getMmsDatabase(context); + IncomingMediaMessage mediaMessage = new IncomingMediaMessage(masterSecret, signal.getSource(), + localNumber, message.getTimestamp(), + Optional.fromNullable(signal.getRelay()), + message.getBody(), + message.getGroupInfo(), + message.getAttachments()); + + Pair messageAndThreadId; + + if (message.isSecure()) { + messageAndThreadId = database.insertSecureDecryptedMessageInbox(masterSecret, mediaMessage, -1); + } else { + messageAndThreadId = database.insertMessageInbox(masterSecret, mediaMessage, null, -1); + } + + ApplicationContext.getInstance(context) + .getJobManager() + .add(new AttachmentDownloadJob(context, messageAndThreadId.first)); + + MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); + } + + private void handleTextMessage(MasterSecret masterSecret, IncomingPushMessage signal, TextSecureMessage message) { + EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); + String body = message.getBody().isPresent() ? message.getBody().get() : ""; + IncomingTextMessage textMessage = new IncomingTextMessage(signal.getSource(), + signal.getSourceDevice(), + message.getTimestamp(), body, + message.getGroupInfo()); + + if (message.isSecure()) { + textMessage = new IncomingEncryptedMessage(textMessage, body); + } + + Pair messageAndThreadId = database.insertMessageInbox(masterSecret, textMessage); +// database.updateMessageBody(masterSecret, messageAndThreadId.first, body); + + MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); + } + + private void handleInvalidVersionMessage(MasterSecret masterSecret, IncomingPushMessage push) { + Pair messageAndThreadId = insertPlaceholder(masterSecret, push); + DatabaseFactory.getEncryptingSmsDatabase(context).markAsInvalidVersionKeyExchange(messageAndThreadId.first); + + MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); + } + + private void handleCorruptMessage(MasterSecret masterSecret, IncomingPushMessage push) { + Pair messageAndThreadId = insertPlaceholder(masterSecret, push); + DatabaseFactory.getEncryptingSmsDatabase(context).markAsDecryptFailed(messageAndThreadId.first); + + MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); + } + + private void handleNoSessionMessage(MasterSecret masterSecret, IncomingPushMessage push) { + Pair messageAndThreadId = insertPlaceholder(masterSecret, push); + DatabaseFactory.getEncryptingSmsDatabase(context).markAsNoSession(messageAndThreadId.first); + + MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); + } + + private void handleLegacyMessage(MasterSecret masterSecret, IncomingPushMessage push) { + Pair messageAndThreadId = insertPlaceholder(masterSecret, push); + DatabaseFactory.getEncryptingSmsDatabase(context).markAsLegacyVersion(messageAndThreadId.first); + + MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); + } + + private void handleDuplicateMessage(MasterSecret masterSecret, IncomingPushMessage push) { + Pair messageAndThreadId = insertPlaceholder(masterSecret, push); + DatabaseFactory.getEncryptingSmsDatabase(context).markAsDecryptDuplicate(messageAndThreadId.first); + + MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); + } + + private void handleUntrustedIdentityMessage(MasterSecret masterSecret, IncomingPushMessage push) { + String encoded = Base64.encodeBytes(push.getBody()); + IncomingTextMessage textMessage = new IncomingTextMessage(push.getSource(), push.getSourceDevice(), + push.getTimestampMillis(), encoded, + Optional.absent()); + + IncomingPreKeyBundleMessage bundleMessage = new IncomingPreKeyBundleMessage(textMessage, encoded); + Pair messageAndThreadId = DatabaseFactory.getEncryptingSmsDatabase(context) + .insertMessageInbox(masterSecret, bundleMessage); + + MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); + } + + private Pair insertPlaceholder(MasterSecret masterSecret, IncomingPushMessage push) { + EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); + + IncomingTextMessage textMessage = new IncomingTextMessage(push.getSource(), push.getSourceDevice(), + push.getTimestampMillis(), "", + Optional.absent()); + + textMessage = new IncomingEncryptedMessage(textMessage, ""); + + return database.insertMessageInbox(masterSecret, textMessage); + } +} diff --git a/src/org/thoughtcrime/securesms/jobs/PushReceiveJob.java b/src/org/thoughtcrime/securesms/jobs/PushReceiveJob.java new file mode 100644 index 0000000000..2a006e7be3 --- /dev/null +++ b/src/org/thoughtcrime/securesms/jobs/PushReceiveJob.java @@ -0,0 +1,98 @@ +package org.thoughtcrime.securesms.jobs; + +import android.content.Context; +import android.util.Log; + +import org.thoughtcrime.securesms.ApplicationContext; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.whispersystems.jobqueue.JobManager; +import org.whispersystems.jobqueue.JobParameters; +import org.whispersystems.libaxolotl.InvalidVersionException; +import org.whispersystems.textsecure.directory.Directory; +import org.whispersystems.textsecure.directory.NotInDirectoryException; +import org.whispersystems.textsecure.push.ContactTokenDetails; +import org.whispersystems.textsecure.push.IncomingEncryptedPushMessage; +import org.whispersystems.textsecure.push.IncomingPushMessage; + +import java.io.IOException; + +public class PushReceiveJob extends ContextJob { + + private static final String TAG = PushReceiveJob.class.getSimpleName(); + + private final String data; + + public PushReceiveJob(Context context, String data) { + super(context, JobParameters.newBuilder() + .withPersistence() + .create()); + + this.data = data; + } + + @Override + public void onAdded() {} + + @Override + public void onRun() { + try { + String sessionKey = TextSecurePreferences.getSignalingKey(context); + IncomingEncryptedPushMessage encrypted = new IncomingEncryptedPushMessage(data, sessionKey); + IncomingPushMessage message = encrypted.getIncomingPushMessage(); + + if (!isActiveNumber(context, message.getSource())) { + Directory directory = Directory.getInstance(context); + ContactTokenDetails contactTokenDetails = new ContactTokenDetails(); + contactTokenDetails.setNumber(message.getSource()); + + directory.setNumber(contactTokenDetails, true); + } + + if (message.isReceipt()) handleReceipt(message); + else handleMessage(message); + } catch (IOException | InvalidVersionException e) { + Log.w(TAG, e); + } + } + + @Override + public void onCanceled() { + + } + + @Override + public boolean onShouldRetry(Throwable throwable) { + return false; + } + + private void handleMessage(IncomingPushMessage message) { + JobManager jobManager = ApplicationContext.getInstance(context).getJobManager(); + long messageId = DatabaseFactory.getPushDatabase(context).insert(message); + + jobManager.add(new DeliveryReceiptJob(context, message.getSource(), + message.getTimestampMillis(), + message.getRelay())); + + jobManager.add(new PushDecryptJob(context, messageId)); + } + + private void handleReceipt(IncomingPushMessage message) { + Log.w(TAG, String.format("Received receipt: (XXXXX, %d)", message.getTimestampMillis())); + DatabaseFactory.getMmsSmsDatabase(context).incrementDeliveryReceiptCount(message.getSource(), + message.getTimestampMillis()); + } + + private boolean isActiveNumber(Context context, String e164number) { + boolean isActiveNumber; + + try { + isActiveNumber = Directory.getInstance(context).isActiveNumber(e164number); + } catch (NotInDirectoryException e) { + isActiveNumber = false; + } + + return isActiveNumber; + } + +} diff --git a/src/org/thoughtcrime/securesms/jobs/SmsDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/SmsDecryptJob.java new file mode 100644 index 0000000000..b6aa9ebb48 --- /dev/null +++ b/src/org/thoughtcrime/securesms/jobs/SmsDecryptJob.java @@ -0,0 +1,220 @@ +package org.thoughtcrime.securesms.jobs; + +import android.content.Context; +import android.util.Log; + +import org.thoughtcrime.securesms.crypto.AsymmetricMasterCipher; +import org.thoughtcrime.securesms.crypto.AsymmetricMasterSecret; +import org.thoughtcrime.securesms.crypto.SecurityEvent; +import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecretUtil; +import org.thoughtcrime.securesms.crypto.SmsCipher; +import org.thoughtcrime.securesms.crypto.storage.TextSecureAxolotlStore; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; +import org.thoughtcrime.securesms.database.SmsDatabase; +import org.thoughtcrime.securesms.database.model.SmsMessageRecord; +import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; +import org.thoughtcrime.securesms.notifications.MessageNotifier; +import org.thoughtcrime.securesms.service.KeyCachingService; +import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage; +import org.thoughtcrime.securesms.sms.IncomingEndSessionMessage; +import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage; +import org.thoughtcrime.securesms.sms.IncomingPreKeyBundleMessage; +import org.thoughtcrime.securesms.sms.IncomingTextMessage; +import org.thoughtcrime.securesms.sms.MessageSender; +import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage; +import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.whispersystems.jobqueue.JobParameters; +import org.whispersystems.libaxolotl.DuplicateMessageException; +import org.whispersystems.libaxolotl.InvalidMessageException; +import org.whispersystems.libaxolotl.InvalidVersionException; +import org.whispersystems.libaxolotl.LegacyMessageException; +import org.whispersystems.libaxolotl.NoSessionException; +import org.whispersystems.libaxolotl.StaleKeyExchangeException; +import org.whispersystems.libaxolotl.UntrustedIdentityException; +import org.whispersystems.libaxolotl.util.guava.Optional; +import org.whispersystems.textsecure.api.messages.TextSecureGroup; + +import java.io.IOException; + +public class SmsDecryptJob extends MasterSecretJob { + + private static final String TAG = SmsDecryptJob.class.getSimpleName(); + + private final long messageId; + + public SmsDecryptJob(Context context, long messageId) { + super(context, JobParameters.newBuilder() + .withPersistence() + .withRequirement(new MasterSecretRequirement(context)) + .create()); + + this.messageId = messageId; + } + + @Override + public void onAdded() { + if (KeyCachingService.getMasterSecret(context) == null) { + MessageNotifier.updateNotification(context, null); + } + } + + @Override + public void onRun() throws RequirementNotMetException { + MasterSecret masterSecret = getMasterSecret(); + EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); + SmsDatabase.Reader reader = null; + + try { + reader = database.getMessage(masterSecret, messageId); + + SmsMessageRecord record = reader.getNext(); + IncomingTextMessage message = createIncomingTextMessage(masterSecret, record); + long messageId = record.getId(); + long threadId = record.getThreadId(); + + if (message.isSecureMessage()) handleSecureMessage(masterSecret, messageId, message); + else if (message.isPreKeyBundle()) handlePreKeyWhisperMessage(masterSecret, messageId, threadId, (IncomingPreKeyBundleMessage) message); + else if (message.isKeyExchange()) handleKeyExchangeMessage(masterSecret, messageId, threadId, (IncomingKeyExchangeMessage) message); + else if (message.isEndSession()) handleSecureMessage(masterSecret, messageId, message); + else database.updateMessageBody(masterSecret, messageId, message.getMessageBody()); + + MessageNotifier.updateNotification(context, masterSecret); + } catch (LegacyMessageException e) { + Log.w(TAG, e); + database.markAsLegacyVersion(messageId); + } catch (InvalidMessageException e) { + Log.w(TAG, e); + database.markAsDecryptFailed(messageId); + } catch (DuplicateMessageException e) { + Log.w(TAG, e); + database.markAsDecryptDuplicate(messageId); + } catch (NoSessionException e) { + Log.w(TAG, e); + database.markAsNoSession(messageId); + } finally { + if (reader != null) + reader.close(); + } + } + + @Override + public void onCanceled() { + // TODO + } + + private void handleSecureMessage(MasterSecret masterSecret, long messageId, IncomingTextMessage message) + throws NoSessionException, DuplicateMessageException, + InvalidMessageException, LegacyMessageException + { + EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); + SmsCipher cipher = new SmsCipher(new TextSecureAxolotlStore(context, masterSecret)); + IncomingTextMessage plaintext = cipher.decrypt(context, message); + + database.updateMessageBody(masterSecret, messageId, plaintext.getMessageBody()); + } + + private void handlePreKeyWhisperMessage(MasterSecret masterSecret, long messageId, long threadId, + IncomingPreKeyBundleMessage message) + throws NoSessionException, DuplicateMessageException, + InvalidMessageException, LegacyMessageException + { + EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); + + try { + SmsCipher smsCipher = new SmsCipher(new TextSecureAxolotlStore(context, masterSecret)); + IncomingEncryptedMessage plaintext = smsCipher.decrypt(context, message); + + database.updateBundleMessageBody(masterSecret, messageId, plaintext.getMessageBody()); + + SecurityEvent.broadcastSecurityUpdateEvent(context, threadId); + } catch (InvalidVersionException e) { + Log.w(TAG, e); + database.markAsInvalidVersionKeyExchange(messageId); + } catch (UntrustedIdentityException e) { + Log.w(TAG, e); + } + } + + private void handleKeyExchangeMessage(MasterSecret masterSecret, long messageId, long threadId, + IncomingKeyExchangeMessage message) + { + EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); + + if (TextSecurePreferences.isAutoRespondKeyExchangeEnabled(context)) { + try { + SmsCipher cipher = new SmsCipher(new TextSecureAxolotlStore(context, masterSecret)); + OutgoingKeyExchangeMessage response = cipher.process(context, message); + + database.markAsProcessedKeyExchange(messageId); + + + if (response != null) { + MessageSender.send(context, masterSecret, response, threadId, true); + } + } catch (InvalidVersionException e) { + Log.w(TAG, e); + database.markAsInvalidVersionKeyExchange(messageId); + } catch (InvalidMessageException e) { + Log.w(TAG, e); + database.markAsCorruptKeyExchange(messageId); + } catch (LegacyMessageException e) { + Log.w(TAG, e); + database.markAsLegacyVersion(messageId); + } catch (StaleKeyExchangeException e) { + Log.w(TAG, e); + database.markAsStaleKeyExchange(messageId); + } catch (UntrustedIdentityException e) { + Log.w(TAG, e); + } + } + } + + @Override + public boolean onShouldRetry(Throwable throwable) { + if (throwable instanceof RequirementNotMetException) return true; + return false; + } + + private String getAsymmetricDecryptedBody(MasterSecret masterSecret, String body) + throws InvalidMessageException + { + try { + AsymmetricMasterSecret asymmetricMasterSecret = MasterSecretUtil.getAsymmetricMasterSecret(context, masterSecret); + AsymmetricMasterCipher asymmetricMasterCipher = new AsymmetricMasterCipher(asymmetricMasterSecret); + + return asymmetricMasterCipher.decryptBody(body); + } catch (IOException e) { + throw new InvalidMessageException(e); + } + } + + private IncomingTextMessage createIncomingTextMessage(MasterSecret masterSecret, SmsMessageRecord record) + throws InvalidMessageException + { + String plaintextBody = record.getBody().getBody(); + + if (record.isAsymmetricEncryption()) { + plaintextBody = getAsymmetricDecryptedBody(masterSecret, record.getBody().getBody()); + } + + IncomingTextMessage message = new IncomingTextMessage(record.getRecipients().getPrimaryRecipient().getNumber(), + record.getRecipientDeviceId(), + record.getDateSent(), + plaintextBody, + Optional.absent()); + + if (record.isEndSession()) { + return new IncomingEndSessionMessage(message); + } else if (record.isBundleKeyExchange()) { + return new IncomingPreKeyBundleMessage(message, message.getMessageBody()); + } else if (record.isKeyExchange()) { + return new IncomingKeyExchangeMessage(message, message.getMessageBody()); + } else if (record.isSecure()) { + return new IncomingEncryptedMessage(message, message.getMessageBody()); + } + + return message; + } +} diff --git a/src/org/thoughtcrime/securesms/jobs/SmsReceiveJob.java b/src/org/thoughtcrime/securesms/jobs/SmsReceiveJob.java new file mode 100644 index 0000000000..2a8731e1f2 --- /dev/null +++ b/src/org/thoughtcrime/securesms/jobs/SmsReceiveJob.java @@ -0,0 +1,110 @@ +package org.thoughtcrime.securesms.jobs; + +import android.content.Context; +import android.telephony.SmsMessage; +import android.util.Pair; + +import org.thoughtcrime.securesms.ApplicationContext; +import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecretUtil; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; +import org.thoughtcrime.securesms.notifications.MessageNotifier; +import org.thoughtcrime.securesms.protocol.WirePrefix; +import org.thoughtcrime.securesms.service.KeyCachingService; +import org.thoughtcrime.securesms.sms.IncomingTextMessage; +import org.thoughtcrime.securesms.sms.MultipartSmsMessageHandler; +import org.whispersystems.jobqueue.JobParameters; +import org.whispersystems.libaxolotl.util.guava.Optional; + +import java.util.LinkedList; +import java.util.List; + +public class SmsReceiveJob extends ContextJob { + + private static final String TAG = SmsReceiveJob.class.getSimpleName(); + + private static MultipartSmsMessageHandler multipartMessageHandler = new MultipartSmsMessageHandler(); + + private final Object[] pdus; + + public SmsReceiveJob(Context context, Object[] pdus) { + super(context, JobParameters.newBuilder() + .withPersistence() + .create()); + + this.pdus = pdus; + } + + @Override + public void onAdded() {} + + @Override + public void onRun() { + Optional message = assembleMessageFragments(pdus); + + if (message.isPresent()) { + Pair messageAndThreadId = storeMessage(message.get()); + MessageNotifier.updateNotification(context, KeyCachingService.getMasterSecret(context), messageAndThreadId.second); + } + } + + @Override + public void onCanceled() { + + } + + @Override + public boolean onShouldRetry(Throwable throwable) { + return false; + } + + private Pair storeMessage(IncomingTextMessage message) { + EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); + MasterSecret masterSecret = KeyCachingService.getMasterSecret(context); + + Pair messageAndThreadId; + + if (message.isSecureMessage()) { + messageAndThreadId = database.insertMessageInbox((MasterSecret)null, message); + } else if (masterSecret == null) { + messageAndThreadId = database.insertMessageInbox(MasterSecretUtil.getAsymmetricMasterSecret(context, null), message); + } else { + messageAndThreadId = database.insertMessageInbox(masterSecret, message); + } + + if (masterSecret == null || message.isSecureMessage() || message.isKeyExchange()) { + ApplicationContext.getInstance(context) + .getJobManager() + .add(new SmsDecryptJob(context, messageAndThreadId.first)); + } else { + MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); + } + + return messageAndThreadId; + } + + private Optional assembleMessageFragments(Object[] pdus) { + List messages = new LinkedList<>(); + + for (Object pdu : pdus) { + messages.add(new IncomingTextMessage(SmsMessage.createFromPdu((byte[])pdu))); + } + + if (messages.isEmpty()) { + return Optional.absent(); + } + + IncomingTextMessage message = new IncomingTextMessage(messages); + + if (WirePrefix.isEncryptedMessage(message.getMessageBody()) || + WirePrefix.isKeyExchange(message.getMessageBody()) || + WirePrefix.isPreKeyBundle(message.getMessageBody()) || + WirePrefix.isEndSession(message.getMessageBody())) + { + return Optional.fromNullable(multipartMessageHandler.processPotentialMultipartMessage(message)); + } else { + return Optional.of(message); + } + } +} diff --git a/src/org/thoughtcrime/securesms/jobs/SmsSendJob.java b/src/org/thoughtcrime/securesms/jobs/SmsSendJob.java new file mode 100644 index 0000000000..2f5f019927 --- /dev/null +++ b/src/org/thoughtcrime/securesms/jobs/SmsSendJob.java @@ -0,0 +1,48 @@ +package org.thoughtcrime.securesms.jobs; + +import android.content.Context; + +import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.util.ParcelUtil; +import org.whispersystems.jobqueue.EncryptionKeys; +import org.whispersystems.jobqueue.JobParameters; +import org.whispersystems.jobqueue.requirements.NetworkRequirement; + +public class SmsSendJob extends ContextJob { + + private transient MasterSecret masterSecret; + + private final long messageId; + + public SmsSendJob(Context context, MasterSecret masterSecret, long messageId, String name) { + super(context, JobParameters.newBuilder() + .withPersistence() + .withEncryption(new EncryptionKeys(ParcelUtil.serialize(masterSecret))) + .withGroupId(name) + .create()); + + this.messageId = messageId; + } + + @Override + public void onAdded() { + + } + + @Override + public void onRun() { + MasterSecret masterSecret = ParcelUtil.deserialize(getEncryptionKeys().getEncoded(), MasterSecret.CREATOR); + + + } + + @Override + public void onCanceled() { + + } + + @Override + public boolean onShouldRetry(Throwable throwable) { + return false; + } +} diff --git a/src/org/thoughtcrime/securesms/jobs/EncryptingJobSerializer.java b/src/org/thoughtcrime/securesms/jobs/persistence/EncryptingJobSerializer.java similarity index 91% rename from src/org/thoughtcrime/securesms/jobs/EncryptingJobSerializer.java rename to src/org/thoughtcrime/securesms/jobs/persistence/EncryptingJobSerializer.java index 8b96cf6f20..0ae906395e 100644 --- a/src/org/thoughtcrime/securesms/jobs/EncryptingJobSerializer.java +++ b/src/org/thoughtcrime/securesms/jobs/persistence/EncryptingJobSerializer.java @@ -1,4 +1,4 @@ -package org.thoughtcrime.securesms.jobs; +package org.thoughtcrime.securesms.jobs.persistence; import android.content.Context; @@ -8,8 +8,8 @@ import org.whispersystems.jobqueue.Job; import org.whispersystems.jobqueue.persistence.JavaJobSerializer; import org.whispersystems.jobqueue.persistence.JobSerializer; import org.whispersystems.libaxolotl.InvalidMessageException; -import org.whispersystems.textsecure.crypto.MasterCipher; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterCipher; +import org.thoughtcrime.securesms.crypto.MasterSecret; import java.io.IOException; diff --git a/src/org/thoughtcrime/securesms/jobs/requirements/MasterSecretRequirement.java b/src/org/thoughtcrime/securesms/jobs/requirements/MasterSecretRequirement.java new file mode 100644 index 0000000000..6e4f9e576f --- /dev/null +++ b/src/org/thoughtcrime/securesms/jobs/requirements/MasterSecretRequirement.java @@ -0,0 +1,26 @@ +package org.thoughtcrime.securesms.jobs.requirements; + +import android.content.Context; + +import org.thoughtcrime.securesms.service.KeyCachingService; +import org.whispersystems.jobqueue.dependencies.ContextDependent; +import org.whispersystems.jobqueue.requirements.Requirement; + +public class MasterSecretRequirement implements Requirement, ContextDependent { + + private transient Context context; + + public MasterSecretRequirement(Context context) { + this.context = context; + } + + @Override + public boolean isPresent() { + return KeyCachingService.getMasterSecret(context) != null; + } + + @Override + public void setContext(Context context) { + this.context = context; + } +} diff --git a/src/org/thoughtcrime/securesms/jobs/requirements/MasterSecretRequirementProvider.java b/src/org/thoughtcrime/securesms/jobs/requirements/MasterSecretRequirementProvider.java new file mode 100644 index 0000000000..f4f1103d81 --- /dev/null +++ b/src/org/thoughtcrime/securesms/jobs/requirements/MasterSecretRequirementProvider.java @@ -0,0 +1,36 @@ +package org.thoughtcrime.securesms.jobs.requirements; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; + +import org.thoughtcrime.securesms.service.KeyCachingService; +import org.whispersystems.jobqueue.requirements.RequirementListener; +import org.whispersystems.jobqueue.requirements.RequirementProvider; + +public class MasterSecretRequirementProvider implements RequirementProvider { + + private final BroadcastReceiver newKeyReceiver; + + private RequirementListener listener; + + public MasterSecretRequirementProvider(Context context) { + this.newKeyReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (listener != null) { + listener.onRequirementStatusChanged(); + } + } + }; + + IntentFilter filter = new IntentFilter(KeyCachingService.NEW_KEY_EVENT); + context.registerReceiver(newKeyReceiver, filter, KeyCachingService.KEY_PERMISSION, null); + } + + @Override + public void setListener(RequirementListener listener) { + this.listener = listener; + } +} diff --git a/src/org/thoughtcrime/securesms/mms/ImageSlide.java b/src/org/thoughtcrime/securesms/mms/ImageSlide.java index c58d2018db..533d13de41 100644 --- a/src/org/thoughtcrime/securesms/mms/ImageSlide.java +++ b/src/org/thoughtcrime/securesms/mms/ImageSlide.java @@ -37,7 +37,7 @@ import org.thoughtcrime.securesms.util.SmilUtil; import org.w3c.dom.smil.SMILDocument; import org.w3c.dom.smil.SMILMediaElement; import org.w3c.dom.smil.SMILRegionElement; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; import java.io.FileNotFoundException; import java.io.IOException; diff --git a/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java b/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java index fd269cf1d9..2896c1bf21 100644 --- a/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java +++ b/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java @@ -2,12 +2,16 @@ package org.thoughtcrime.securesms.mms; import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.Util; -import org.whispersystems.textsecure.crypto.MasterCipher; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterCipher; +import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.whispersystems.libaxolotl.util.guava.Optional; +import org.whispersystems.textsecure.api.messages.TextSecureAttachment; +import org.whispersystems.textsecure.api.messages.TextSecureGroup; import org.whispersystems.textsecure.push.IncomingPushMessage; import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent; import org.whispersystems.textsecure.util.Base64; -import org.whispersystems.textsecure.util.Hex; + +import java.util.List; import ws.com.google.android.mms.pdu.CharacterSets; import ws.com.google.android.mms.pdu.EncodedStringValue; @@ -30,49 +34,56 @@ public class IncomingMediaMessage { this.push = false; } - public IncomingMediaMessage(MasterSecret masterSecret, String localNumber, - IncomingPushMessage message, - PushMessageContent messageContent) + public IncomingMediaMessage(MasterSecret masterSecret, + String from, + String to, + long sentTimeMillis, + Optional relay, + Optional body, + Optional group, + Optional> attachments) { this.headers = new PduHeaders(); this.body = new PduBody(); this.push = true; - if (messageContent.hasGroup()) { - this.groupId = GroupUtil.getEncodedId(messageContent.getGroup().getId().toByteArray()); + if (group.isPresent()) { + this.groupId = GroupUtil.getEncodedId(group.get().getGroupId()); } else { this.groupId = null; } - this.headers.setEncodedStringValue(new EncodedStringValue(message.getSource()), PduHeaders.FROM); - this.headers.appendEncodedStringValue(new EncodedStringValue(localNumber), PduHeaders.TO); - this.headers.setLongInteger(message.getTimestampMillis() / 1000, PduHeaders.DATE); + this.headers.setEncodedStringValue(new EncodedStringValue(from), PduHeaders.FROM); + this.headers.appendEncodedStringValue(new EncodedStringValue(to), PduHeaders.TO); + this.headers.setLongInteger(sentTimeMillis / 1000, PduHeaders.DATE); - if (!org.whispersystems.textsecure.util.Util.isEmpty(messageContent.getBody())) { + if (body.isPresent() && !org.whispersystems.textsecure.util.Util.isEmpty(body.get())) { PduPart text = new PduPart(); - text.setData(Util.toUtf8Bytes(messageContent.getBody())); + text.setData(Util.toUtf8Bytes(body.get())); text.setContentType(Util.toIsoBytes("text/plain")); text.setCharset(CharacterSets.UTF_8); - body.addPart(text); + this.body.addPart(text); } - if (messageContent.getAttachmentsCount() > 0) { - for (PushMessageContent.AttachmentPointer attachment : messageContent.getAttachmentsList()) { - PduPart media = new PduPart(); - byte[] encryptedKey = new MasterCipher(masterSecret).encryptBytes(attachment.getKey().toByteArray()); + if (attachments.isPresent()) { + for (TextSecureAttachment attachment : attachments.get()) { + 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.getId()))); - media.setContentDisposition(Util.toIsoBytes(Base64.encodeBytes(encryptedKey))); + media.setContentType(Util.toIsoBytes(attachment.getContentType())); + media.setContentLocation(Util.toIsoBytes(String.valueOf(attachment.asPointer().getId()))); + media.setContentDisposition(Util.toIsoBytes(Base64.encodeBytes(encryptedKey))); - if (message.getRelay() != null) { - media.setName(Util.toIsoBytes(message.getRelay())); + if (relay.isPresent()) { + media.setName(Util.toIsoBytes(relay.get())); + } + + media.setPendingPush(true); + + this.body.addPart(media); } - - media.setPendingPush(true); - - body.addPart(media); } } } diff --git a/src/org/thoughtcrime/securesms/mms/MmsRadio.java b/src/org/thoughtcrime/securesms/mms/MmsRadio.java index c8e5de8563..0b13cbf6ca 100644 --- a/src/org/thoughtcrime/securesms/mms/MmsRadio.java +++ b/src/org/thoughtcrime/securesms/mms/MmsRadio.java @@ -99,6 +99,8 @@ public class MmsRadio { private boolean isConnected() { NetworkInfo info = connectivityManager.getNetworkInfo(TYPE_MOBILE_MMS); + Log.w("MmsRadio", "Connected: " + info); + if ((info == null) || (info.getType() != TYPE_MOBILE_MMS) || !info.isConnected()) return false; diff --git a/src/org/thoughtcrime/securesms/mms/Slide.java b/src/org/thoughtcrime/securesms/mms/Slide.java index 56c9c23718..524083e951 100644 --- a/src/org/thoughtcrime/securesms/mms/Slide.java +++ b/src/org/thoughtcrime/securesms/mms/Slide.java @@ -23,7 +23,7 @@ import java.io.InputStream; import org.w3c.dom.smil.SMILDocument; import org.w3c.dom.smil.SMILMediaElement; import org.w3c.dom.smil.SMILRegionElement; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.providers.PartProvider; diff --git a/src/org/thoughtcrime/securesms/mms/SlideDeck.java b/src/org/thoughtcrime/securesms/mms/SlideDeck.java index b2120a8d25..e9cb21c7bf 100644 --- a/src/org/thoughtcrime/securesms/mms/SlideDeck.java +++ b/src/org/thoughtcrime/securesms/mms/SlideDeck.java @@ -20,7 +20,7 @@ import android.content.Context; import org.thoughtcrime.securesms.dom.smil.parser.SmilXmlSerializer; import org.thoughtcrime.securesms.util.SmilUtil; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; import java.io.ByteArrayOutputStream; import java.io.UnsupportedEncodingException; diff --git a/src/org/thoughtcrime/securesms/mms/TextSlide.java b/src/org/thoughtcrime/securesms/mms/TextSlide.java index 8b3721e4a6..70c21d0f1a 100644 --- a/src/org/thoughtcrime/securesms/mms/TextSlide.java +++ b/src/org/thoughtcrime/securesms/mms/TextSlide.java @@ -24,7 +24,7 @@ import org.thoughtcrime.securesms.util.SmilUtil; import org.w3c.dom.smil.SMILDocument; import org.w3c.dom.smil.SMILMediaElement; import org.w3c.dom.smil.SMILRegionElement; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.util.LRUCache; import java.io.UnsupportedEncodingException; diff --git a/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java b/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java index 2a30c4d8b5..4338a7a42c 100644 --- a/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java +++ b/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java @@ -7,7 +7,7 @@ import android.content.Intent; import android.os.AsyncTask; import android.util.Log; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.DatabaseFactory; public class MarkReadReceiver extends BroadcastReceiver { diff --git a/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java b/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java index 44145dda6b..aef3c411bc 100644 --- a/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java +++ b/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java @@ -39,11 +39,10 @@ import android.util.Log; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.RoutingActivity; -import org.thoughtcrime.securesms.contacts.ContactPhotoFactory; import org.thoughtcrime.securesms.database.PushDatabase; import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.RecipientFormattingException; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MmsSmsDatabase; import org.thoughtcrime.securesms.database.model.MessageRecord; diff --git a/src/org/thoughtcrime/securesms/notifications/NotificationState.java b/src/org/thoughtcrime/securesms/notifications/NotificationState.java index fe94d63549..36db7a9f38 100644 --- a/src/org/thoughtcrime/securesms/notifications/NotificationState.java +++ b/src/org/thoughtcrime/securesms/notifications/NotificationState.java @@ -6,7 +6,7 @@ import android.content.Intent; import android.graphics.Bitmap; import android.util.Log; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; import java.util.HashSet; import java.util.LinkedList; diff --git a/src/org/thoughtcrime/securesms/providers/PartProvider.java b/src/org/thoughtcrime/securesms/providers/PartProvider.java index d1e364413c..3c3be5a263 100644 --- a/src/org/thoughtcrime/securesms/providers/PartProvider.java +++ b/src/org/thoughtcrime/securesms/providers/PartProvider.java @@ -31,7 +31,7 @@ import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.util.Log; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.service.KeyCachingService; @@ -144,23 +144,9 @@ public class PartProvider extends ContentProvider { IntentFilter filter = new IntentFilter(KeyCachingService.NEW_KEY_EVENT); getContext().registerReceiver(receiver, filter, KeyCachingService.KEY_PERMISSION, null); - Intent bindIntent = new Intent(getContext(), KeyCachingService.class); - getContext().bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE); + initializeWithMasterSecret(KeyCachingService.getMasterSecret(getContext())); } - private ServiceConnection serviceConnection = new ServiceConnection() { - public void onServiceConnected(ComponentName className, IBinder service) { - KeyCachingService keyCachingService = ((KeyCachingService.KeyCachingBinder)service).getService(); - MasterSecret masterSecret = keyCachingService.getMasterSecret(); - - initializeWithMasterSecret(masterSecret); - - PartProvider.this.getContext().unbindService(this); - } - - public void onServiceDisconnected(ComponentName name) {} - }; - private class NewKeyReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { diff --git a/src/org/thoughtcrime/securesms/push/TextSecureMessageReceiverFactory.java b/src/org/thoughtcrime/securesms/push/TextSecureMessageReceiverFactory.java new file mode 100644 index 0000000000..73a20fb941 --- /dev/null +++ b/src/org/thoughtcrime/securesms/push/TextSecureMessageReceiverFactory.java @@ -0,0 +1,21 @@ +package org.thoughtcrime.securesms.push; + +import android.content.Context; + +import org.thoughtcrime.securesms.Release; +import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.storage.TextSecureAxolotlStore; +import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.whispersystems.textsecure.api.TextSecureMessageReceiver; + +public class TextSecureMessageReceiverFactory { + public static TextSecureMessageReceiver create(Context context, MasterSecret masterSecret) { + return new TextSecureMessageReceiver(context, + TextSecurePreferences.getSignalingKey(context), + Release.PUSH_URL, + new TextSecurePushTrustStore(context), + TextSecurePreferences.getLocalNumber(context), + TextSecurePreferences.getPushServerPassword(context), + new TextSecureAxolotlStore(context, masterSecret)); + } +} diff --git a/src/org/thoughtcrime/securesms/push/TextSecureMessageSenderFactory.java b/src/org/thoughtcrime/securesms/push/TextSecureMessageSenderFactory.java new file mode 100644 index 0000000000..6019bc93a6 --- /dev/null +++ b/src/org/thoughtcrime/securesms/push/TextSecureMessageSenderFactory.java @@ -0,0 +1,43 @@ +package org.thoughtcrime.securesms.push; + +import android.content.Context; + +import org.thoughtcrime.securesms.Release; +import org.thoughtcrime.securesms.crypto.SecurityEvent; +import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.storage.TextSecureAxolotlStore; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.recipients.RecipientFactory; +import org.thoughtcrime.securesms.recipients.Recipients; +import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.whispersystems.libaxolotl.util.guava.Optional; +import org.whispersystems.textsecure.api.TextSecureMessageSender; + +import static org.whispersystems.textsecure.api.TextSecureMessageSender.EventListener; + +public class TextSecureMessageSenderFactory { + public static TextSecureMessageSender create(Context context, MasterSecret masterSecret) { + return new TextSecureMessageSender(context, Release.PUSH_URL, + new TextSecurePushTrustStore(context), + TextSecurePreferences.getLocalNumber(context), + TextSecurePreferences.getPushServerPassword(context), + new TextSecureAxolotlStore(context, masterSecret), + Optional.of((EventListener)new SecurityEventListener(context))); + } + + private static class SecurityEventListener implements EventListener { + + private final Context context; + + public SecurityEventListener(Context context) { + this.context = context.getApplicationContext(); + } + + @Override + public void onSecurityEvent(long recipientId) { + Recipients recipients = RecipientFactory.getRecipientsForIds(context, String.valueOf(recipientId), false); + long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients); + SecurityEvent.broadcastSecurityUpdateEvent(context, threadId); + } + } +} diff --git a/src/org/thoughtcrime/securesms/service/ApplicationMigrationService.java b/src/org/thoughtcrime/securesms/service/ApplicationMigrationService.java index f91af8cf3a..3b1ada766e 100644 --- a/src/org/thoughtcrime/securesms/service/ApplicationMigrationService.java +++ b/src/org/thoughtcrime/securesms/service/ApplicationMigrationService.java @@ -19,7 +19,7 @@ import android.util.Log; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.RoutingActivity; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.SmsMigrator; import org.thoughtcrime.securesms.database.SmsMigrator.ProgressDescription; diff --git a/src/org/thoughtcrime/securesms/service/AvatarDownloader.java b/src/org/thoughtcrime/securesms/service/AvatarDownloader.java deleted file mode 100644 index 050bcc3bd0..0000000000 --- a/src/org/thoughtcrime/securesms/service/AvatarDownloader.java +++ /dev/null @@ -1,85 +0,0 @@ -package org.thoughtcrime.securesms.service; - -import android.content.Context; -import android.content.Intent; -import android.graphics.Bitmap; -import android.util.Log; - -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.GroupDatabase; -import org.thoughtcrime.securesms.database.PartDatabase; -import org.thoughtcrime.securesms.push.PushServiceSocketFactory; -import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientFactory; -import org.thoughtcrime.securesms.recipients.RecipientFormattingException; -import org.thoughtcrime.securesms.util.BitmapDecodingException; -import org.thoughtcrime.securesms.util.BitmapUtil; -import org.thoughtcrime.securesms.util.GroupUtil; -import org.whispersystems.libaxolotl.InvalidMessageException; -import org.whispersystems.textsecure.crypto.AttachmentCipherInputStream; -import org.whispersystems.textsecure.crypto.MasterSecret; -import org.whispersystems.textsecure.push.PushServiceSocket; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; - -public class AvatarDownloader { - - private final Context context; - - public AvatarDownloader(Context context) { - this.context = context.getApplicationContext(); - } - - public void process(MasterSecret masterSecret, Intent intent) { - try { - if (!SendReceiveService.DOWNLOAD_AVATAR_ACTION.equals(intent.getAction())) - return; - - byte[] groupId = intent.getByteArrayExtra("group_id"); - GroupDatabase database = DatabaseFactory.getGroupDatabase(context); - GroupDatabase.GroupRecord record = database.getGroup(groupId); - - if (record != null) { - long avatarId = record.getAvatarId(); - byte[] key = record.getAvatarKey(); - String relay = record.getRelay(); - - if (avatarId == -1 || key == null) { - return; - } - - File attachment = downloadAttachment(relay, avatarId); - InputStream scaleInputStream = new AttachmentCipherInputStream(attachment, key); - InputStream measureInputStream = new AttachmentCipherInputStream(attachment, key); - Bitmap avatar = BitmapUtil.createScaledBitmap(measureInputStream, scaleInputStream, 500, 500); - - database.updateAvatar(groupId, avatar); - - try { - Recipient groupRecipient = RecipientFactory.getRecipientsFromString(context, GroupUtil.getEncodedId(groupId), true) - .getPrimaryRecipient(); - groupRecipient.setContactPhoto(avatar); - } catch (RecipientFormattingException e) { - Log.w("AvatarDownloader", e); - } - -// avatar.recycle(); - attachment.delete(); - } - } catch (IOException e) { - Log.w("AvatarDownloader", e); - } catch (InvalidMessageException e) { - Log.w("AvatarDownloader", e); - } catch (BitmapDecodingException e) { - Log.w("AvatarDownloader", e); - } - } - - private File downloadAttachment(String relay, long contentLocation) throws IOException { - PushServiceSocket socket = PushServiceSocketFactory.create(context); - return socket.retrieveAttachment(relay, contentLocation); - } - -} diff --git a/src/org/thoughtcrime/securesms/service/GroupReceiver.java b/src/org/thoughtcrime/securesms/service/GroupReceiver.java deleted file mode 100644 index 12cd741861..0000000000 --- a/src/org/thoughtcrime/securesms/service/GroupReceiver.java +++ /dev/null @@ -1,161 +0,0 @@ -package org.thoughtcrime.securesms.service; - -import android.content.Context; -import android.content.Intent; -import android.util.Log; -import android.util.Pair; - -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; -import org.thoughtcrime.securesms.database.GroupDatabase; -import org.thoughtcrime.securesms.notifications.MessageNotifier; -import org.thoughtcrime.securesms.sms.IncomingGroupMessage; -import org.thoughtcrime.securesms.sms.IncomingTextMessage; -import org.whispersystems.textsecure.crypto.MasterSecret; -import org.whispersystems.textsecure.push.IncomingPushMessage; -import org.whispersystems.textsecure.util.Base64; - -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; - -import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord; -import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent; -import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext; - -public class GroupReceiver { - - private static final String TAG = GroupReceiver.class.getSimpleName();; - private final Context context; - - public GroupReceiver(Context context) { - this.context = context.getApplicationContext(); - } - - public void process(MasterSecret masterSecret, - IncomingPushMessage message, - PushMessageContent messageContent, - boolean secure) - { - if (!messageContent.getGroup().hasId()) { - Log.w(TAG, "Received group message with no id! Ignoring..."); - return; - } - - if (!secure) { - Log.w(TAG, "Received insecure group push action! Ignoring..."); - return; - } - - GroupDatabase database = DatabaseFactory.getGroupDatabase(context); - GroupContext group = messageContent.getGroup(); - byte[] id = group.getId().toByteArray(); - int type = group.getType().getNumber(); - GroupRecord record = database.getGroup(id); - - if (record != null && type == GroupContext.Type.UPDATE_VALUE) { - handleGroupUpdate(masterSecret, message, group, record); - } else if (record == null && type == GroupContext.Type.UPDATE_VALUE) { - handleGroupCreate(masterSecret, message, group); - } else if (record != null && type == GroupContext.Type.QUIT_VALUE) { - handleGroupLeave(masterSecret, message, group, record); - } else if (type == GroupContext.Type.UNKNOWN_VALUE) { - Log.w(TAG, "Received unknown type, ignoring..."); - } - } - - private void handleGroupCreate(MasterSecret masterSecret, - IncomingPushMessage message, - GroupContext group) - { - GroupDatabase database = DatabaseFactory.getGroupDatabase(context); - byte[] id = group.getId().toByteArray(); - - database.create(id, group.getName(), group.getMembersList(), - group.getAvatar(), message.getRelay()); - - storeMessage(masterSecret, message, group); - } - - private void handleGroupUpdate(MasterSecret masterSecret, - IncomingPushMessage message, - GroupContext group, - GroupRecord groupRecord) - { - - GroupDatabase database = DatabaseFactory.getGroupDatabase(context); - byte[] id = group.getId().toByteArray(); - - Set recordMembers = new HashSet(groupRecord.getMembers()); - Set messageMembers = new HashSet(group.getMembersList()); - - Set addedMembers = new HashSet(messageMembers); - addedMembers.removeAll(recordMembers); - - Set missingMembers = new HashSet(recordMembers); - missingMembers.removeAll(messageMembers); - - if (addedMembers.size() > 0) { - Set unionMembers = new HashSet(recordMembers); - unionMembers.addAll(messageMembers); - database.updateMembers(id, new LinkedList(unionMembers)); - - group = group.toBuilder().clearMembers().addAllMembers(addedMembers).build(); - } else { - group = group.toBuilder().clearMembers().build(); - } - - if (missingMembers.size() > 0) { - // TODO We should tell added and missing about each-other. - } - - if (group.hasName() || group.hasAvatar()) { - database.update(id, group.getName(), group.getAvatar()); - } - - if (group.hasName() && group.getName() != null && group.getName().equals(groupRecord.getTitle())) { - group = group.toBuilder().clearName().build(); - } - - if (!groupRecord.isActive()) database.setActive(id, true); - storeMessage(masterSecret, message, group); - } - - private void handleGroupLeave(MasterSecret masterSecret, - IncomingPushMessage message, - GroupContext group, - GroupRecord record) - { - GroupDatabase database = DatabaseFactory.getGroupDatabase(context); - byte[] id = group.getId().toByteArray(); - List members = record.getMembers(); - - if (members.contains(message.getSource())) { - database.remove(id, message.getSource()); - - storeMessage(masterSecret, message, group); - } - } - - - private void storeMessage(MasterSecret masterSecret, IncomingPushMessage message, GroupContext group) { - if (group.hasAvatar()) { - Intent intent = new Intent(context, SendReceiveService.class); - intent.setAction(SendReceiveService.DOWNLOAD_AVATAR_ACTION); - intent.putExtra("group_id", group.getId().toByteArray()); - context.startService(intent); - } - - EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context); - String body = Base64.encodeBytes(group.toByteArray()); - IncomingTextMessage incoming = new IncomingTextMessage(message, body, group); - IncomingGroupMessage groupMessage = new IncomingGroupMessage(incoming, group, body); - - Pair messageAndThreadId = smsDatabase.insertMessageInbox(masterSecret, groupMessage); - smsDatabase.updateMessageBody(masterSecret, messageAndThreadId.first, body); - - MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); - } - -} diff --git a/src/org/thoughtcrime/securesms/service/KeyCachingService.java b/src/org/thoughtcrime/securesms/service/KeyCachingService.java index 07388dd4a0..ab808ab3d6 100644 --- a/src/org/thoughtcrime/securesms/service/KeyCachingService.java +++ b/src/org/thoughtcrime/securesms/service/KeyCachingService.java @@ -32,16 +32,18 @@ import android.support.v4.app.NotificationCompat; import android.util.Log; import android.widget.RemoteViews; +import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.DatabaseUpgradeActivity; import org.thoughtcrime.securesms.DummyActivity; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.RoutingActivity; -import org.thoughtcrime.securesms.crypto.DecryptingQueue; import org.thoughtcrime.securesms.crypto.InvalidPassphraseException; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecretUtil; import org.thoughtcrime.securesms.notifications.MessageNotifier; +import org.thoughtcrime.securesms.util.ParcelUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.whispersystems.jobqueue.EncryptionKeys; /** * Small service that stays running to keep a key cached in memory. @@ -64,33 +66,51 @@ public class KeyCachingService extends Service { private PendingIntent pending; private int activitiesRunning = 0; - private final IBinder binder = new KeyCachingBinder(); + private final IBinder binder = new KeySetBinder(); - private MasterSecret masterSecret; + private static MasterSecret masterSecret; public KeyCachingService() {} - public synchronized MasterSecret getMasterSecret() { + public static synchronized MasterSecret getMasterSecret(Context context) { + if (masterSecret == null && TextSecurePreferences.isPasswordDisabled(context)) { + try { + MasterSecret masterSecret = MasterSecretUtil.getMasterSecret(context, MasterSecretUtil.UNENCRYPTED_PASSPHRASE); + Intent intent = new Intent(context, KeyCachingService.class); + + context.startService(intent); + + return masterSecret; + } catch (InvalidPassphraseException e) { + Log.w("KeyCachingService", e); + } + } + return masterSecret; } - public synchronized void setMasterSecret(final MasterSecret masterSecret) { - this.masterSecret = masterSecret; + public void setMasterSecret(final MasterSecret masterSecret) { + synchronized (KeyCachingService.class) { + KeyCachingService.masterSecret = masterSecret; - foregroundService(); - broadcastNewSecret(); - startTimeoutIfAppropriate(); + foregroundService(); + broadcastNewSecret(); + startTimeoutIfAppropriate(); - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - if (!DatabaseUpgradeActivity.isUpdate(KeyCachingService.this)) { - DecryptingQueue.schedulePendingDecrypts(KeyCachingService.this, masterSecret); - MessageNotifier.updateNotification(KeyCachingService.this, masterSecret); + new AsyncTask() { + @Override + protected Void doInBackground(Void... params) { + if (!DatabaseUpgradeActivity.isUpdate(KeyCachingService.this)) { +// DecryptingQueue.schedulePendingDecrypts(KeyCachingService.this, masterSecret); + ApplicationContext.getInstance(KeyCachingService.this) + .getJobManager() + .setEncryptionKeys(new EncryptionKeys(ParcelUtil.serialize(masterSecret))); + MessageNotifier.updateNotification(KeyCachingService.this, masterSecret); + } + return null; } - return null; - } - }.execute(); + }.execute(); + } } @Override @@ -287,7 +307,7 @@ public class KeyCachingService extends Service { return binder; } - public class KeyCachingBinder extends Binder { + public class KeySetBinder extends Binder { public KeyCachingService getService() { return KeyCachingService.this; } diff --git a/src/org/thoughtcrime/securesms/service/MmsListener.java b/src/org/thoughtcrime/securesms/service/MmsListener.java index 4ee08d7f76..b679d4c641 100644 --- a/src/org/thoughtcrime/securesms/service/MmsListener.java +++ b/src/org/thoughtcrime/securesms/service/MmsListener.java @@ -23,6 +23,8 @@ import android.os.Build; import android.provider.Telephony; import android.util.Log; +import org.thoughtcrime.securesms.ApplicationContext; +import org.thoughtcrime.securesms.jobs.MmsReceiveJob; import org.thoughtcrime.securesms.protocol.WirePrefix; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; @@ -80,11 +82,10 @@ public class MmsListener extends BroadcastReceiver { isRelevant(context, intent))) { Log.w("MmsListener", "Relevant!"); - intent.setAction(SendReceiveService.RECEIVE_MMS_ACTION); - intent.putExtra("ResultCode", this.getResultCode()); - intent.setClass(context, SendReceiveService.class); + ApplicationContext.getInstance(context) + .getJobManager() + .add(new MmsReceiveJob(context, intent.getByteArrayExtra("data"))); - context.startService(intent); abortBroadcast(); } } diff --git a/src/org/thoughtcrime/securesms/service/MmsReceiver.java b/src/org/thoughtcrime/securesms/service/MmsReceiver.java deleted file mode 100644 index 4a5d6a3069..0000000000 --- a/src/org/thoughtcrime/securesms/service/MmsReceiver.java +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Copyright (C) 2011 Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.thoughtcrime.securesms.service; - -import android.content.Context; -import android.content.Intent; -import android.util.Log; -import android.util.Pair; - -import org.whispersystems.textsecure.crypto.MasterSecret; -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.MmsDatabase; -import org.thoughtcrime.securesms.mms.IncomingMediaMessage; -import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.whispersystems.textsecure.push.IncomingPushMessage; -import org.whispersystems.textsecure.push.PushServiceSocket; - -import java.io.File; -import java.io.IOException; -import java.util.List; - -import ws.com.google.android.mms.MmsException; -import ws.com.google.android.mms.pdu.GenericPdu; -import ws.com.google.android.mms.pdu.NotificationInd; -import ws.com.google.android.mms.pdu.PduHeaders; -import ws.com.google.android.mms.pdu.PduParser; - -public class MmsReceiver { - - private final Context context; - - public MmsReceiver(Context context) { - this.context = context; - } - - public void process(MasterSecret masterSecret, Intent intent) { - if (intent.getAction().equals(SendReceiveService.RECEIVE_MMS_ACTION)) { - handleMmsNotification(intent); - } - } - - private void handleMmsNotification(Intent intent) { - byte[] mmsData = intent.getByteArrayExtra("data"); - PduParser parser = new PduParser(mmsData); - GenericPdu pdu = parser.parse(); - - if (pdu != null && pdu.getMessageType() == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND) { - MmsDatabase database = DatabaseFactory.getMmsDatabase(context); - Pair messageAndThreadId = database.insertMessageInbox((NotificationInd)pdu); - - Log.w("MmsReceiver", "Inserted received MMS notification..."); - scheduleDownload((NotificationInd)pdu, messageAndThreadId.first, messageAndThreadId.second); - } - } - - private void scheduleDownload(NotificationInd pdu, long messageId, long threadId) { - Intent intent = new Intent(SendReceiveService.DOWNLOAD_MMS_ACTION, null, context, SendReceiveService.class); - intent.putExtra("content_location", new String(pdu.getContentLocation())); - intent.putExtra("message_id", messageId); - intent.putExtra("transaction_id", pdu.getTransactionId()); - intent.putExtra("thread_id", threadId); - intent.putExtra("automatic", true); - - context.startService(intent); - } - -} diff --git a/src/org/thoughtcrime/securesms/service/MmsSender.java b/src/org/thoughtcrime/securesms/service/MmsSender.java index 5c4a96eabf..512220b997 100644 --- a/src/org/thoughtcrime/securesms/service/MmsSender.java +++ b/src/org/thoughtcrime/securesms/service/MmsSender.java @@ -36,8 +36,8 @@ import org.thoughtcrime.securesms.transport.RetryLaterException; import org.thoughtcrime.securesms.transport.SecureFallbackApprovalException; import org.thoughtcrime.securesms.transport.UndeliverableMessageException; import org.thoughtcrime.securesms.transport.UniversalTransport; -import org.thoughtcrime.securesms.transport.UntrustedIdentityException; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.whispersystems.textsecure.crypto.UntrustedIdentityException; +import org.thoughtcrime.securesms.crypto.MasterSecret; import ws.com.google.android.mms.MmsException; import ws.com.google.android.mms.pdu.SendReq; diff --git a/src/org/thoughtcrime/securesms/service/PreKeyService.java b/src/org/thoughtcrime/securesms/service/PreKeyService.java index 417210ffb7..7a58a179bf 100644 --- a/src/org/thoughtcrime/securesms/service/PreKeyService.java +++ b/src/org/thoughtcrime/securesms/service/PreKeyService.java @@ -14,11 +14,11 @@ import org.whispersystems.libaxolotl.InvalidKeyIdException; import org.whispersystems.libaxolotl.state.PreKeyRecord; import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; import org.whispersystems.libaxolotl.state.SignedPreKeyStore; -import org.whispersystems.textsecure.crypto.MasterSecret; -import org.whispersystems.textsecure.crypto.PreKeyUtil; +import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.PreKeyUtil; import org.whispersystems.textsecure.push.PushServiceSocket; import org.whispersystems.textsecure.push.SignedPreKeyEntity; -import org.whispersystems.textsecure.storage.TextSecurePreKeyStore; +import org.thoughtcrime.securesms.crypto.storage.TextSecurePreKeyStore; import java.io.IOException; import java.util.Arrays; diff --git a/src/org/thoughtcrime/securesms/service/PushDownloader.java b/src/org/thoughtcrime/securesms/service/PushDownloader.java deleted file mode 100644 index 262258a7be..0000000000 --- a/src/org/thoughtcrime/securesms/service/PushDownloader.java +++ /dev/null @@ -1,117 +0,0 @@ -package org.thoughtcrime.securesms.service; - - -import android.content.Context; -import android.content.Intent; -import android.util.Log; -import android.util.Pair; - -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.EncryptingPartDatabase; -import org.thoughtcrime.securesms.database.PartDatabase; -import org.thoughtcrime.securesms.push.PushServiceSocketFactory; -import org.thoughtcrime.securesms.util.Util; -import org.whispersystems.libaxolotl.InvalidMessageException; -import org.whispersystems.textsecure.crypto.AttachmentCipherInputStream; -import org.whispersystems.textsecure.crypto.MasterCipher; -import org.whispersystems.textsecure.crypto.MasterSecret; -import org.whispersystems.textsecure.push.exceptions.NotFoundException; -import org.whispersystems.textsecure.push.PushServiceSocket; -import org.whispersystems.textsecure.util.Base64; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.util.List; - -import ws.com.google.android.mms.MmsException; -import ws.com.google.android.mms.pdu.PduPart; - -public class PushDownloader { - - private final Context context; - - public PushDownloader(Context context) { - this.context = context.getApplicationContext(); - } - - public void process(MasterSecret masterSecret, Intent intent) { - if (!SendReceiveService.DOWNLOAD_PUSH_ACTION.equals(intent.getAction())) - return; - - long messageId = intent.getLongExtra("message_id", -1); - PartDatabase database = DatabaseFactory.getEncryptingPartDatabase(context, masterSecret); - - Log.w("PushDownloader", "Downloading push parts for: " + messageId); - - if (messageId != -1) { - List> parts = database.getParts(messageId, false); - - for (Pair partPair : parts) { - retrievePart(masterSecret, partPair.second, messageId, partPair.first); - Log.w("PushDownloader", "Got part: " + partPair.first); - } - } else { - List>> parts = database.getPushPendingParts(); - - for (Pair> partPair : parts) { - retrievePart(masterSecret, partPair.second.second, partPair.first, partPair.second.first); - Log.w("PushDownloader", "Got part: " + partPair.second.first); - } - } - } - - private void retrievePart(MasterSecret masterSecret, PduPart part, long messageId, long partId) { - EncryptingPartDatabase database = DatabaseFactory.getEncryptingPartDatabase(context, masterSecret); - File attachmentFile = null; - - try { - MasterCipher masterCipher = new MasterCipher(masterSecret); - long contentLocation = Long.parseLong(Util.toIsoString(part.getContentLocation())); - byte[] key = masterCipher.decryptBytes(Base64.decode(Util.toIsoString(part.getContentDisposition()))); - String relay = null; - - if (part.getName() != null) { - relay = Util.toIsoString(part.getName()); - } - - attachmentFile = downloadAttachment(relay, contentLocation); - InputStream attachmentInput = new AttachmentCipherInputStream(attachmentFile, key); - - database.updateDownloadedPart(messageId, partId, part, attachmentInput); - } catch (NotFoundException e) { - Log.w("PushDownloader", e); - try { - database.updateFailedDownloadedPart(messageId, partId, part); - } catch (MmsException mme) { - Log.w("PushDownloader", mme); - } - } catch (InvalidMessageException e) { - Log.w("PushDownloader", e); - try { - database.updateFailedDownloadedPart(messageId, partId, part); - } catch (MmsException mme) { - Log.w("PushDownloader", mme); - } - } catch (MmsException e) { - Log.w("PushDownloader", e); - try { - database.updateFailedDownloadedPart(messageId, partId, part); - } catch (MmsException mme) { - Log.w("PushDownloader", mme); - } - } catch (IOException e) { - Log.w("PushDownloader", e); - /// XXX schedule some kind of soft failure retry action - } finally { - if (attachmentFile != null) - attachmentFile.delete(); - } - } - - private File downloadAttachment(String relay, long contentLocation) throws IOException { - PushServiceSocket socket = PushServiceSocketFactory.create(context); - return socket.retrieveAttachment(relay, contentLocation); - } - -} diff --git a/src/org/thoughtcrime/securesms/service/PushReceiver.java b/src/org/thoughtcrime/securesms/service/PushReceiver.java deleted file mode 100644 index 13d5a55c54..0000000000 --- a/src/org/thoughtcrime/securesms/service/PushReceiver.java +++ /dev/null @@ -1,322 +0,0 @@ -package org.thoughtcrime.securesms.service; - -import android.content.Context; -import android.content.Intent; -import android.util.Log; -import android.util.Pair; - -import com.google.protobuf.InvalidProtocolBufferException; - -import org.thoughtcrime.securesms.crypto.DecryptingQueue; -import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor; -import org.thoughtcrime.securesms.crypto.TextSecureCipher; -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; -import org.thoughtcrime.securesms.database.MmsDatabase; -import org.thoughtcrime.securesms.mms.IncomingMediaMessage; -import org.thoughtcrime.securesms.notifications.MessageNotifier; -import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientFactory; -import org.thoughtcrime.securesms.recipients.RecipientFormattingException; -import org.thoughtcrime.securesms.recipients.Recipients; -import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage; -import org.thoughtcrime.securesms.sms.IncomingEndSessionMessage; -import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage; -import org.thoughtcrime.securesms.sms.IncomingPreKeyBundleMessage; -import org.thoughtcrime.securesms.sms.IncomingTextMessage; -import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.whispersystems.libaxolotl.DuplicateMessageException; -import org.whispersystems.libaxolotl.InvalidKeyException; -import org.whispersystems.libaxolotl.InvalidMessageException; -import org.whispersystems.libaxolotl.InvalidVersionException; -import org.whispersystems.libaxolotl.LegacyMessageException; -import org.whispersystems.libaxolotl.NoSessionException; -import org.whispersystems.libaxolotl.UntrustedIdentityException; -import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage; -import org.whispersystems.libaxolotl.state.SessionStore; -import org.whispersystems.textsecure.crypto.MasterSecret; -import org.whispersystems.textsecure.crypto.TransportDetails; -import org.whispersystems.textsecure.push.IncomingPushMessage; -import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent; -import org.whispersystems.libaxolotl.InvalidKeyIdException; -import org.whispersystems.textsecure.push.PushTransportDetails; -import org.whispersystems.textsecure.storage.RecipientDevice; -import org.whispersystems.textsecure.storage.TextSecureSessionStore; -import org.whispersystems.textsecure.util.Base64; - -import ws.com.google.android.mms.MmsException; - -import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.Type; - -public class PushReceiver { - - private static final String TAG = PushReceiver.class.getSimpleName(); - - public static final int RESULT_OK = 0; - public static final int RESULT_NO_SESSION = 1; - public static final int RESULT_DECRYPT_FAILED = 2; - public static final int RESULT_DECRYPT_DUPLICATE = 3; - - private final Context context; - private final GroupReceiver groupReceiver; - - public PushReceiver(Context context) { - this.context = context.getApplicationContext(); - this.groupReceiver = new GroupReceiver(context); - } - - public void process(MasterSecret masterSecret, Intent intent) { - if (SendReceiveService.RECEIVE_PUSH_ACTION.equals(intent.getAction())) { - handleMessage(masterSecret, intent); - } else if (SendReceiveService.DECRYPTED_PUSH_ACTION.equals(intent.getAction())) { - handleDecrypt(masterSecret, intent); - } - } - - private void handleDecrypt(MasterSecret masterSecret, Intent intent) { - IncomingPushMessage message = intent.getParcelableExtra("message"); - long messageId = intent.getLongExtra("message_id", -1); - int result = intent.getIntExtra("result", 0); - - if (result == RESULT_OK) handleReceivedMessage(masterSecret, message, true); - else if (result == RESULT_NO_SESSION) handleReceivedMessageForNoSession(masterSecret, message); - else if (result == RESULT_DECRYPT_FAILED) handleReceivedCorruptedMessage(masterSecret, message, true); - else if (result == RESULT_DECRYPT_DUPLICATE) handleReceivedDuplicateMessage(message); - - DatabaseFactory.getPushDatabase(context).delete(messageId); - } - - private void handleMessage(MasterSecret masterSecret, Intent intent) { - if (intent.getExtras() == null) { - return; - } - - IncomingPushMessage message = intent.getExtras().getParcelable("message"); - - if (message == null) { - return; - } - - if (message.isSecureMessage()) handleReceivedSecureMessage(masterSecret, message); - else if (message.isPreKeyBundle()) handleReceivedPreKeyBundle(masterSecret, message); - else if (message.isReceipt()) handleReceivedReceipt(message); - else if (message.isPlaintext()) handleReceivedMessage(masterSecret, message, false); - else Log.w(TAG, "Received push of unknown type!"); - } - - private void handleReceivedSecureMessage(MasterSecret masterSecret, IncomingPushMessage message) { - long id = DatabaseFactory.getPushDatabase(context).insert(message); - - if (masterSecret != null) { - DecryptingQueue.scheduleDecryption(context, masterSecret, id, message); - } else { - Recipients recipients = RecipientFactory.getRecipientsFromMessage(context, message, false); - long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients); - MessageNotifier.updateNotification(context, null, threadId); - } - } - - private void handleReceivedPreKeyBundle(MasterSecret masterSecret, IncomingPushMessage message) { - if (masterSecret == null) { - handleReceivedSecureMessage(null, message); - return; - } - - try { - Recipient recipient = RecipientFactory.getRecipientsFromString(context, message.getSource(), false).getPrimaryRecipient(); - RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), message.getSourceDevice()); - PreKeyWhisperMessage preKeyWhisperMessage = new PreKeyWhisperMessage(message.getBody()); - TransportDetails transportDetails = new PushTransportDetails(preKeyWhisperMessage.getMessageVersion()); - TextSecureCipher cipher = new TextSecureCipher(context, masterSecret, recipientDevice, transportDetails); - byte[] plaintext = cipher.decrypt(preKeyWhisperMessage); - - IncomingPushMessage bundledMessage = message.withBody(plaintext); - handleReceivedMessage(masterSecret, bundledMessage, true); - - } catch (InvalidVersionException e) { - Log.w(TAG, e); - handleReceivedCorruptedKey(masterSecret, message, true); - } catch (InvalidKeyException | InvalidKeyIdException | InvalidMessageException | - RecipientFormattingException | LegacyMessageException e) - { - Log.w(TAG, e); - handleReceivedCorruptedKey(masterSecret, message, false); - } catch (DuplicateMessageException e) { - Log.w(TAG, e); - handleReceivedDuplicateMessage(message); - } catch (NoSessionException e) { - Log.w(TAG, e); - handleReceivedMessageForNoSession(masterSecret, message); - } catch (UntrustedIdentityException e) { - Log.w(TAG, e); - String encoded = Base64.encodeBytes(message.getBody()); - IncomingTextMessage textMessage = new IncomingTextMessage(message, encoded, null); - IncomingPreKeyBundleMessage bundleMessage = new IncomingPreKeyBundleMessage(textMessage, encoded); - Pair messageAndThreadId = DatabaseFactory.getEncryptingSmsDatabase(context) - .insertMessageInbox(masterSecret, bundleMessage); - - MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); - } - } - - private void handleReceivedMessage(MasterSecret masterSecret, - IncomingPushMessage message, - boolean secure) - { - try { - PushMessageContent messageContent = PushMessageContent.parseFrom(message.getBody()); - - if (secure && (messageContent.getFlags() & PushMessageContent.Flags.END_SESSION_VALUE) != 0) { - Log.w(TAG, "Received end session message..."); - handleEndSessionMessage(masterSecret, message, messageContent); - } else if (messageContent.hasGroup() && messageContent.getGroup().getType().getNumber() != Type.DELIVER_VALUE) { - Log.w(TAG, "Received push group message..."); - groupReceiver.process(masterSecret, message, messageContent, secure); - } else if (messageContent.getAttachmentsCount() > 0) { - Log.w(TAG, "Received push media message..."); - handleReceivedMediaMessage(masterSecret, message, messageContent, secure); - } else { - Log.w(TAG, "Received push text message..."); - handleReceivedTextMessage(masterSecret, message, messageContent, secure); - } - } catch (InvalidProtocolBufferException e) { - Log.w(TAG, e); - handleReceivedCorruptedMessage(masterSecret, message, secure); - } - } - - private void handleReceivedReceipt(IncomingPushMessage message) - { - Log.w("PushReceiver", String.format("Received receipt: (XXXXX, %d)", message.getTimestampMillis())); - DatabaseFactory.getMmsSmsDatabase(context).incrementDeliveryReceiptCount(message.getSource(), - message.getTimestampMillis()); - } - - private void handleEndSessionMessage(MasterSecret masterSecret, - IncomingPushMessage message, - PushMessageContent messageContent) - { - try { - Recipient recipient = RecipientFactory.getRecipientsFromString(context, message.getSource(), true).getPrimaryRecipient(); - IncomingTextMessage incomingTextMessage = new IncomingTextMessage(message, "", null); - IncomingEndSessionMessage incomingEndSessionMessage = new IncomingEndSessionMessage(incomingTextMessage); - - EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); - - Pair messageAndThreadId = database.insertMessageInbox(masterSecret, incomingEndSessionMessage); - database.updateMessageBody(masterSecret, messageAndThreadId.first, messageContent.getBody()); - - SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret); - sessionStore.deleteAllSessions(recipient.getRecipientId()); - - KeyExchangeProcessor.broadcastSecurityUpdateEvent(context, messageAndThreadId.second); - } catch (RecipientFormattingException e) { - Log.w(TAG, e); - } - } - - private void handleReceivedMediaMessage(MasterSecret masterSecret, - IncomingPushMessage message, - PushMessageContent messageContent, - boolean secure) - { - - try { - String localNumber = TextSecurePreferences.getLocalNumber(context); - MmsDatabase database = DatabaseFactory.getMmsDatabase(context); - IncomingMediaMessage mediaMessage = new IncomingMediaMessage(masterSecret, localNumber, - message, messageContent); - - Pair messageAndThreadId; - - if (secure) { - messageAndThreadId = database.insertSecureDecryptedMessageInbox(masterSecret, mediaMessage, -1); - } else { - messageAndThreadId = database.insertMessageInbox(masterSecret, mediaMessage, null, -1); - } - - Intent intent = new Intent(context, SendReceiveService.class); - intent.setAction(SendReceiveService.DOWNLOAD_PUSH_ACTION); - intent.putExtra("message_id", messageAndThreadId.first); - context.startService(intent); - - MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); - } catch (MmsException e) { - Log.w(TAG, e); - // XXX - } - } - - private void handleReceivedTextMessage(MasterSecret masterSecret, - IncomingPushMessage message, - PushMessageContent messageContent, - boolean secure) - { - EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); - IncomingTextMessage textMessage = new IncomingTextMessage(message, "", - messageContent.hasGroup() ? - messageContent.getGroup() : null); - - if (secure) { - textMessage = new IncomingEncryptedMessage(textMessage, ""); - } - - Pair messageAndThreadId = database.insertMessageInbox(masterSecret, textMessage); - database.updateMessageBody(masterSecret, messageAndThreadId.first, messageContent.getBody()); - - MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); - } - - private void handleReceivedCorruptedMessage(MasterSecret masterSecret, - IncomingPushMessage message, - boolean secure) - { - Pair messageAndThreadId = insertMessagePlaceholder(masterSecret, message, secure); - DatabaseFactory.getEncryptingSmsDatabase(context).markAsDecryptFailed(messageAndThreadId.first); - - MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); - } - - private void handleReceivedDuplicateMessage(IncomingPushMessage message) { - Log.w(TAG, "Received duplicate message: " + message.getSource() + " , " + message.getSourceDevice()); - } - - private void handleReceivedCorruptedKey(MasterSecret masterSecret, - IncomingPushMessage message, - boolean invalidVersion) - { - IncomingTextMessage corruptedMessage = new IncomingTextMessage(message, "", null); - IncomingKeyExchangeMessage corruptedKeyMessage = new IncomingKeyExchangeMessage(corruptedMessage, ""); - - if (!invalidVersion) corruptedKeyMessage.setCorrupted(true); - else corruptedKeyMessage.setInvalidVersion(true); - - Pair messageAndThreadId = DatabaseFactory.getEncryptingSmsDatabase(context) - .insertMessageInbox(masterSecret, - corruptedKeyMessage); - - MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); - } - - private void handleReceivedMessageForNoSession(MasterSecret masterSecret, - IncomingPushMessage message) - { - Pair messageAndThreadId = insertMessagePlaceholder(masterSecret, message, true); - DatabaseFactory.getEncryptingSmsDatabase(context).markAsNoSession(messageAndThreadId.first); - MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); - } - - private Pair insertMessagePlaceholder(MasterSecret masterSecret, - IncomingPushMessage message, - boolean secure) - { - IncomingTextMessage placeholder = new IncomingTextMessage(message, "", null); - - if (secure) { - placeholder = new IncomingEncryptedMessage(placeholder, ""); - } - - return DatabaseFactory.getEncryptingSmsDatabase(context) - .insertMessageInbox(masterSecret, placeholder); - } -} diff --git a/src/org/thoughtcrime/securesms/service/RegistrationService.java b/src/org/thoughtcrime/securesms/service/RegistrationService.java index c084654951..56916edcdf 100644 --- a/src/org/thoughtcrime/securesms/service/RegistrationService.java +++ b/src/org/thoughtcrime/securesms/service/RegistrationService.java @@ -21,8 +21,8 @@ import org.whispersystems.libaxolotl.IdentityKeyPair; import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; import org.whispersystems.libaxolotl.state.PreKeyRecord; import org.whispersystems.libaxolotl.util.KeyHelper; -import org.whispersystems.textsecure.crypto.MasterSecret; -import org.whispersystems.textsecure.crypto.PreKeyUtil; +import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.PreKeyUtil; import org.whispersystems.textsecure.push.exceptions.ExpectationFailedException; import org.whispersystems.textsecure.push.PushServiceSocket; import org.whispersystems.textsecure.util.Util; diff --git a/src/org/thoughtcrime/securesms/service/SendReceiveService.java b/src/org/thoughtcrime/securesms/service/SendReceiveService.java index a1ba4b0555..b4bc8b9445 100644 --- a/src/org/thoughtcrime/securesms/service/SendReceiveService.java +++ b/src/org/thoughtcrime/securesms/service/SendReceiveService.java @@ -34,7 +34,7 @@ import org.thoughtcrime.securesms.crypto.MasterSecretUtil; import org.thoughtcrime.securesms.database.CanonicalSessionMigrator; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.WorkerThread; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecret; import java.util.Iterator; import java.util.LinkedList; @@ -51,40 +51,40 @@ public class SendReceiveService extends Service { public static final String SEND_SMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.SEND_SMS_ACTION"; public static final String SENT_SMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.SENT_SMS_ACTION"; public static final String DELIVERED_SMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DELIVERED_SMS_ACTION"; - public static final String RECEIVE_SMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.RECEIVE_SMS_ACTION"; +// public static final String RECEIVE_SMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.RECEIVE_SMS_ACTION"; public static final String SEND_MMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.SEND_MMS_ACTION"; - public static final String RECEIVE_MMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.RECEIVE_MMS_ACTION"; - public static final String DOWNLOAD_MMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DOWNLOAD_MMS_ACTION"; - public static final String DOWNLOAD_MMS_CONNECTIVITY_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DOWNLOAD_MMS_CONNECTIVITY_ACTION"; - public static final String DOWNLOAD_MMS_PENDING_APN_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DOWNLOAD_MMS_PENDING_APN_ACTION"; - public static final String RECEIVE_PUSH_ACTION = "org.thoughtcrime.securesms.SendReceiveService.RECEIVE_PUSH_ACTION"; - public static final String DECRYPTED_PUSH_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DECRYPTED_PUSH_ACTION"; - public static final String DOWNLOAD_PUSH_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DOWNLOAD_PUSH_ACTION"; - public static final String DOWNLOAD_AVATAR_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DOWNLOAD_AVATAR_ACTION"; +// public static final String RECEIVE_MMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.RECEIVE_MMS_ACTION"; +// public static final String DOWNLOAD_MMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DOWNLOAD_MMS_ACTION"; +// public static final String DOWNLOAD_MMS_CONNECTIVITY_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DOWNLOAD_MMS_CONNECTIVITY_ACTION"; +// public static final String DOWNLOAD_MMS_PENDING_APN_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DOWNLOAD_MMS_PENDING_APN_ACTION"; +// public static final String RECEIVE_PUSH_ACTION = "org.thoughtcrime.securesms.SendReceiveService.RECEIVE_PUSH_ACTION"; +// public static final String DECRYPTED_PUSH_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DECRYPTED_PUSH_ACTION"; +// public static final String DOWNLOAD_PUSH_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DOWNLOAD_PUSH_ACTION"; +// public static final String DOWNLOAD_AVATAR_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DOWNLOAD_AVATAR_ACTION"; public static final String MASTER_SECRET_EXTRA = "master_secret"; private static final int SEND_SMS = 0; - private static final int RECEIVE_SMS = 1; +// private static final int RECEIVE_SMS = 1; private static final int SEND_MMS = 2; - private static final int RECEIVE_MMS = 3; - private static final int DOWNLOAD_MMS = 4; - private static final int DOWNLOAD_MMS_PENDING = 5; - private static final int RECEIVE_PUSH = 6; - private static final int DOWNLOAD_PUSH = 7; - private static final int DOWNLOAD_AVATAR = 8; +// private static final int RECEIVE_MMS = 3; +// private static final int DOWNLOAD_MMS = 4; +// private static final int DOWNLOAD_MMS_PENDING = 5; +// private static final int RECEIVE_PUSH = 6; +// private static final int DOWNLOAD_PUSH = 7; +// private static final int DOWNLOAD_AVATAR = 8; private ToastHandler toastHandler; private SystemStateListener systemStateListener; - private SmsReceiver smsReceiver; +// private MmsReceiver mmsReceiver; + // private SmsReceiver smsReceiver; private SmsSender smsSender; - private MmsReceiver mmsReceiver; private MmsSender mmsSender; - private MmsDownloader mmsDownloader; - private PushReceiver pushReceiver; - private PushDownloader pushDownloader; - private AvatarDownloader avatarDownloader; +// private MmsDownloader mmsDownloader; +// private PushReceiver pushReceiver; +// private PushDownloader pushDownloader; +// private AvatarDownloader avatarDownloader; private MasterSecret masterSecret; private boolean hasSecret; @@ -111,28 +111,28 @@ public class SendReceiveService extends Service { if (action.equals(SEND_SMS_ACTION)) scheduleSecretRequiredIntent(SEND_SMS, intent); - else if (action.equals(RECEIVE_SMS_ACTION)) - scheduleIntent(RECEIVE_SMS, intent); +// else if (action.equals(RECEIVE_SMS_ACTION)) +// scheduleIntent(RECEIVE_SMS, intent); else if (action.equals(SENT_SMS_ACTION)) scheduleIntent(SEND_SMS, intent); else if (action.equals(DELIVERED_SMS_ACTION)) scheduleIntent(SEND_SMS, intent); else if (action.equals(SEND_MMS_ACTION)) scheduleSecretRequiredIntent(SEND_MMS, intent); - else if (action.equals(RECEIVE_MMS_ACTION)) - scheduleIntent(RECEIVE_MMS, intent); - else if (action.equals(DOWNLOAD_MMS_ACTION)) - scheduleSecretRequiredIntent(DOWNLOAD_MMS, intent); - else if (intent.getAction().equals(DOWNLOAD_MMS_PENDING_APN_ACTION)) - scheduleSecretRequiredIntent(DOWNLOAD_MMS_PENDING, intent); - else if (action.equals(RECEIVE_PUSH_ACTION)) - scheduleIntent(RECEIVE_PUSH, intent); - else if (action.equals(DECRYPTED_PUSH_ACTION)) - scheduleSecretRequiredIntent(RECEIVE_PUSH, intent); - else if (action.equals(DOWNLOAD_PUSH_ACTION)) - scheduleSecretRequiredIntent(DOWNLOAD_PUSH, intent); - else if (action.equals(DOWNLOAD_AVATAR_ACTION)) - scheduleIntent(DOWNLOAD_AVATAR, intent); +// else if (action.equals(RECEIVE_MMS_ACTION)) +// scheduleIntent(RECEIVE_MMS, intent); +// else if (action.equals(DOWNLOAD_MMS_ACTION)) +// scheduleSecretRequiredIntent(DOWNLOAD_MMS, intent); +// else if (intent.getAction().equals(DOWNLOAD_MMS_PENDING_APN_ACTION)) +// scheduleSecretRequiredIntent(DOWNLOAD_MMS_PENDING, intent); +// else if (action.equals(RECEIVE_PUSH_ACTION)) +// scheduleIntent(RECEIVE_PUSH, intent); +// else if (action.equals(DECRYPTED_PUSH_ACTION)) +// scheduleSecretRequiredIntent(RECEIVE_PUSH, intent); +// else if (action.equals(DOWNLOAD_PUSH_ACTION)) +// scheduleSecretRequiredIntent(DOWNLOAD_PUSH, intent); +// else if (action.equals(DOWNLOAD_AVATAR_ACTION)) +// scheduleIntent(DOWNLOAD_AVATAR, intent); else Log.w("SendReceiveService", "Received intent with unknown action: " + intent.getAction()); } @@ -160,14 +160,14 @@ public class SendReceiveService extends Service { } private void initializeProcessors() { - smsReceiver = new SmsReceiver(this); +// smsReceiver = new SmsReceiver(this); smsSender = new SmsSender(this, systemStateListener, toastHandler); - mmsReceiver = new MmsReceiver(this); +// mmsReceiver = new MmsReceiver(this); mmsSender = new MmsSender(this, systemStateListener, toastHandler); - mmsDownloader = new MmsDownloader(this, toastHandler); - pushReceiver = new PushReceiver(this); - pushDownloader = new PushDownloader(this); - avatarDownloader = new AvatarDownloader(this); +// mmsDownloader = new MmsDownloader(this, toastHandler); +// pushReceiver = new PushReceiver(this); +// pushDownloader = new PushDownloader(this); +// avatarDownloader = new AvatarDownloader(this); } private void initializeWorkQueue() { @@ -189,9 +189,10 @@ public class SendReceiveService extends Service { IntentFilter clearKeyFilter = new IntentFilter(KeyCachingService.CLEAR_KEY_EVENT); registerReceiver(clearKeyReceiver, clearKeyFilter, KeyCachingService.KEY_PERMISSION, null); - Intent bindIntent = new Intent(this, KeyCachingService.class); - startService(bindIntent); - bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE); + initializeWithMasterSecret(KeyCachingService.getMasterSecret(this)); +// Intent bindIntent = new Intent(this, KeyCachingService.class); +// startService(bindIntent); +// bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE); } private void initializeWithMasterSecret(MasterSecret masterSecret) { @@ -269,15 +270,15 @@ public class SendReceiveService extends Service { } switch (what) { - case RECEIVE_SMS: smsReceiver.process(masterSecret, intent); return; +// case RECEIVE_SMS: smsReceiver.process(masterSecret, intent); return; case SEND_SMS: smsSender.process(masterSecret, intent); return; - case RECEIVE_MMS: mmsReceiver.process(masterSecret, intent); return; +// case RECEIVE_MMS: mmsReceiver.process(masterSecret, intent); return; case SEND_MMS: mmsSender.process(masterSecret, intent); return; - case DOWNLOAD_MMS: mmsDownloader.process(masterSecret, intent); return; - case DOWNLOAD_MMS_PENDING: mmsDownloader.process(masterSecret, intent); return; - case RECEIVE_PUSH: pushReceiver.process(masterSecret, intent); return; - case DOWNLOAD_PUSH: pushDownloader.process(masterSecret, intent); return; - case DOWNLOAD_AVATAR: avatarDownloader.process(masterSecret, intent); return; +// case DOWNLOAD_MMS: mmsDownloader.process(masterSecret, intent); return; +// case DOWNLOAD_MMS_PENDING: mmsDownloader.process(masterSecret, intent); return; +// case RECEIVE_PUSH: pushReceiver.process(masterSecret, intent); return; +// case DOWNLOAD_PUSH: pushDownloader.process(masterSecret, intent); return; +// case DOWNLOAD_AVATAR: avatarDownloader.process(masterSecret, intent); return; } } } @@ -294,20 +295,20 @@ public class SendReceiveService extends Service { } } - private ServiceConnection serviceConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName className, IBinder service) { - KeyCachingService keyCachingService = ((KeyCachingService.KeyCachingBinder)service).getService(); - MasterSecret masterSecret = keyCachingService.getMasterSecret(); - - initializeWithMasterSecret(masterSecret); - - SendReceiveService.this.unbindService(this); - } - - @Override - public void onServiceDisconnected(ComponentName name) {} - }; +// private ServiceConnection serviceConnection = new ServiceConnection() { +// @Override +// public void onServiceConnected(ComponentName className, IBinder service) { +// KeyCachingService keyCachingService = ((KeyCachingService.KeyCachingBinder)service).getService(); +// MasterSecret masterSecret = keyCachingService.getMasterSecret(); +// +// initializeWithMasterSecret(masterSecret); +// +// SendReceiveService.this.unbindService(this); +// } +// +// @Override +// public void onServiceDisconnected(ComponentName name) {} +// }; private class NewKeyReceiver extends BroadcastReceiver { @Override diff --git a/src/org/thoughtcrime/securesms/service/SmsListener.java b/src/org/thoughtcrime/securesms/service/SmsListener.java index cc21f11050..e706e85ab2 100644 --- a/src/org/thoughtcrime/securesms/service/SmsListener.java +++ b/src/org/thoughtcrime/securesms/service/SmsListener.java @@ -25,6 +25,8 @@ import android.provider.Telephony; import android.telephony.SmsMessage; import android.util.Log; +import org.thoughtcrime.securesms.ApplicationContext; +import org.thoughtcrime.securesms.jobs.SmsReceiveJob; import org.thoughtcrime.securesms.protocol.WirePrefix; import org.thoughtcrime.securesms.sms.IncomingTextMessage; import org.thoughtcrime.securesms.util.TextSecurePreferences; @@ -78,15 +80,15 @@ public class SmsListener extends BroadcastReceiver { return bodyBuilder.toString(); } - private ArrayList getAsTextMessages(Intent intent) { - Object[] pdus = (Object[])intent.getExtras().get("pdus"); - ArrayList messages = new ArrayList(pdus.length); - - for (int i=0;i getAsTextMessages(Intent intent) { +// Object[] pdus = (Object[])intent.getExtras().get("pdus"); +// ArrayList messages = new ArrayList(pdus.length); +// +// for (int i=0;i. - */ -package org.thoughtcrime.securesms.service; - -import android.content.Context; -import android.content.Intent; -import android.util.Log; -import android.util.Pair; - -import org.thoughtcrime.securesms.crypto.DecryptingQueue; -import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor; -import org.thoughtcrime.securesms.crypto.MasterSecretUtil; -import org.thoughtcrime.securesms.crypto.TextSecureCipher; -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; -import org.thoughtcrime.securesms.database.SmsDatabase; -import org.thoughtcrime.securesms.notifications.MessageNotifier; -import org.thoughtcrime.securesms.protocol.WirePrefix; -import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientFactory; -import org.thoughtcrime.securesms.recipients.RecipientFormattingException; -import org.thoughtcrime.securesms.recipients.Recipients; -import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage; -import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage; -import org.thoughtcrime.securesms.sms.IncomingPreKeyBundleMessage; -import org.thoughtcrime.securesms.sms.IncomingTextMessage; -import org.thoughtcrime.securesms.sms.MessageSender; -import org.thoughtcrime.securesms.sms.MultipartSmsMessageHandler; -import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage; -import org.thoughtcrime.securesms.sms.SmsTransportDetails; -import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.whispersystems.libaxolotl.DuplicateMessageException; -import org.whispersystems.libaxolotl.InvalidKeyException; -import org.whispersystems.libaxolotl.InvalidMessageException; -import org.whispersystems.libaxolotl.InvalidVersionException; -import org.whispersystems.libaxolotl.LegacyMessageException; -import org.whispersystems.libaxolotl.NoSessionException; -import org.whispersystems.libaxolotl.StaleKeyExchangeException; -import org.whispersystems.libaxolotl.UntrustedIdentityException; -import org.whispersystems.libaxolotl.protocol.KeyExchangeMessage; -import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage; -import org.whispersystems.libaxolotl.protocol.WhisperMessage; -import org.whispersystems.textsecure.crypto.MasterSecret; -import org.whispersystems.libaxolotl.InvalidKeyIdException; -import org.whispersystems.textsecure.storage.RecipientDevice; -import org.whispersystems.textsecure.util.Base64; - -import java.io.IOException; -import java.util.List; - -public class SmsReceiver { - - private MultipartSmsMessageHandler multipartMessageHandler = new MultipartSmsMessageHandler(); - - private final Context context; - - public SmsReceiver(Context context) { - this.context = context; - } - - private IncomingTextMessage assembleMessageFragments(List messages) { - IncomingTextMessage message = new IncomingTextMessage(messages); - - if (WirePrefix.isEncryptedMessage(message.getMessageBody()) || - WirePrefix.isKeyExchange(message.getMessageBody()) || - WirePrefix.isPreKeyBundle(message.getMessageBody()) || - WirePrefix.isEndSession(message.getMessageBody())) - { - return multipartMessageHandler.processPotentialMultipartMessage(message); - } else { - return message; - } - } - - private Pair storeSecureMessage(MasterSecret masterSecret, IncomingTextMessage message) { - Pair messageAndThreadId = DatabaseFactory.getEncryptingSmsDatabase(context) - .insertMessageInbox(masterSecret, message); - - if (masterSecret != null) { - DecryptingQueue.scheduleDecryption(context, masterSecret, messageAndThreadId.first, - messageAndThreadId.second, - message.getSender(), message.getSenderDeviceId(), - message.getMessageBody(), message.isSecureMessage(), - message.isKeyExchange(), message.isEndSession()); - } - - return messageAndThreadId; - } - - private Pair storeStandardMessage(MasterSecret masterSecret, IncomingTextMessage message) { - EncryptingSmsDatabase encryptingDatabase = DatabaseFactory.getEncryptingSmsDatabase(context); - SmsDatabase plaintextDatabase = DatabaseFactory.getSmsDatabase(context); - - if (masterSecret != null) { - return encryptingDatabase.insertMessageInbox(masterSecret, message); - } else if (MasterSecretUtil.hasAsymmericMasterSecret(context)) { - return encryptingDatabase.insertMessageInbox(MasterSecretUtil.getAsymmetricMasterSecret(context, null), message); - } else { - return plaintextDatabase.insertMessageInbox(message); - } - } - - private Pair storePreKeyWhisperMessage(MasterSecret masterSecret, - IncomingPreKeyBundleMessage message) - { - Log.w("SmsReceiver", "Processing prekey message..."); - EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); - - if (masterSecret != null) { - try { - Recipient recipient = RecipientFactory.getRecipientsFromString(context, message.getSender(), false).getPrimaryRecipient(); - RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), message.getSenderDeviceId()); - SmsTransportDetails transportDetails = new SmsTransportDetails(); - TextSecureCipher cipher = new TextSecureCipher(context, masterSecret, recipientDevice, transportDetails); - byte[] rawMessage = transportDetails.getDecodedMessage(message.getMessageBody().getBytes()); - PreKeyWhisperMessage preKeyWhisperMessage = new PreKeyWhisperMessage(rawMessage); - byte[] plaintext = cipher.decrypt(preKeyWhisperMessage); - - IncomingEncryptedMessage bundledMessage = new IncomingEncryptedMessage(message, new String(transportDetails.getEncodedMessage(preKeyWhisperMessage.getWhisperMessage().serialize()))); - Pair messageAndThreadId = database.insertMessageInbox(masterSecret, bundledMessage); - - database.updateMessageBody(masterSecret, messageAndThreadId.first, new String(plaintext)); - - Intent intent = new Intent(KeyExchangeProcessor.SECURITY_UPDATE_EVENT); - intent.putExtra("thread_id", messageAndThreadId.second); - intent.setPackage(context.getPackageName()); - context.sendBroadcast(intent, KeyCachingService.KEY_PERMISSION); - - return messageAndThreadId; - } catch (InvalidKeyException | RecipientFormattingException | InvalidMessageException | IOException | NoSessionException e) { - Log.w("SmsReceiver", e); - message.setCorrupted(true); - } catch (InvalidVersionException e) { - Log.w("SmsReceiver", e); - message.setInvalidVersion(true); - } catch (InvalidKeyIdException e) { - Log.w("SmsReceiver", e); - message.setStale(true); - } catch (UntrustedIdentityException e) { - Log.w("SmsReceiver", e); - } catch (DuplicateMessageException e) { - Log.w("SmsReceiver", e); - message.setDuplicate(true); - } catch (LegacyMessageException e) { - Log.w("SmsReceiver", e); - message.setLegacyVersion(true); - } - } - - return storeStandardMessage(masterSecret, message); - } - - private Pair storeKeyExchangeMessage(MasterSecret masterSecret, - IncomingKeyExchangeMessage message) - { - if (masterSecret != null && TextSecurePreferences.isAutoRespondKeyExchangeEnabled(context)) { - try { - Recipient recipient = RecipientFactory.getRecipientsFromString(context, message.getSender(), false).getPrimaryRecipient(); - RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), message.getSenderDeviceId()); - KeyExchangeMessage exchangeMessage = new KeyExchangeMessage(Base64.decodeWithoutPadding(message.getMessageBody())); - KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipientDevice); - long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(new Recipients(recipient)); - OutgoingKeyExchangeMessage response = processor.processKeyExchangeMessage(exchangeMessage, threadId); - - message.setProcessed(true); - - Pair messageAndThreadId = storeStandardMessage(masterSecret, message); - - if (response != null) { - MessageSender.send(context, masterSecret, response, messageAndThreadId.second, true); - } - - return messageAndThreadId; - } catch (InvalidVersionException e) { - Log.w("SmsReceiver", e); - message.setInvalidVersion(true); - } catch (InvalidMessageException | InvalidKeyException | IOException | RecipientFormattingException e) { - Log.w("SmsReceiver", e); - message.setCorrupted(true); - } catch (LegacyMessageException e) { - Log.w("SmsReceiver", e); - message.setLegacyVersion(true); - } catch (StaleKeyExchangeException e) { - Log.w("SmsReceiver", e); - message.setStale(true); - } catch (UntrustedIdentityException e) { - Log.w("SmsReceiver", e); - } - } - - return storeStandardMessage(masterSecret, message); - } - - private Pair storeMessage(MasterSecret masterSecret, IncomingTextMessage message) { - if (message.isSecureMessage()) return storeSecureMessage(masterSecret, message); - else if (message.isPreKeyBundle()) return storePreKeyWhisperMessage(masterSecret, (IncomingPreKeyBundleMessage) message); - else if (message.isKeyExchange()) return storeKeyExchangeMessage(masterSecret, (IncomingKeyExchangeMessage) message); - else if (message.isEndSession()) return storeSecureMessage(masterSecret, message); - else return storeStandardMessage(masterSecret, message); - } - - private void handleReceiveMessage(MasterSecret masterSecret, Intent intent) { - if (intent.getExtras() == null) return; - - List messagesList = intent.getExtras().getParcelableArrayList("text_messages"); - IncomingTextMessage message = assembleMessageFragments(messagesList); - - if (message != null) { - Pair messageAndThreadId = storeMessage(masterSecret, message); - MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); - } - } - - public void process(MasterSecret masterSecret, Intent intent) { - if (SendReceiveService.RECEIVE_SMS_ACTION.equals(intent.getAction())) { - handleReceiveMessage(masterSecret, intent); - } - } -} diff --git a/src/org/thoughtcrime/securesms/service/SmsSender.java b/src/org/thoughtcrime/securesms/service/SmsSender.java index 3deafac2e3..ea1184cebd 100644 --- a/src/org/thoughtcrime/securesms/service/SmsSender.java +++ b/src/org/thoughtcrime/securesms/service/SmsSender.java @@ -27,7 +27,7 @@ import android.telephony.SmsMessage; import android.util.Log; import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor; +import org.thoughtcrime.securesms.crypto.SecurityEvent; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; import org.thoughtcrime.securesms.database.SmsDatabase; @@ -41,10 +41,10 @@ import org.thoughtcrime.securesms.transport.RetryLaterException; import org.thoughtcrime.securesms.transport.SecureFallbackApprovalException; import org.thoughtcrime.securesms.transport.UndeliverableMessageException; import org.thoughtcrime.securesms.transport.UniversalTransport; -import org.thoughtcrime.securesms.transport.UntrustedIdentityException; +import org.whispersystems.textsecure.crypto.UntrustedIdentityException; import org.whispersystems.libaxolotl.state.SessionStore; -import org.whispersystems.textsecure.crypto.MasterSecret; -import org.whispersystems.textsecure.storage.TextSecureSessionStore; +import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore; public class SmsSender { @@ -141,7 +141,7 @@ public class SmsSender { Log.w("SmsSender", "Ending session..."); SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret); sessionStore.deleteAllSessions(record.getIndividualRecipient().getRecipientId()); - KeyExchangeProcessor.broadcastSecurityUpdateEvent(context, record.getThreadId()); + SecurityEvent.broadcastSecurityUpdateEvent(context, record.getThreadId()); } unregisterForRadioChanges(); diff --git a/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java b/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java index ac814c905c..85a257041d 100644 --- a/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java +++ b/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java @@ -5,6 +5,8 @@ import android.os.Parcelable; import android.telephony.SmsMessage; import org.thoughtcrime.securesms.util.GroupUtil; +import org.whispersystems.libaxolotl.util.guava.Optional; +import org.whispersystems.textsecure.api.messages.TextSecureGroup; import org.whispersystems.textsecure.push.IncomingPushMessage; import org.whispersystems.textsecure.storage.RecipientDevice; @@ -50,19 +52,21 @@ public class IncomingTextMessage implements Parcelable { this.push = false; } - public IncomingTextMessage(IncomingPushMessage message, String encodedBody, GroupContext group) { + public IncomingTextMessage(String sender, int senderDeviceId, long sentTimestampMillis, + String encodedBody, Optional group) + { this.message = encodedBody; - this.sender = message.getSource(); - this.senderDeviceId = message.getSourceDevice(); + this.sender = sender; + this.senderDeviceId = senderDeviceId; this.protocol = 31337; this.serviceCenterAddress = "GCM"; this.replyPathPresent = true; this.pseudoSubject = ""; - this.sentTimestampMillis = message.getTimestampMillis(); + this.sentTimestampMillis = sentTimestampMillis; this.push = true; - if (group != null && group.hasId()) { - this.groupId = GroupUtil.getEncodedId(group.getId().toByteArray()); + if (group.isPresent()) { + this.groupId = GroupUtil.getEncodedId(group.get().getGroupId()); } else { this.groupId = null; } diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index 3e006601eb..f3b34b8898 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -21,25 +21,20 @@ import android.content.Intent; import android.util.Log; import android.util.Pair; +import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; import org.thoughtcrime.securesms.database.MmsDatabase; -import org.thoughtcrime.securesms.database.MmsSmsColumns; -import org.thoughtcrime.securesms.mms.IncomingMediaMessage; import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; import org.thoughtcrime.securesms.recipients.Recipients; +import org.thoughtcrime.securesms.service.SendReceiveService; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; -import org.whispersystems.textsecure.crypto.MasterSecret; -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.service.SendReceiveService; -import org.whispersystems.textsecure.push.IncomingPushMessage; -import org.whispersystems.textsecure.push.PushMessageProtos; import org.whispersystems.textsecure.util.InvalidNumberException; import java.util.List; import ws.com.google.android.mms.MmsException; -import ws.com.google.android.mms.pdu.RetrieveConf; public class MessageSender { diff --git a/src/org/thoughtcrime/securesms/sms/MultipartSmsMessageHandler.java b/src/org/thoughtcrime/securesms/sms/MultipartSmsMessageHandler.java index d54de8fe5d..429aae771a 100644 --- a/src/org/thoughtcrime/securesms/sms/MultipartSmsMessageHandler.java +++ b/src/org/thoughtcrime/securesms/sms/MultipartSmsMessageHandler.java @@ -26,27 +26,28 @@ import java.util.HashMap; public class MultipartSmsMessageHandler { - private final HashMap partialMessages = - new HashMap(); + private static final String TAG = MultipartSmsMessageHandler.class.getSimpleName(); + + private final HashMap partialMessages = new HashMap<>(); private IncomingTextMessage processMultipartMessage(MultipartSmsTransportMessage message) { - Log.w("MultipartSmsMessageHandler", "Processing multipart message..."); - Log.w("MultipartSmsMessageHandler", "Multipart Count: " + message.getMultipartCount()); - Log.w("MultipartSmsMessageHandler", "Multipart ID: " + message.getIdentifier()); - Log.w("MultipartSmsMessageHandler", "Multipart Key: " + message.getKey()); + Log.w(TAG, "Processing multipart message..."); + Log.w(TAG, "Multipart Count: " + message.getMultipartCount()); + Log.w(TAG, "Multipart ID: " + message.getIdentifier()); + Log.w(TAG, "Multipart Key: " + message.getKey()); MultipartSmsTransportMessageFragments container = partialMessages.get(message.getKey()); - Log.w("MultipartSmsMessageHandler", "Found multipart container: " + container); + Log.w(TAG, "Found multipart container: " + container); if (container == null || container.getSize() != message.getMultipartCount() || container.isExpired()) { - Log.w("MultipartSmsMessageHandler", "Constructing new container..."); + Log.w(TAG, "Constructing new container..."); container = new MultipartSmsTransportMessageFragments(message.getMultipartCount()); partialMessages.put(message.getKey(), container); } container.add(message); - Log.w("MultipartSmsMessageHandler", "Filled buffer at index: " + message.getMultipartIndex()); + Log.w(TAG, "Filled buffer at index: " + message.getMultipartIndex()); if (!container.isComplete()) return null; @@ -64,7 +65,7 @@ public class MultipartSmsMessageHandler { } private IncomingTextMessage processSinglePartMessage(MultipartSmsTransportMessage message) { - Log.w("MultipartSmsMessageHandler", "Processing single part message..."); + Log.w(TAG, "Processing single part message..."); String strippedMessage = Base64.encodeBytesWithoutPadding(message.getStrippedMessage()); if (message.getWireType() == MultipartSmsTransportMessage.WIRETYPE_KEY) { @@ -78,7 +79,7 @@ public class MultipartSmsMessageHandler { } } - public IncomingTextMessage processPotentialMultipartMessage(IncomingTextMessage message) { + public synchronized IncomingTextMessage processPotentialMultipartMessage(IncomingTextMessage message) { try { MultipartSmsTransportMessage transportMessage = new MultipartSmsTransportMessage(message); @@ -86,12 +87,12 @@ public class MultipartSmsMessageHandler { else if (transportMessage.isSinglePart()) return processSinglePartMessage(transportMessage); else return processMultipartMessage(transportMessage); } catch (IOException e) { - Log.w("MultipartSmsMessageHandler", e); + Log.w(TAG, e); return message; } } - public ArrayList divideMessage(OutgoingTextMessage message) { + public synchronized ArrayList divideMessage(OutgoingTextMessage message) { String number = message.getRecipients().getPrimaryRecipient().getNumber(); byte identifier = MultipartSmsIdentifier.getInstance().getIdForRecipient(number); return MultipartSmsTransportMessage.getEncoded(message, identifier); diff --git a/src/org/thoughtcrime/securesms/transport/InsecureFallbackApprovalException.java b/src/org/thoughtcrime/securesms/transport/InsecureFallbackApprovalException.java index ae72d715f6..f8d3a20d75 100644 --- a/src/org/thoughtcrime/securesms/transport/InsecureFallbackApprovalException.java +++ b/src/org/thoughtcrime/securesms/transport/InsecureFallbackApprovalException.java @@ -4,4 +4,7 @@ public class InsecureFallbackApprovalException extends Exception { public InsecureFallbackApprovalException(String detailMessage) { super(detailMessage); } + public InsecureFallbackApprovalException(Exception e) { + super(e); + } } diff --git a/src/org/thoughtcrime/securesms/transport/MmsTransport.java b/src/org/thoughtcrime/securesms/transport/MmsTransport.java index 2a24d59f46..f8c3ee17d1 100644 --- a/src/org/thoughtcrime/securesms/transport/MmsTransport.java +++ b/src/org/thoughtcrime/securesms/transport/MmsTransport.java @@ -21,39 +21,33 @@ import android.content.Context; import android.telephony.TelephonyManager; import android.util.Log; -import org.thoughtcrime.securesms.crypto.TextSecureCipher; +import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MmsCipher; +import org.thoughtcrime.securesms.crypto.storage.TextSecureAxolotlStore; import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.mms.ApnUnavailableException; import org.thoughtcrime.securesms.mms.MmsRadio; import org.thoughtcrime.securesms.mms.MmsRadioException; import org.thoughtcrime.securesms.mms.MmsSendResult; import org.thoughtcrime.securesms.mms.OutgoingMmsConnection; -import org.thoughtcrime.securesms.mms.TextTransport; -import org.thoughtcrime.securesms.protocol.WirePrefix; -import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.RecipientFormattingException; import org.thoughtcrime.securesms.util.NumberUtil; -import org.whispersystems.libaxolotl.protocol.CiphertextMessage; -import org.whispersystems.textsecure.crypto.MasterSecret; -import org.whispersystems.textsecure.storage.RecipientDevice; -import org.whispersystems.textsecure.storage.SessionUtil; +import org.whispersystems.libaxolotl.NoSessionException; import org.whispersystems.textsecure.util.Hex; import java.io.IOException; import java.util.Arrays; -import ws.com.google.android.mms.ContentType; import ws.com.google.android.mms.pdu.EncodedStringValue; -import ws.com.google.android.mms.pdu.PduBody; import ws.com.google.android.mms.pdu.PduComposer; import ws.com.google.android.mms.pdu.PduHeaders; -import ws.com.google.android.mms.pdu.PduPart; import ws.com.google.android.mms.pdu.SendConf; import ws.com.google.android.mms.pdu.SendReq; public class MmsTransport { + private static final String TAG = MmsTransport.class.getSimpleName(); + private final Context context; private final MasterSecret masterSecret; private final MmsRadio radio; @@ -72,15 +66,15 @@ public class MmsTransport { try { if (isCdmaDevice()) { - Log.w("MmsTransport", "Sending MMS directly without radio change..."); + Log.w(TAG, "Sending MMS directly without radio change..."); try { return sendMms(message, false, false); } catch (IOException e) { - Log.w("MmsTransport", e); + Log.w(TAG, e); } } - Log.w("MmsTransport", "Sending MMS with radio change and proxy..."); + Log.w(TAG, "Sending MMS with radio change and proxy..."); radio.connect(); try { @@ -88,23 +82,23 @@ public class MmsTransport { radio.disconnect(); return result; } catch (IOException e) { - Log.w("MmsTransport", e); + Log.w(TAG, e); } - Log.w("MmsTransport", "Sending MMS with radio change and without proxy..."); + Log.w(TAG, "Sending MMS with radio change and without proxy..."); try { MmsSendResult result = sendMms(message, true, false); radio.disconnect(); return result; } catch (IOException ioe) { - Log.w("MmsTransport", ioe); + Log.w(TAG, ioe); radio.disconnect(); throw new UndeliverableMessageException(ioe); } } catch (MmsRadioException mre) { - Log.w("MmsTransport", mre); + Log.w(TAG, mre); throw new UndeliverableMessageException(mre); } } @@ -129,7 +123,7 @@ public class MmsTransport { SendConf conf = connection.send(usingMmsRadio, useProxy); for (int i=0;i addresses = getPushAddresses(recipients); + List attachments = getAttachments(message); + + if (MmsSmsColumns.Types.isGroupUpdate(message.getDatabaseMessageBox()) || + MmsSmsColumns.Types.isGroupQuit(message.getDatabaseMessageBox())) + { + String content = PartParser.getMessageText(message.getBody()); + + if (content != null && !content.trim().isEmpty()) { + GroupContext groupContext = GroupContext.parseFrom(Base64.decode(content)); + TextSecureAttachment avatar = attachments.isEmpty() ? null : attachments.get(0); + TextSecureGroup.Type type = MmsSmsColumns.Types.isGroupQuit(message.getDatabaseMessageBox()) ? TextSecureGroup.Type.QUIT : TextSecureGroup.Type.UPDATE; + TextSecureGroup group = new TextSecureGroup(type, groupId, groupContext.getName(), groupContext.getMembersList(), avatar); + TextSecureMessage groupMessage = new TextSecureMessage(message.getSentTimestamp(), group, null, null); + + messageSender.sendMessage(addresses, groupMessage); + } + } else { + String body = PartParser.getMessageText(message.getBody()); + TextSecureGroup group = new TextSecureGroup(groupId); + TextSecureMessage groupMessage = new TextSecureMessage(message.getSentTimestamp(), group, attachments, body); + + messageSender.sendMessage(addresses, groupMessage); + } + } + public void deliver(SendReq message, long threadId) throws IOException, RecipientFormattingException, InvalidNumberException, EncapsulatedExceptions { - PushServiceSocket socket = PushServiceSocketFactory.create(context); - byte[] plaintext = getPlaintextMessage(socket, message); - String destination = message.getTo()[0].getString(); - - Recipients recipients; - - if (GroupUtil.isEncodedGroup(destination)) { - recipients = DatabaseFactory.getGroupDatabase(context) - .getGroupMembers(GroupUtil.getDecodedId(destination), false); - } else { - recipients = RecipientFactory.getRecipientsFromString(context, destination, false); - } + TextSecureMessageSender messageSender = TextSecureMessageSenderFactory.create(context, masterSecret); + String destination = message.getTo()[0].getString(); List untrustedIdentities = new LinkedList<>(); List unregisteredUsers = new LinkedList<>(); - for (Recipient recipient : recipients.getRecipientsList()) { - try { - deliver(socket, recipient, message.getSentTimestamp(), threadId, plaintext); - } catch (UntrustedIdentityException e) { - Log.w("PushTransport", e); - untrustedIdentities.add(e); - } catch (UnregisteredUserException e) { - Log.w("PushTransport", e); - unregisteredUsers.add(e); - } + if (GroupUtil.isEncodedGroup(destination)) { + deliverGroupMessage(message, threadId); + return; + } + + try { + Recipients recipients = RecipientFactory.getRecipientsFromString(context, destination, false); + PushAddress address = getPushAddress(recipients.getPrimaryRecipient()); + List attachments = getAttachments(message); + String body = PartParser.getMessageText(message.getBody()); + TextSecureMessage mediaMessage = new TextSecureMessage(message.getSentTimestamp(), attachments, body); + + messageSender.sendMessage(address, mediaMessage); + } catch (UntrustedIdentityException e) { + Log.w(TAG, e); + untrustedIdentities.add(e); + } catch (UnregisteredUserException e) { + Log.w(TAG, e); + unregisteredUsers.add(e); } if (!untrustedIdentities.isEmpty() || !unregisteredUsers.isEmpty()) { @@ -144,220 +156,36 @@ public class PushTransport extends BaseTransport { } } - private void deliver(PushServiceSocket socket, Recipient recipient, long timestamp, - long threadId, byte[] plaintext) - throws IOException, InvalidNumberException, UntrustedIdentityException - { - for (int i=0;i<3;i++) { - try { - OutgoingPushMessageList messages = getEncryptedMessages(socket, threadId, recipient, - timestamp, plaintext); - socket.sendMessage(messages); - - return; - } catch (MismatchedDevicesException mde) { - Log.w("PushTransport", mde); - handleMismatchedDevices(socket, threadId, recipient, mde.getMismatchedDevices()); - } catch (StaleDevicesException ste) { - Log.w("PushTransport", ste); - handleStaleDevices(recipient, ste.getStaleDevices()); - } - } + private PushAddress getPushAddress(Recipient recipient) throws InvalidNumberException { + String e164number = Util.canonicalizeNumber(context, recipient.getNumber()); + String relay = Directory.getInstance(context).getRelay(e164number); + return new PushAddress(recipient.getRecipientId(), e164number, 1, relay); } - private List getPushAttachmentPointers(PushServiceSocket socket, PduBody body) - throws IOException - { - List attachments = new LinkedList<>(); + private List getPushAddresses(Recipients recipients) throws InvalidNumberException { + List addresses = new LinkedList<>(); - for (int i=0;i getAttachments(SendReq message) { + List attachments = new LinkedList<>(); + + for (int i=0;i attachments = getPushAttachmentPointers(socket, message.getBody()); - - PushMessageContent.Builder builder = PushMessageContent.newBuilder(); - - if (GroupUtil.isEncodedGroup(message.getTo()[0].getString())) { - GroupContext.Builder groupBuilder = GroupContext.newBuilder(); - byte[] groupId = GroupUtil.getDecodedId(message.getTo()[0].getString()); - - groupBuilder.setId(ByteString.copyFrom(groupId)); - groupBuilder.setType(GroupContext.Type.DELIVER); - - if (MmsSmsColumns.Types.isGroupUpdate(message.getDatabaseMessageBox()) || - MmsSmsColumns.Types.isGroupQuit(message.getDatabaseMessageBox())) - { - if (messageBody != null && messageBody.trim().length() > 0) { - groupBuilder = GroupContext.parseFrom(Base64.decode(messageBody)).toBuilder(); - messageBody = null; - - if (attachments != null && !attachments.isEmpty()) { - groupBuilder.setAvatar(AttachmentPointer.newBuilder() - .setId(attachments.get(0).getId()) - .setContentType(attachments.get(0).getContentType()) - .setKey(ByteString.copyFrom(attachments.get(0).getKey())) - .build()); - - attachments.remove(0); - } - } - } - - builder.setGroup(groupBuilder.build()); - } - - if (messageBody != null) { - builder.setBody(messageBody); - } - - for (PushAttachmentPointer attachment : attachments) { - AttachmentPointer.Builder attachmentBuilder = - AttachmentPointer.newBuilder(); - - attachmentBuilder.setId(attachment.getId()); - attachmentBuilder.setContentType(attachment.getContentType()); - attachmentBuilder.setKey(ByteString.copyFrom(attachment.getKey())); - - builder.addAttachments(attachmentBuilder.build()); - } - - return builder.build().toByteArray(); - } - - private byte[] getPlaintextMessage(SmsMessageRecord record) { - PushMessageContent.Builder builder = PushMessageContent.newBuilder() - .setBody(record.getBody().getBody()); - - if (record.isEndSession()) { - builder.setFlags(PushMessageContent.Flags.END_SESSION_VALUE); - } - - return builder.build().toByteArray(); - } - - private OutgoingPushMessageList getEncryptedMessages(PushServiceSocket socket, long threadId, - Recipient recipient, long timestamp, - byte[] plaintext) - throws IOException, InvalidNumberException, UntrustedIdentityException - { - SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret); - String e164number = Util.canonicalizeNumber(context, recipient.getNumber()); - long recipientId = recipient.getRecipientId(); - PushAddress masterDevice = PushAddress.create(context, recipientId, e164number, 1); - PushBody masterBody = getEncryptedMessage(socket, threadId, masterDevice, plaintext); - - List messages = new LinkedList<>(); - messages.add(new OutgoingPushMessage(masterDevice, masterBody)); - - for (int deviceId : sessionStore.getSubDeviceSessions(recipientId)) { - PushAddress device = PushAddress.create(context, recipientId, e164number, deviceId); - PushBody body = getEncryptedMessage(socket, threadId, device, plaintext); - - messages.add(new OutgoingPushMessage(device, body)); - } - - return new OutgoingPushMessageList(e164number, timestamp, masterDevice.getRelay(), messages); - } - - private PushBody getEncryptedMessage(PushServiceSocket socket, long threadId, - PushAddress pushAddress, byte[] plaintext) - throws IOException, UntrustedIdentityException - { - if (!SessionUtil.hasEncryptCapableSession(context, masterSecret, pushAddress)) { - try { - List preKeys = socket.getPreKeys(pushAddress); - - for (PreKeyBundle preKey : preKeys) { - PushAddress device = PushAddress.create(context, pushAddress.getRecipientId(), pushAddress.getNumber(), preKey.getDeviceId()); - KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, device); - - try { - processor.processKeyExchangeMessage(preKey, threadId); - } catch (org.whispersystems.libaxolotl.UntrustedIdentityException e) { - throw new UntrustedIdentityException("Untrusted identity key!", pushAddress.getNumber(), preKey.getIdentityKey()); - } - } - } catch (InvalidKeyException e) { - throw new IOException(e); - } - } - - TransportDetails transportDetails = new PushTransportDetails(SessionUtil.getSessionVersion(context, masterSecret, pushAddress)); - TextSecureCipher cipher = new TextSecureCipher(context, masterSecret, pushAddress, transportDetails); - CiphertextMessage message = cipher.encrypt(plaintext); - int remoteRegistrationId = cipher.getRemoteRegistrationId(); - - if (message.getType() == CiphertextMessage.PREKEY_TYPE) { - return new PushBody(IncomingPushMessageSignal.Type.PREKEY_BUNDLE_VALUE, remoteRegistrationId, message.serialize()); - } else if (message.getType() == CiphertextMessage.WHISPER_TYPE) { - return new PushBody(IncomingPushMessageSignal.Type.CIPHERTEXT_VALUE, remoteRegistrationId, message.serialize()); - } else { - throw new AssertionError("Unknown ciphertext type: " + message.getType()); - } - } } diff --git a/src/org/thoughtcrime/securesms/transport/SmsTransport.java b/src/org/thoughtcrime/securesms/transport/SmsTransport.java index 0ee328ef36..5cea9c3406 100644 --- a/src/org/thoughtcrime/securesms/transport/SmsTransport.java +++ b/src/org/thoughtcrime/securesms/transport/SmsTransport.java @@ -22,19 +22,15 @@ import android.content.Context; import android.telephony.SmsManager; import android.util.Log; -import org.thoughtcrime.securesms.crypto.TextSecureCipher; +import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.SmsCipher; +import org.thoughtcrime.securesms.crypto.storage.TextSecureAxolotlStore; import org.thoughtcrime.securesms.database.model.SmsMessageRecord; -import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.sms.MultipartSmsMessageHandler; -import org.thoughtcrime.securesms.sms.OutgoingPrekeyBundleMessage; import org.thoughtcrime.securesms.sms.OutgoingTextMessage; -import org.thoughtcrime.securesms.sms.SmsTransportDetails; import org.thoughtcrime.securesms.util.NumberUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.whispersystems.libaxolotl.protocol.CiphertextMessage; -import org.whispersystems.textsecure.crypto.MasterSecret; -import org.whispersystems.textsecure.storage.RecipientDevice; -import org.whispersystems.textsecure.storage.SessionUtil; +import org.whispersystems.libaxolotl.NoSessionException; import java.util.ArrayList; @@ -138,7 +134,7 @@ public class SmsTransport extends BaseTransport { private ArrayList constructSentIntents(long messageId, long type, ArrayList messages, boolean secure) { - ArrayList sentIntents = new ArrayList(messages.size()); + ArrayList sentIntents = new ArrayList<>(messages.size()); for (String ignored : messages) { sentIntents.add(PendingIntent.getBroadcast(context, 0, @@ -154,7 +150,7 @@ public class SmsTransport extends BaseTransport { return null; } - ArrayList deliveredIntents = new ArrayList(messages.size()); + ArrayList deliveredIntents = new ArrayList<>(messages.size()); for (String ignored : messages) { deliveredIntents.add(PendingIntent.getBroadcast(context, 0, @@ -169,26 +165,10 @@ public class SmsTransport extends BaseTransport { OutgoingTextMessage message) throws InsecureFallbackApprovalException { - Recipient recipient = message.getRecipients().getPrimaryRecipient(); - RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), - RecipientDevice.DEFAULT_DEVICE_ID); - - if (!SessionUtil.hasEncryptCapableSession(context, masterSecret, recipientDevice)) { - throw new InsecureFallbackApprovalException("No session exists for this secure message."); + try { + return new SmsCipher(new TextSecureAxolotlStore(context, masterSecret)).encrypt(message); + } catch (NoSessionException e) { + throw new InsecureFallbackApprovalException(e); } - - String body = message.getMessageBody(); - SmsTransportDetails transportDetails = new SmsTransportDetails(); - TextSecureCipher cipher = new TextSecureCipher(context, masterSecret, recipientDevice, transportDetails); - CiphertextMessage ciphertextMessage = cipher.encrypt(body.getBytes()); - String encodedCiphertext = new String(transportDetails.getEncodedMessage(ciphertextMessage.serialize())); - - if (ciphertextMessage.getType() == CiphertextMessage.PREKEY_TYPE) { - message = new OutgoingPrekeyBundleMessage(message, encodedCiphertext); - } else { - message = message.withBody(encodedCiphertext); - } - - return message; } } diff --git a/src/org/thoughtcrime/securesms/transport/UniversalTransport.java b/src/org/thoughtcrime/securesms/transport/UniversalTransport.java index 440251d819..d649408d02 100644 --- a/src/org/thoughtcrime/securesms/transport/UniversalTransport.java +++ b/src/org/thoughtcrime/securesms/transport/UniversalTransport.java @@ -19,6 +19,8 @@ package org.thoughtcrime.securesms.transport; import android.content.Context; import android.util.Log; +import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.storage.TextSecureAxolotlStore; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.model.SmsMessageRecord; @@ -32,13 +34,15 @@ import org.thoughtcrime.securesms.sms.IncomingIdentityUpdateMessage; import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.whispersystems.libaxolotl.state.AxolotlStore; +import org.whispersystems.textsecure.crypto.UntrustedIdentityException; import org.whispersystems.textsecure.directory.Directory; import org.whispersystems.textsecure.directory.NotInDirectoryException; import org.whispersystems.textsecure.push.ContactTokenDetails; import org.whispersystems.textsecure.push.PushServiceSocket; import org.whispersystems.textsecure.push.UnregisteredUserException; -import org.whispersystems.textsecure.storage.SessionUtil; +import org.whispersystems.textsecure.push.exceptions.EncapsulatedExceptions; +import org.whispersystems.textsecure.storage.RecipientDevice; import org.whispersystems.textsecure.util.DirectoryUtil; import org.whispersystems.textsecure.util.InvalidNumberException; @@ -48,6 +52,8 @@ import ws.com.google.android.mms.pdu.SendReq; public class UniversalTransport { + private static final String TAG = UniversalTransport.class.getSimpleName(); + private final Context context; private final MasterSecret masterSecret; private final PushTransport pushTransport; @@ -84,23 +90,23 @@ public class UniversalTransport { boolean isSmsFallbackSupported = isSmsFallbackSupported(number); try { - Log.w("UniversalTransport", "Using GCM as transport..."); + Log.w(TAG, "Using PUSH as transport..."); pushTransport.deliver(message); } catch (UnregisteredUserException uue) { - Log.w("UniversalTransport", uue); + Log.w(TAG, uue); if (isSmsFallbackSupported) fallbackOrAskApproval(message, number); else throw new UndeliverableMessageException(uue); } catch (IOException ioe) { - Log.w("UniversalTransport", ioe); + Log.w(TAG, ioe); if (isSmsFallbackSupported) fallbackOrAskApproval(message, number); else throw new RetryLaterException(ioe); } } else { - Log.w("UniversalTransport", "Using SMS as transport..."); + Log.w(TAG, "Using SMS as transport..."); deliverDirectSms(message); } } catch (InvalidNumberException e) { - Log.w("UniversalTransport", e); + Log.w(TAG, e); deliverDirectSms(message); } } @@ -136,19 +142,19 @@ public class UniversalTransport { boolean isSmsFallbackSupported = isSmsFallbackSupported(destination); try { - Log.w("UniversalTransport", "Using GCM as transport..."); + Log.w(TAG, "Using GCM as transport..."); pushTransport.deliver(mediaMessage, threadId); return new MmsSendResult("push".getBytes("UTF-8"), 0, true, true); } catch (IOException ioe) { - Log.w("UniversalTransport", ioe); + Log.w(TAG, ioe); if (isSmsFallbackSupported) return fallbackOrAskApproval(mediaMessage, destination); else throw new RetryLaterException(ioe); } catch (RecipientFormattingException e) { - Log.w("UniversalTransport", e); + Log.w(TAG, e); if (isSmsFallbackSupported) return fallbackOrAskApproval(mediaMessage, destination); else throw new UndeliverableMessageException(e); } catch (EncapsulatedExceptions ee) { - Log.w("UniversalTransport", ee); + Log.w(TAG, ee); if (!ee.getUnregisteredUserExceptions().isEmpty()) { if (isSmsFallbackSupported) return mmsTransport.deliver(mediaMessage); else throw new UndeliverableMessageException(ee); @@ -157,11 +163,11 @@ public class UniversalTransport { } } } else { - Log.w("UniversalTransport", "Delivering media message with MMS..."); + Log.w(TAG, "Delivering media message with MMS..."); return deliverDirectMms(mediaMessage); } } catch (InvalidNumberException ine) { - Log.w("UniversalTransport", ine); + Log.w(TAG, ine); return deliverDirectMms(mediaMessage); } } @@ -170,18 +176,19 @@ public class UniversalTransport { throws SecureFallbackApprovalException, UndeliverableMessageException, InsecureFallbackApprovalException { try { - Recipient recipient = RecipientFactory.getRecipientsFromString(context, destination, false).getPrimaryRecipient(); - boolean isSmsFallbackApprovalRequired = isSmsFallbackApprovalRequired(destination); + Recipient recipient = RecipientFactory.getRecipientsFromString(context, destination, false).getPrimaryRecipient(); + boolean isSmsFallbackApprovalRequired = isSmsFallbackApprovalRequired(destination); + AxolotlStore axolotlStore = new TextSecureAxolotlStore(context, masterSecret); if (!isSmsFallbackApprovalRequired) { - Log.w("UniversalTransport", "Falling back to MMS"); + Log.w(TAG, "Falling back to MMS"); DatabaseFactory.getMmsDatabase(context).markAsForcedSms(mediaMessage.getDatabaseMessageId()); return mmsTransport.deliver(mediaMessage); - } else if (!SessionUtil.hasEncryptCapableSession(context, masterSecret, recipient)) { - Log.w("UniversalTransport", "Marking message as pending insecure SMS fallback"); + } else if (!axolotlStore.containsSession(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID)) { + Log.w(TAG, "Marking message as pending insecure SMS fallback"); throw new InsecureFallbackApprovalException("Pending user approval for fallback to insecure SMS"); } else { - Log.w("UniversalTransport", "Marking message as pending secure SMS fallback"); + Log.w(TAG, "Marking message as pending secure SMS fallback"); throw new SecureFallbackApprovalException("Pending user approval for fallback secure to SMS"); } } catch (RecipientFormattingException rfe) { @@ -192,18 +199,19 @@ public class UniversalTransport { private void fallbackOrAskApproval(SmsMessageRecord smsMessage, String destination) throws SecureFallbackApprovalException, UndeliverableMessageException, InsecureFallbackApprovalException { - Recipient recipient = smsMessage.getIndividualRecipient(); - boolean isSmsFallbackApprovalRequired = isSmsFallbackApprovalRequired(destination); + Recipient recipient = smsMessage.getIndividualRecipient(); + boolean isSmsFallbackApprovalRequired = isSmsFallbackApprovalRequired(destination); + AxolotlStore axolotlStore = new TextSecureAxolotlStore(context, masterSecret); if (!isSmsFallbackApprovalRequired) { - Log.w("UniversalTransport", "Falling back to SMS"); + Log.w(TAG, "Falling back to SMS"); DatabaseFactory.getSmsDatabase(context).markAsForcedSms(smsMessage.getId()); smsTransport.deliver(smsMessage); - } else if (!SessionUtil.hasEncryptCapableSession(context, masterSecret, recipient)) { - Log.w("UniversalTransport", "Marking message as pending insecure fallback."); + } else if (!axolotlStore.containsSession(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID)) { + Log.w(TAG, "Marking message as pending insecure fallback."); throw new InsecureFallbackApprovalException("Pending user approval for fallback to insecure SMS"); } else { - Log.w("UniversalTransport", "Marking message as pending secure fallback."); + Log.w(TAG, "Marking message as pending secure fallback."); throw new SecureFallbackApprovalException("Pending user approval for fallback to secure SMS"); } } @@ -219,14 +227,12 @@ public class UniversalTransport { pushTransport.deliver(mediaMessage, threadId); return new MmsSendResult("push".getBytes("UTF-8"), 0, true, true); } catch (IOException e) { - Log.w("UniversalTransport", e); + Log.w(TAG, e); throw new RetryLaterException(e); - } catch (RecipientFormattingException e) { - throw new UndeliverableMessageException(e); - } catch (InvalidNumberException e) { + } catch (RecipientFormattingException | InvalidNumberException e) { throw new UndeliverableMessageException(e); } catch (EncapsulatedExceptions ee) { - Log.w("UniversalTransport", ee); + Log.w(TAG, ee); try { for (UnregisteredUserException unregistered : ee.getUnregisteredUserExceptions()) { IncomingGroupMessage quitMessage = IncomingGroupMessage.createForQuit(mediaMessage.getTo()[0].getString(), unregistered.getE164Number()); @@ -329,7 +335,7 @@ public class UniversalTransport { return true; } } catch (IOException e1) { - Log.w("UniversalTransport", e1); + Log.w(TAG, e1); return false; } } diff --git a/src/org/thoughtcrime/securesms/util/MemoryCleaner.java b/src/org/thoughtcrime/securesms/util/MemoryCleaner.java index b9eff7b731..8838aba08a 100644 --- a/src/org/thoughtcrime/securesms/util/MemoryCleaner.java +++ b/src/org/thoughtcrime/securesms/util/MemoryCleaner.java @@ -16,12 +16,7 @@ */ package org.thoughtcrime.securesms.util; -import java.lang.reflect.Field; -import java.util.Arrays; - -import org.whispersystems.textsecure.crypto.MasterSecret; - -import android.util.Log; +import org.thoughtcrime.securesms.crypto.MasterSecret; /** * This is not straightforward in Java, but this class makes diff --git a/src/org/thoughtcrime/securesms/util/SaveAttachmentTask.java b/src/org/thoughtcrime/securesms/util/SaveAttachmentTask.java index 89621f9547..888a54aa83 100644 --- a/src/org/thoughtcrime/securesms/util/SaveAttachmentTask.java +++ b/src/org/thoughtcrime/securesms/util/SaveAttachmentTask.java @@ -12,9 +12,9 @@ import android.webkit.MimeTypeMap; import android.widget.Toast; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.providers.PartProvider; -import org.whispersystems.textsecure.crypto.MasterSecret; import java.io.File; import java.io.FileOutputStream;