From 832763f6956cbf2014c93b48d23b44cbae6af7f6 Mon Sep 17 00:00:00 2001 From: Jake McGinty Date: Mon, 31 Mar 2014 18:47:24 -0700 Subject: [PATCH] UX for unencrypted fallback case --- .../textsecure/storage/Session.java | 21 +++++++ res/values/strings.xml | 3 + .../securesms/ConversationActivity.java | 4 +- .../securesms/ConversationItem.java | 48 +++++++++++----- .../securesms/database/MmsDatabase.java | 13 ++++- .../securesms/database/MmsSmsColumns.java | 31 +++++++---- .../securesms/database/SmsDatabase.java | 12 +++- .../database/model/MessageRecord.java | 13 ++++- .../securesms/service/MmsSender.java | 24 +++++--- .../securesms/service/SmsSender.java | 14 +++-- .../InsecureFallbackApprovalException.java | 7 +++ .../securesms/transport/MmsTransport.java | 16 ++++-- .../SecureFallbackApprovalException.java | 7 +++ .../securesms/transport/SmsTransport.java | 18 +++++- .../transport/UniversalTransport.java | 55 ++++++++++++------- .../UserInterventionRequiredException.java | 7 --- 16 files changed, 213 insertions(+), 80 deletions(-) create mode 100644 src/org/thoughtcrime/securesms/transport/InsecureFallbackApprovalException.java create mode 100644 src/org/thoughtcrime/securesms/transport/SecureFallbackApprovalException.java delete mode 100644 src/org/thoughtcrime/securesms/transport/UserInterventionRequiredException.java diff --git a/library/src/org/whispersystems/textsecure/storage/Session.java b/library/src/org/whispersystems/textsecure/storage/Session.java index 8f44a6d714..7270e9f738 100644 --- a/library/src/org/whispersystems/textsecure/storage/Session.java +++ b/library/src/org/whispersystems/textsecure/storage/Session.java @@ -35,6 +35,27 @@ public class Session { return hasV1Session(context, recipient) || hasV2Session(context, masterSecret, recipient); } + public static boolean hasEncryptCapableSession(Context context, + MasterSecret masterSecret, + CanonicalRecipient recipient) + { + RecipientDevice device = new RecipientDevice(recipient.getRecipientId(), + RecipientDevice.DEFAULT_DEVICE_ID); + + return hasEncryptCapableSession(context, masterSecret, recipient, device); + } + + public static boolean hasEncryptCapableSession(Context context, + MasterSecret masterSecret, + CanonicalRecipient recipient, + RecipientDevice device) + { + return + hasV1Session(context, recipient) || + (hasV2Session(context, masterSecret, recipient) && + !SessionRecordV2.needsRefresh(context, masterSecret, device)); + } + public static boolean hasRemoteIdentityKey(Context context, MasterSecret masterSecret, CanonicalRecipient recipient) diff --git a/res/values/strings.xml b/res/values/strings.xml index 11a153a339..377297ec04 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -64,7 +64,10 @@ %1$s have joined the group. %1$s has updated the group. Tap for SMS fallback + Tap for insecure fallback Fallback to SMS? + Fallback to unencrypted SMS? + This message will not be encrypted because a secure session could not be established.\n\nSend insecure message? Initiate Secure Session? diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java index 6b9477d684..0b3e71f14c 100644 --- a/src/org/thoughtcrime/securesms/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationActivity.java @@ -675,7 +675,7 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi R.attr.conversation_send_secure_button}; TypedArray drawables = obtainStyledAttributes(attributes); - if ((getRecipients() != null && getRecipients().isGroupRecipient()) || + if (isPushDestination() || (getRecipients() != null && getRecipients().isGroupRecipient()) || (isSingleConversation() && Session.hasSession(this, masterSecret, getRecipients().getPrimaryRecipient()))) { sendButton.setImageDrawable(drawables.getDrawable(1)); @@ -1074,7 +1074,7 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi private void sendMessage(boolean forcePlaintext) { try { - Recipients recipients = getRecipients(); + Recipients recipients = getRecipients(); if (recipients == null) throw new RecipientFormattingException("Badly formatted"); diff --git a/src/org/thoughtcrime/securesms/ConversationItem.java b/src/org/thoughtcrime/securesms/ConversationItem.java index 5f934b7b82..4399a3361f 100644 --- a/src/org/thoughtcrime/securesms/ConversationItem.java +++ b/src/org/thoughtcrime/securesms/ConversationItem.java @@ -25,8 +25,6 @@ import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.media.MediaScannerConnection; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; import android.net.Uri; import android.os.Build; import android.os.Environment; @@ -59,8 +57,6 @@ import org.thoughtcrime.securesms.util.DateUtils; import org.thoughtcrime.securesms.util.Emoji; import org.thoughtcrime.securesms.util.Dialogs; import org.whispersystems.textsecure.crypto.MasterSecret; -import org.whispersystems.textsecure.directory.Directory; -import org.whispersystems.textsecure.directory.NotInDirectoryException; import org.whispersystems.textsecure.storage.Session; import org.whispersystems.textsecure.util.FutureTaskListener; import org.whispersystems.textsecure.util.ListenableFutureTask; @@ -230,7 +226,7 @@ public class ConversationItem extends LinearLayout { if (messageRecord.isPending() && pushDestination && !messageRecord.isForcedSms()) { background = SENT_PUSH_PENDING; triangleBackground = SENT_PUSH_PENDING_TRIANGLE; - } else if (messageRecord.isPending() || messageRecord.isPendingFallbackApproval()) { + } else if (messageRecord.isPending() || messageRecord.isPendingSmsFallback()) { background = SENT_SMS_PENDING; triangleBackground = SENT_SMS_PENDING_TRIANGLE; } else if (messageRecord.isPush()) { @@ -265,8 +261,8 @@ public class ConversationItem extends LinearLayout { private void setStatusIcons(MessageRecord messageRecord) { failedImage.setVisibility(messageRecord.isFailed() ? View.VISIBLE : View.GONE); if (messageRecord.isOutgoing()) { - pendingIndicator.setVisibility(messageRecord.isPendingFallbackApproval() ? View.VISIBLE : View.GONE); - indicatorText.setVisibility(messageRecord.isPendingFallbackApproval() ? View.VISIBLE : View.GONE); + pendingIndicator.setVisibility(messageRecord.isPendingSmsFallback() ? View.VISIBLE : View.GONE); + indicatorText.setVisibility(messageRecord.isPendingSmsFallback() ? View.VISIBLE : View.GONE); } secureImage.setVisibility(messageRecord.isSecure() ? View.VISIBLE : View.GONE); keyImage.setVisibility(messageRecord.isKeyExchange() ? View.VISIBLE : View.GONE); @@ -278,9 +274,10 @@ public class ConversationItem extends LinearLayout { if (messageRecord.isFailed()) { dateText.setText(R.string.ConversationItem_error_sending_message); - } else if (messageRecord.isPendingFallbackApproval() && indicatorText != null) { + } else if (messageRecord.isPendingSmsFallback() && indicatorText != null) { dateText.setText(""); - indicatorText.setText(R.string.ConversationItem_click_to_approve); + if (messageRecord.isPendingSecureSmsFallback()) indicatorText.setText(R.string.ConversationItem_click_to_approve); + else indicatorText.setText(R.string.ConversationItem_click_to_approve_unencrypted); } else if (messageRecord.isPending()) { dateText.setText(" ยทยทยท "); } else { @@ -294,14 +291,15 @@ public class ConversationItem extends LinearLayout { private void setMinimumWidth() { if (indicatorText != null && indicatorText.getVisibility() == View.VISIBLE && indicatorText.getText() != null) { - conversationParent.setMinimumWidth(indicatorText.getText().length() * 20); + final float density = getResources().getDisplayMetrics().density; + conversationParent.setMinimumWidth(indicatorText.getText().length() * (int)(6.5 * density)); } else { conversationParent.setMinimumWidth(0); } } private void setEvents(MessageRecord messageRecord) { - setClickable(messageRecord.isPendingFallbackApproval() || + setClickable(messageRecord.isPendingSmsFallback() || (messageRecord.isKeyExchange() && !messageRecord.isCorruptedKeyExchange() && !messageRecord.isOutgoing())); @@ -640,23 +638,40 @@ public class ConversationItem extends LinearLayout { !messageRecord.isProcessedKeyExchange() && !messageRecord.isStaleKeyExchange()) handleKeyExchangeClicked(); - else if (messageRecord.isPendingFallbackApproval()) + else if (messageRecord.isPendingSmsFallback()) handleMessageApproval(); } } private void handleMessageApproval() { + final int title; + final int message; + if (messageRecord.isPendingSecureSmsFallback()) { + title = R.string.ConversationItem_click_to_approve_dialog_title; + message = -1; + } else { + title = R.string.ConversationItem_click_to_approve_unencrypted_dialog_title; + message = R.string.ConversationItem_click_to_approve_unencrypted_dialog_message; + } + AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(R.string.ConversationItem_click_to_approve_dialog_title); + builder.setTitle(title); + if (message > -1) builder.setMessage(message); builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { if (messageRecord.isMms()) { MmsDatabase database = DatabaseFactory.getMmsDatabase(context); + if (messageRecord.isPendingInsecureSmsFallback()) { + database.markAsInsecure(messageRecord.getId()); + } database.markAsOutbox(messageRecord.getId()); database.markAsForcedSms(messageRecord.getId()); } else { SmsDatabase database = DatabaseFactory.getSmsDatabase(context); + if (messageRecord.isPendingInsecureSmsFallback()) { + database.markAsInsecure(messageRecord.getId()); + } database.markAsOutbox(messageRecord.getId()); database.markAsForcedSms(messageRecord.getId()); } @@ -669,8 +684,11 @@ public class ConversationItem extends LinearLayout { builder.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { - if (messageRecord.isMms()) DatabaseFactory.getMmsDatabase(context).markAsSentFailed(messageRecord.getId()); - else DatabaseFactory.getSmsDatabase(context).markAsSentFailed(messageRecord.getId()); + if (messageRecord.isMms()) { + DatabaseFactory.getMmsDatabase(context).markAsSentFailed(messageRecord.getId()); + } else { + DatabaseFactory.getSmsDatabase(context).markAsSentFailed(messageRecord.getId()); + } } }); builder.show(); diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java index b2771b2530..7ba85c4a39 100644 --- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -285,8 +285,13 @@ public class MmsDatabase extends Database implements MmsSmsColumns { updateMailboxBitmask(id, 0, Types.MESSAGE_FORCE_SMS_BIT); } - public void markAsPendingApproval(long messageId) { - updateMailboxBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_PENDING_FALLBACK_APPROVAL); + public void markAsPendingSecureSmsFallback(long messageId) { + updateMailboxBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_PENDING_SECURE_SMS_FALLBACK); + notifyConversationListeners(getThreadIdForMessage(messageId)); + } + + public void markAsPendingInsecureSmsFallback(long messageId) { + updateMailboxBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_PENDING_INSECURE_SMS_FALLBACK); notifyConversationListeners(getThreadIdForMessage(messageId)); } @@ -338,6 +343,10 @@ public class MmsDatabase extends Database implements MmsSmsColumns { updateMailboxBitmask(messageId, 0, Types.SECURE_MESSAGE_BIT); } + public void markAsInsecure(long messageId) { + updateMailboxBitmask(messageId, Types.SECURE_MESSAGE_BIT, 0); + } + public void markAsPush(long messageId) { updateMailboxBitmask(messageId, 0, Types.PUSH_MESSAGE_BIT); } diff --git a/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java b/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java index 540ee73f91..bd2ac72b63 100644 --- a/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java +++ b/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java @@ -15,18 +15,20 @@ public interface MmsSmsColumns { protected static final long TOTAL_MASK = 0xFFFFFFFF; // Base Types - protected static final long BASE_TYPE_MASK = 0x1F; + protected static final long BASE_TYPE_MASK = 0x1F; - protected static final long BASE_INBOX_TYPE = 20; - protected static final long BASE_OUTBOX_TYPE = 21; - protected static final long BASE_SENDING_TYPE = 22; - protected static final long BASE_SENT_TYPE = 23; - protected static final long BASE_SENT_FAILED_TYPE = 24; - protected static final long BASE_PENDING_FALLBACK_APPROVAL = 25; + protected static final long BASE_INBOX_TYPE = 20; + protected static final long BASE_OUTBOX_TYPE = 21; + protected static final long BASE_SENDING_TYPE = 22; + protected static final long BASE_SENT_TYPE = 23; + protected static final long BASE_SENT_FAILED_TYPE = 24; + protected static final long BASE_PENDING_SECURE_SMS_FALLBACK = 25; + protected static final long BASE_PENDING_INSECURE_SMS_FALLBACK = 26; protected static final long[] OUTGOING_MESSAGE_TYPES = {BASE_OUTBOX_TYPE, BASE_SENT_TYPE, BASE_SENDING_TYPE, BASE_SENT_FAILED_TYPE, - BASE_PENDING_FALLBACK_APPROVAL}; + BASE_PENDING_SECURE_SMS_FALLBACK, + BASE_PENDING_INSECURE_SMS_FALLBACK}; // Message attributes protected static final long MESSAGE_ATTRIBUTE_MASK = 0xE0; @@ -82,8 +84,17 @@ public interface MmsSmsColumns { (type & BASE_TYPE_MASK) == BASE_SENDING_TYPE; } - public static boolean isPendingApprovalType(long type) { - return (type & BASE_TYPE_MASK) == BASE_PENDING_FALLBACK_APPROVAL; + public static boolean isPendingSmsFallbackType(long type) { + return (type & BASE_TYPE_MASK) == BASE_PENDING_INSECURE_SMS_FALLBACK || + (type & BASE_TYPE_MASK) == BASE_PENDING_SECURE_SMS_FALLBACK; + } + + public static boolean isPendingSecureSmsFallbackType(long type) { + return (type & BASE_TYPE_MASK) == BASE_PENDING_SECURE_SMS_FALLBACK; + } + + public static boolean isPendingInsecureSmsFallbackType(long type) { + return (type & BASE_TYPE_MASK) == BASE_PENDING_INSECURE_SMS_FALLBACK; } public static boolean isInboxType(long type) { diff --git a/src/org/thoughtcrime/securesms/database/SmsDatabase.java b/src/org/thoughtcrime/securesms/database/SmsDatabase.java index 0a126747c5..62a9bd0603 100644 --- a/src/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -174,6 +174,10 @@ public class SmsDatabase extends Database implements MmsSmsColumns { updateTypeBitmask(id, 0, Types.SECURE_MESSAGE_BIT); } + public void markAsInsecure(long id) { + updateTypeBitmask(id, Types.SECURE_MESSAGE_BIT, 0); + } + public void markAsPush(long id) { updateTypeBitmask(id, 0, Types.PUSH_MESSAGE_BIT); } @@ -202,8 +206,12 @@ public class SmsDatabase extends Database implements MmsSmsColumns { updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_OUTBOX_TYPE); } - public void markAsPendingApproval(long id) { - updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_PENDING_FALLBACK_APPROVAL); + public void markAsPendingSecureSmsFallback(long id) { + updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_PENDING_SECURE_SMS_FALLBACK); + } + + public void markAsPendingInsecureSmsFallback(long id) { + updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_PENDING_INSECURE_SMS_FALLBACK); } public void markAsSending(long id) { diff --git a/src/org/thoughtcrime/securesms/database/model/MessageRecord.java b/src/org/thoughtcrime/securesms/database/model/MessageRecord.java index b262d6e09f..8d8790551b 100644 --- a/src/org/thoughtcrime/securesms/database/model/MessageRecord.java +++ b/src/org/thoughtcrime/securesms/database/model/MessageRecord.java @@ -28,7 +28,6 @@ import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.util.GroupUtil; -import org.whispersystems.textsecure.util.Util; /** * The base class for message record models that are displayed in @@ -125,8 +124,16 @@ public abstract class MessageRecord extends DisplayRecord { return SmsDatabase.Types.isProcessedKeyExchange(type); } - public boolean isPendingFallbackApproval() { - return SmsDatabase.Types.isPendingApprovalType(type); + public boolean isPendingSmsFallback() { + return SmsDatabase.Types.isPendingSmsFallbackType(type); + } + + public boolean isPendingSecureSmsFallback() { + return SmsDatabase.Types.isPendingSecureSmsFallbackType(type); + } + + public boolean isPendingInsecureSmsFallback() { + return SmsDatabase.Types.isPendingInsecureSmsFallbackType(type); } public boolean isBundleKeyExchange() { diff --git a/src/org/thoughtcrime/securesms/service/MmsSender.java b/src/org/thoughtcrime/securesms/service/MmsSender.java index ab32fff2cd..5c4a96eabf 100644 --- a/src/org/thoughtcrime/securesms/service/MmsSender.java +++ b/src/org/thoughtcrime/securesms/service/MmsSender.java @@ -31,11 +31,12 @@ import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.service.SendReceiveService.ToastHandler; import org.thoughtcrime.securesms.sms.IncomingIdentityUpdateMessage; +import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException; 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.thoughtcrime.securesms.transport.UserInterventionRequiredException; import org.whispersystems.textsecure.crypto.MasterSecret; import ws.com.google.android.mms.MmsException; @@ -84,16 +85,18 @@ public class MmsSender { result.getResponseStatus()); systemStateListener.unregisterForConnectivityChange(); - } catch (UserInterventionRequiredException uire) { - Log.w("MmsSender", uire); - database.markAsPendingApproval(message.getDatabaseMessageId()); - Recipients recipients = threads.getRecipientsForThreadId(threadId); - MessageNotifier.notifyMessageDeliveryFailed(context, recipients, threadId); + } catch (InsecureFallbackApprovalException ifae) { + Log.w("MmsSender", ifae); + database.markAsPendingInsecureSmsFallback(message.getDatabaseMessageId()); + notifyMessageDeliveryFailed(context, threads, threadId); + } catch (SecureFallbackApprovalException sfae) { + Log.w("MmsSender", sfae); + database.markAsPendingSecureSmsFallback(message.getDatabaseMessageId()); + notifyMessageDeliveryFailed(context, threads, threadId); } catch (UndeliverableMessageException e) { Log.w("MmsSender", e); database.markAsSentFailed(message.getDatabaseMessageId()); - Recipients recipients = threads.getRecipientsForThreadId(threadId); - MessageNotifier.notifyMessageDeliveryFailed(context, recipients, threadId); + notifyMessageDeliveryFailed(context, threads, threadId); } catch (UntrustedIdentityException uie) { IncomingIdentityUpdateMessage identityUpdateMessage = IncomingIdentityUpdateMessage.createFor(message.getTo()[0].getString(), uie.getIdentityKey()); DatabaseFactory.getEncryptingSmsDatabase(context).insertMessageInbox(masterSecret, identityUpdateMessage); @@ -117,6 +120,11 @@ public class MmsSender { } } + private static void notifyMessageDeliveryFailed(Context context, ThreadDatabase threads, long threadId) { + Recipients recipients = threads.getRecipientsForThreadId(threadId); + MessageNotifier.notifyMessageDeliveryFailed(context, recipients, threadId); + } + private void scheduleQuickRetryAlarm() { ((AlarmManager)context.getSystemService(Context.ALARM_SERVICE)) .set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + (30 * 1000), diff --git a/src/org/thoughtcrime/securesms/service/SmsSender.java b/src/org/thoughtcrime/securesms/service/SmsSender.java index daa6e53aed..e42319967b 100644 --- a/src/org/thoughtcrime/securesms/service/SmsSender.java +++ b/src/org/thoughtcrime/securesms/service/SmsSender.java @@ -36,12 +36,12 @@ import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.service.SendReceiveService.ToastHandler; import org.thoughtcrime.securesms.sms.IncomingIdentityUpdateMessage; +import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException; 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.thoughtcrime.securesms.transport.UserInterventionRequiredException; -import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.storage.Session; @@ -86,9 +86,13 @@ public class SmsSender { database.markAsSending(record.getId()); transport.deliver(record); - } catch (UserInterventionRequiredException uire) { - Log.w("SmsSender", uire); - DatabaseFactory.getSmsDatabase(context).markAsPendingApproval(record.getId()); + } catch (InsecureFallbackApprovalException ifae) { + Log.w("SmsSender", ifae); + DatabaseFactory.getSmsDatabase(context).markAsPendingInsecureSmsFallback(record.getId()); + MessageNotifier.notifyMessageDeliveryFailed(context, record.getRecipients(), record.getThreadId()); + } catch (SecureFallbackApprovalException sfae) { + Log.w("SmsSender", sfae); + DatabaseFactory.getSmsDatabase(context).markAsPendingSecureSmsFallback(record.getId()); MessageNotifier.notifyMessageDeliveryFailed(context, record.getRecipients(), record.getThreadId()); } catch (UntrustedIdentityException e) { Log.w("SmsSender", e); diff --git a/src/org/thoughtcrime/securesms/transport/InsecureFallbackApprovalException.java b/src/org/thoughtcrime/securesms/transport/InsecureFallbackApprovalException.java new file mode 100644 index 0000000000..ae72d715f6 --- /dev/null +++ b/src/org/thoughtcrime/securesms/transport/InsecureFallbackApprovalException.java @@ -0,0 +1,7 @@ +package org.thoughtcrime.securesms.transport; + +public class InsecureFallbackApprovalException extends Exception { + public InsecureFallbackApprovalException(String detailMessage) { + super(detailMessage); + } +} diff --git a/src/org/thoughtcrime/securesms/transport/MmsTransport.java b/src/org/thoughtcrime/securesms/transport/MmsTransport.java index 3bb2a04f45..d7005b8817 100644 --- a/src/org/thoughtcrime/securesms/transport/MmsTransport.java +++ b/src/org/thoughtcrime/securesms/transport/MmsTransport.java @@ -36,6 +36,7 @@ import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.SessionCipher; import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; import org.whispersystems.textsecure.storage.RecipientDevice; +import org.whispersystems.textsecure.storage.Session; import org.whispersystems.textsecure.util.Hex; import java.io.IOException; @@ -62,7 +63,9 @@ public class MmsTransport { this.radio = MmsRadio.getInstance(context); } - public MmsSendResult deliver(SendReq message) throws UndeliverableMessageException { + public MmsSendResult deliver(SendReq message) throws UndeliverableMessageException, + InsecureFallbackApprovalException + { if (TextSecurePreferences.isPushRegistered(context) && !TextSecurePreferences.isSmsFallbackEnabled(context)) { @@ -109,7 +112,7 @@ public class MmsTransport { } private MmsSendResult sendMms(SendReq message, boolean usingMmsRadio, boolean useProxy) - throws IOException, UndeliverableMessageException + throws IOException, UndeliverableMessageException, InsecureFallbackApprovalException { String number = ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE)).getLine1Number(); boolean upgradedSecure = false; @@ -141,7 +144,7 @@ public class MmsTransport { } } - private SendReq getEncryptedMessage(SendReq pdu) { + private SendReq getEncryptedMessage(SendReq pdu) throws InsecureFallbackApprovalException { EncodedStringValue[] encodedRecipient = pdu.getTo(); String recipient = encodedRecipient[0].getString(); byte[] pduBytes = new PduComposer(context, pdu).make(); @@ -162,11 +165,16 @@ public class MmsTransport { return encryptedPdu; } - private byte[] getEncryptedPdu(MasterSecret masterSecret, String recipientString, byte[] pduBytes) { + private byte[] getEncryptedPdu(MasterSecret masterSecret, String recipientString, byte[] pduBytes) throws InsecureFallbackApprovalException { try { TextTransport transportDetails = new TextTransport(); Recipient recipient = RecipientFactory.getRecipientsFromString(context, recipientString, false).getPrimaryRecipient(); RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID); + + if (!Session.hasEncryptCapableSession(context, masterSecret, recipient, recipientDevice)) { + throw new InsecureFallbackApprovalException("No session exists for this secure message."); + } + SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipientDevice); CiphertextMessage ciphertextMessage = sessionCipher.encrypt(pduBytes); diff --git a/src/org/thoughtcrime/securesms/transport/SecureFallbackApprovalException.java b/src/org/thoughtcrime/securesms/transport/SecureFallbackApprovalException.java new file mode 100644 index 0000000000..011594ae54 --- /dev/null +++ b/src/org/thoughtcrime/securesms/transport/SecureFallbackApprovalException.java @@ -0,0 +1,7 @@ +package org.thoughtcrime.securesms.transport; + +public class SecureFallbackApprovalException extends Exception { + public SecureFallbackApprovalException(String detailMessage) { + super(detailMessage); + } +} diff --git a/src/org/thoughtcrime/securesms/transport/SmsTransport.java b/src/org/thoughtcrime/securesms/transport/SmsTransport.java index 48c184ce3c..0f337157a8 100644 --- a/src/org/thoughtcrime/securesms/transport/SmsTransport.java +++ b/src/org/thoughtcrime/securesms/transport/SmsTransport.java @@ -33,6 +33,7 @@ import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.SessionCipher; import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; import org.whispersystems.textsecure.storage.RecipientDevice; +import org.whispersystems.textsecure.storage.Session; import java.util.ArrayList; @@ -46,7 +47,9 @@ public class SmsTransport extends BaseTransport { this.masterSecret = masterSecret; } - public void deliver(SmsMessageRecord message) throws UndeliverableMessageException { + public void deliver(SmsMessageRecord message) throws UndeliverableMessageException, + InsecureFallbackApprovalException + { if (!TextSecurePreferences.isSmsNonDataOutEnabled(context) && !TextSecurePreferences.isSmsFallbackEnabled(context)) { throw new UndeliverableMessageException("SMS Transport is not enabled!"); } @@ -58,7 +61,9 @@ public class SmsTransport extends BaseTransport { } } - private void deliverSecureMessage(SmsMessageRecord message) throws UndeliverableMessageException { + private void deliverSecureMessage(SmsMessageRecord message) throws UndeliverableMessageException, + InsecureFallbackApprovalException + { MultipartSmsMessageHandler multipartMessageHandler = new MultipartSmsMessageHandler(); OutgoingTextMessage transportMessage = OutgoingTextMessage.from(message); @@ -161,9 +166,16 @@ public class SmsTransport extends BaseTransport { private OutgoingTextMessage getAsymmetricEncrypt(MasterSecret masterSecret, OutgoingTextMessage message) + throws InsecureFallbackApprovalException { Recipient recipient = message.getRecipients().getPrimaryRecipient(); - RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID); + RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), + RecipientDevice.DEFAULT_DEVICE_ID); + + if (!Session.hasEncryptCapableSession(context, masterSecret, recipient, recipientDevice)) { + throw new InsecureFallbackApprovalException("No session exists for this secure message."); + } + String body = message.getMessageBody(); SmsTransportDetails transportDetails = new SmsTransportDetails(); SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipientDevice); diff --git a/src/org/thoughtcrime/securesms/transport/UniversalTransport.java b/src/org/thoughtcrime/securesms/transport/UniversalTransport.java index 46a9c2457c..80c84dba1a 100644 --- a/src/org/thoughtcrime/securesms/transport/UniversalTransport.java +++ b/src/org/thoughtcrime/securesms/transport/UniversalTransport.java @@ -24,6 +24,7 @@ import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.mms.MmsSendResult; 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.sms.IncomingGroupMessage; import org.thoughtcrime.securesms.sms.IncomingIdentityUpdateMessage; @@ -36,6 +37,7 @@ 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.Session; import org.whispersystems.textsecure.util.DirectoryUtil; import org.whispersystems.textsecure.util.InvalidNumberException; @@ -60,7 +62,8 @@ public class UniversalTransport { } public void deliver(SmsMessageRecord message) - throws UndeliverableMessageException, UntrustedIdentityException, RetryLaterException, UserInterventionRequiredException + throws UndeliverableMessageException, UntrustedIdentityException, RetryLaterException, + SecureFallbackApprovalException, InsecureFallbackApprovalException { if (!TextSecurePreferences.isPushRegistered(context)) { smsTransport.deliver(message); @@ -99,7 +102,8 @@ public class UniversalTransport { } public MmsSendResult deliver(SendReq mediaMessage, long threadId) - throws UndeliverableMessageException, RetryLaterException, UntrustedIdentityException, UserInterventionRequiredException + throws UndeliverableMessageException, RetryLaterException, UntrustedIdentityException, + SecureFallbackApprovalException, InsecureFallbackApprovalException { if (Util.isEmpty(mediaMessage.getTo())) { return mmsTransport.deliver(mediaMessage); @@ -155,28 +159,42 @@ public class UniversalTransport { } private MmsSendResult fallbackOrAskApproval(SendReq mediaMessage, String destination) - throws UserInterventionRequiredException, UndeliverableMessageException + throws SecureFallbackApprovalException, UndeliverableMessageException, InsecureFallbackApprovalException { - boolean isSmsFallbackApprovalRequired = isSmsFallbackApprovalRequired(destination); - if (!isSmsFallbackApprovalRequired) { - Log.i("UniversalTransport", "Falling back to MMS without user intervention"); - return mmsTransport.deliver(mediaMessage); - } else { - Log.i("UniversalTransport", "Marking message as pending user approval per their settings"); - throw new UserInterventionRequiredException("Pending user approval for fallback to SMS"); + try { + Recipient recipient = RecipientFactory.getRecipientsFromString(context, destination, false).getPrimaryRecipient(); + boolean isSmsFallbackApprovalRequired = isSmsFallbackApprovalRequired(destination); + + if (!isSmsFallbackApprovalRequired) { + Log.w("UniversalTransport", "Falling back to MMS"); + return mmsTransport.deliver(mediaMessage); + } else if (!Session.hasEncryptCapableSession(context, masterSecret, recipient)) { + Log.w("UniversalTransport", "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"); + throw new SecureFallbackApprovalException("Pending user approval for fallback secure to SMS"); + } + } catch (RecipientFormattingException rfe) { + throw new UndeliverableMessageException(rfe); } } private void fallbackOrAskApproval(SmsMessageRecord smsMessage, String destination) - throws UserInterventionRequiredException, UndeliverableMessageException + throws SecureFallbackApprovalException, UndeliverableMessageException, InsecureFallbackApprovalException { - boolean isSmsFallbackApprovalRequired = isSmsFallbackApprovalRequired(destination); + Recipient recipient = smsMessage.getIndividualRecipient(); + boolean isSmsFallbackApprovalRequired = isSmsFallbackApprovalRequired(destination); + if (!isSmsFallbackApprovalRequired) { - Log.i("UniversalTransport", "Falling back to SMS without user intervention"); + Log.w("UniversalTransport", "Falling back to SMS"); smsTransport.deliver(smsMessage); + } else if (!Session.hasEncryptCapableSession(context, masterSecret, recipient)) { + Log.w("UniversalTransport", "Marking message as pending insecure fallback."); + throw new InsecureFallbackApprovalException("Pending user approval for fallback to insecure SMS"); } else { - Log.i("UniversalTransport", "Marking message as pending user approval per their settings"); - throw new UserInterventionRequiredException("Pending user approval for fallback to SMS"); + Log.w("UniversalTransport", "Marking message as pending secure fallback."); + throw new SecureFallbackApprovalException("Pending user approval for fallback to secure SMS"); } } @@ -218,7 +236,6 @@ public class UniversalTransport { } } - public boolean isMultipleRecipients(SendReq mediaMessage) { int recipientCount = 0; @@ -267,9 +284,9 @@ public class UniversalTransport { return directory.isActiveNumber(destination); } catch (NotInDirectoryException e) { try { - PushServiceSocket socket = PushServiceSocketFactory.create(context); - String contactToken = DirectoryUtil.getDirectoryServerToken(destination); - ContactTokenDetails registeredUser = socket.getContactTokenDetails(contactToken); + PushServiceSocket socket = PushServiceSocketFactory.create(context); + String contactToken = DirectoryUtil.getDirectoryServerToken(destination); + ContactTokenDetails registeredUser = socket.getContactTokenDetails(contactToken); if (registeredUser == null) { registeredUser = new ContactTokenDetails(); diff --git a/src/org/thoughtcrime/securesms/transport/UserInterventionRequiredException.java b/src/org/thoughtcrime/securesms/transport/UserInterventionRequiredException.java deleted file mode 100644 index 2622e80207..0000000000 --- a/src/org/thoughtcrime/securesms/transport/UserInterventionRequiredException.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.thoughtcrime.securesms.transport; - -public class UserInterventionRequiredException extends Exception { - public UserInterventionRequiredException(String detailMessage) { - super(detailMessage); - } -}