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);
- }
-}