UX for unencrypted fallback case

pull/1/head
Jake McGinty 10 years ago committed by Moxie Marlinspike
parent 40629a3bcf
commit 832763f695

@ -35,6 +35,27 @@ public class Session {
return hasV1Session(context, recipient) || hasV2Session(context, masterSecret, recipient); 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, public static boolean hasRemoteIdentityKey(Context context,
MasterSecret masterSecret, MasterSecret masterSecret,
CanonicalRecipient recipient) CanonicalRecipient recipient)

@ -64,7 +64,10 @@
<string name="ConversationItem_group_action_joined">%1$s have joined the group.</string> <string name="ConversationItem_group_action_joined">%1$s have joined the group.</string>
<string name="ConversationItem_group_action_modify">%1$s has updated the group.</string> <string name="ConversationItem_group_action_modify">%1$s has updated the group.</string>
<string name="ConversationItem_click_to_approve">Tap for SMS fallback</string> <string name="ConversationItem_click_to_approve">Tap for SMS fallback</string>
<string name="ConversationItem_click_to_approve_unencrypted">Tap for insecure fallback</string>
<string name="ConversationItem_click_to_approve_dialog_title">Fallback to SMS?</string> <string name="ConversationItem_click_to_approve_dialog_title">Fallback to SMS?</string>
<string name="ConversationItem_click_to_approve_unencrypted_dialog_title">Fallback to unencrypted SMS?</string>
<string name="ConversationItem_click_to_approve_unencrypted_dialog_message">This message will <b>not</b> be encrypted because a secure session could not be established.\n\nSend insecure message?</string>
<!-- ConversationActivity --> <!-- ConversationActivity -->
<string name="ConversationActivity_initiate_secure_session_question">Initiate Secure Session?</string> <string name="ConversationActivity_initiate_secure_session_question">Initiate Secure Session?</string>

@ -675,7 +675,7 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
R.attr.conversation_send_secure_button}; R.attr.conversation_send_secure_button};
TypedArray drawables = obtainStyledAttributes(attributes); TypedArray drawables = obtainStyledAttributes(attributes);
if ((getRecipients() != null && getRecipients().isGroupRecipient()) || if (isPushDestination() || (getRecipients() != null && getRecipients().isGroupRecipient()) ||
(isSingleConversation() && Session.hasSession(this, masterSecret, getRecipients().getPrimaryRecipient()))) (isSingleConversation() && Session.hasSession(this, masterSecret, getRecipients().getPrimaryRecipient())))
{ {
sendButton.setImageDrawable(drawables.getDrawable(1)); sendButton.setImageDrawable(drawables.getDrawable(1));
@ -1074,7 +1074,7 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
private void sendMessage(boolean forcePlaintext) { private void sendMessage(boolean forcePlaintext) {
try { try {
Recipients recipients = getRecipients(); Recipients recipients = getRecipients();
if (recipients == null) if (recipients == null)
throw new RecipientFormattingException("Badly formatted"); throw new RecipientFormattingException("Badly formatted");

@ -25,8 +25,6 @@ import android.content.res.TypedArray;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.ColorDrawable;
import android.media.MediaScannerConnection; import android.media.MediaScannerConnection;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Environment; 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.Emoji;
import org.thoughtcrime.securesms.util.Dialogs; import org.thoughtcrime.securesms.util.Dialogs;
import org.whispersystems.textsecure.crypto.MasterSecret; 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.storage.Session;
import org.whispersystems.textsecure.util.FutureTaskListener; import org.whispersystems.textsecure.util.FutureTaskListener;
import org.whispersystems.textsecure.util.ListenableFutureTask; import org.whispersystems.textsecure.util.ListenableFutureTask;
@ -230,7 +226,7 @@ public class ConversationItem extends LinearLayout {
if (messageRecord.isPending() && pushDestination && !messageRecord.isForcedSms()) { if (messageRecord.isPending() && pushDestination && !messageRecord.isForcedSms()) {
background = SENT_PUSH_PENDING; background = SENT_PUSH_PENDING;
triangleBackground = SENT_PUSH_PENDING_TRIANGLE; triangleBackground = SENT_PUSH_PENDING_TRIANGLE;
} else if (messageRecord.isPending() || messageRecord.isPendingFallbackApproval()) { } else if (messageRecord.isPending() || messageRecord.isPendingSmsFallback()) {
background = SENT_SMS_PENDING; background = SENT_SMS_PENDING;
triangleBackground = SENT_SMS_PENDING_TRIANGLE; triangleBackground = SENT_SMS_PENDING_TRIANGLE;
} else if (messageRecord.isPush()) { } else if (messageRecord.isPush()) {
@ -265,8 +261,8 @@ public class ConversationItem extends LinearLayout {
private void setStatusIcons(MessageRecord messageRecord) { private void setStatusIcons(MessageRecord messageRecord) {
failedImage.setVisibility(messageRecord.isFailed() ? View.VISIBLE : View.GONE); failedImage.setVisibility(messageRecord.isFailed() ? View.VISIBLE : View.GONE);
if (messageRecord.isOutgoing()) { if (messageRecord.isOutgoing()) {
pendingIndicator.setVisibility(messageRecord.isPendingFallbackApproval() ? View.VISIBLE : View.GONE); pendingIndicator.setVisibility(messageRecord.isPendingSmsFallback() ? View.VISIBLE : View.GONE);
indicatorText.setVisibility(messageRecord.isPendingFallbackApproval() ? View.VISIBLE : View.GONE); indicatorText.setVisibility(messageRecord.isPendingSmsFallback() ? View.VISIBLE : View.GONE);
} }
secureImage.setVisibility(messageRecord.isSecure() ? View.VISIBLE : View.GONE); secureImage.setVisibility(messageRecord.isSecure() ? View.VISIBLE : View.GONE);
keyImage.setVisibility(messageRecord.isKeyExchange() ? View.VISIBLE : View.GONE); keyImage.setVisibility(messageRecord.isKeyExchange() ? View.VISIBLE : View.GONE);
@ -278,9 +274,10 @@ public class ConversationItem extends LinearLayout {
if (messageRecord.isFailed()) { if (messageRecord.isFailed()) {
dateText.setText(R.string.ConversationItem_error_sending_message); dateText.setText(R.string.ConversationItem_error_sending_message);
} else if (messageRecord.isPendingFallbackApproval() && indicatorText != null) { } else if (messageRecord.isPendingSmsFallback() && indicatorText != null) {
dateText.setText(""); 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()) { } else if (messageRecord.isPending()) {
dateText.setText(" ··· "); dateText.setText(" ··· ");
} else { } else {
@ -294,14 +291,15 @@ public class ConversationItem extends LinearLayout {
private void setMinimumWidth() { private void setMinimumWidth() {
if (indicatorText != null && indicatorText.getVisibility() == View.VISIBLE && indicatorText.getText() != null) { 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 { } else {
conversationParent.setMinimumWidth(0); conversationParent.setMinimumWidth(0);
} }
} }
private void setEvents(MessageRecord messageRecord) { private void setEvents(MessageRecord messageRecord) {
setClickable(messageRecord.isPendingFallbackApproval() || setClickable(messageRecord.isPendingSmsFallback() ||
(messageRecord.isKeyExchange() && (messageRecord.isKeyExchange() &&
!messageRecord.isCorruptedKeyExchange() && !messageRecord.isCorruptedKeyExchange() &&
!messageRecord.isOutgoing())); !messageRecord.isOutgoing()));
@ -640,23 +638,40 @@ public class ConversationItem extends LinearLayout {
!messageRecord.isProcessedKeyExchange() && !messageRecord.isProcessedKeyExchange() &&
!messageRecord.isStaleKeyExchange()) !messageRecord.isStaleKeyExchange())
handleKeyExchangeClicked(); handleKeyExchangeClicked();
else if (messageRecord.isPendingFallbackApproval()) else if (messageRecord.isPendingSmsFallback())
handleMessageApproval(); handleMessageApproval();
} }
} }
private void 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); 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() { builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialogInterface, int i) { public void onClick(DialogInterface dialogInterface, int i) {
if (messageRecord.isMms()) { if (messageRecord.isMms()) {
MmsDatabase database = DatabaseFactory.getMmsDatabase(context); MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
if (messageRecord.isPendingInsecureSmsFallback()) {
database.markAsInsecure(messageRecord.getId());
}
database.markAsOutbox(messageRecord.getId()); database.markAsOutbox(messageRecord.getId());
database.markAsForcedSms(messageRecord.getId()); database.markAsForcedSms(messageRecord.getId());
} else { } else {
SmsDatabase database = DatabaseFactory.getSmsDatabase(context); SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
if (messageRecord.isPendingInsecureSmsFallback()) {
database.markAsInsecure(messageRecord.getId());
}
database.markAsOutbox(messageRecord.getId()); database.markAsOutbox(messageRecord.getId());
database.markAsForcedSms(messageRecord.getId()); database.markAsForcedSms(messageRecord.getId());
} }
@ -669,8 +684,11 @@ public class ConversationItem extends LinearLayout {
builder.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() { builder.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialogInterface, int i) { public void onClick(DialogInterface dialogInterface, int i) {
if (messageRecord.isMms()) DatabaseFactory.getMmsDatabase(context).markAsSentFailed(messageRecord.getId()); if (messageRecord.isMms()) {
else DatabaseFactory.getSmsDatabase(context).markAsSentFailed(messageRecord.getId()); DatabaseFactory.getMmsDatabase(context).markAsSentFailed(messageRecord.getId());
} else {
DatabaseFactory.getSmsDatabase(context).markAsSentFailed(messageRecord.getId());
}
} }
}); });
builder.show(); builder.show();

@ -285,8 +285,13 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
updateMailboxBitmask(id, 0, Types.MESSAGE_FORCE_SMS_BIT); updateMailboxBitmask(id, 0, Types.MESSAGE_FORCE_SMS_BIT);
} }
public void markAsPendingApproval(long messageId) { public void markAsPendingSecureSmsFallback(long messageId) {
updateMailboxBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_PENDING_FALLBACK_APPROVAL); 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)); notifyConversationListeners(getThreadIdForMessage(messageId));
} }
@ -338,6 +343,10 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
updateMailboxBitmask(messageId, 0, Types.SECURE_MESSAGE_BIT); updateMailboxBitmask(messageId, 0, Types.SECURE_MESSAGE_BIT);
} }
public void markAsInsecure(long messageId) {
updateMailboxBitmask(messageId, Types.SECURE_MESSAGE_BIT, 0);
}
public void markAsPush(long messageId) { public void markAsPush(long messageId) {
updateMailboxBitmask(messageId, 0, Types.PUSH_MESSAGE_BIT); updateMailboxBitmask(messageId, 0, Types.PUSH_MESSAGE_BIT);
} }

@ -15,18 +15,20 @@ public interface MmsSmsColumns {
protected static final long TOTAL_MASK = 0xFFFFFFFF; protected static final long TOTAL_MASK = 0xFFFFFFFF;
// Base Types // 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_INBOX_TYPE = 20;
protected static final long BASE_OUTBOX_TYPE = 21; protected static final long BASE_OUTBOX_TYPE = 21;
protected static final long BASE_SENDING_TYPE = 22; protected static final long BASE_SENDING_TYPE = 22;
protected static final long BASE_SENT_TYPE = 23; protected static final long BASE_SENT_TYPE = 23;
protected static final long BASE_SENT_FAILED_TYPE = 24; protected static final long BASE_SENT_FAILED_TYPE = 24;
protected static final long BASE_PENDING_FALLBACK_APPROVAL = 25; 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, protected static final long[] OUTGOING_MESSAGE_TYPES = {BASE_OUTBOX_TYPE, BASE_SENT_TYPE,
BASE_SENDING_TYPE, BASE_SENT_FAILED_TYPE, BASE_SENDING_TYPE, BASE_SENT_FAILED_TYPE,
BASE_PENDING_FALLBACK_APPROVAL}; BASE_PENDING_SECURE_SMS_FALLBACK,
BASE_PENDING_INSECURE_SMS_FALLBACK};
// Message attributes // Message attributes
protected static final long MESSAGE_ATTRIBUTE_MASK = 0xE0; protected static final long MESSAGE_ATTRIBUTE_MASK = 0xE0;
@ -82,8 +84,17 @@ public interface MmsSmsColumns {
(type & BASE_TYPE_MASK) == BASE_SENDING_TYPE; (type & BASE_TYPE_MASK) == BASE_SENDING_TYPE;
} }
public static boolean isPendingApprovalType(long type) { public static boolean isPendingSmsFallbackType(long type) {
return (type & BASE_TYPE_MASK) == BASE_PENDING_FALLBACK_APPROVAL; 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) { public static boolean isInboxType(long type) {

@ -174,6 +174,10 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
updateTypeBitmask(id, 0, Types.SECURE_MESSAGE_BIT); updateTypeBitmask(id, 0, Types.SECURE_MESSAGE_BIT);
} }
public void markAsInsecure(long id) {
updateTypeBitmask(id, Types.SECURE_MESSAGE_BIT, 0);
}
public void markAsPush(long id) { public void markAsPush(long id) {
updateTypeBitmask(id, 0, Types.PUSH_MESSAGE_BIT); 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); updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_OUTBOX_TYPE);
} }
public void markAsPendingApproval(long id) { public void markAsPendingSecureSmsFallback(long id) {
updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_PENDING_FALLBACK_APPROVAL); 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) { public void markAsSending(long id) {

@ -28,7 +28,6 @@ import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.GroupUtil;
import org.whispersystems.textsecure.util.Util;
/** /**
* The base class for message record models that are displayed in * 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); return SmsDatabase.Types.isProcessedKeyExchange(type);
} }
public boolean isPendingFallbackApproval() { public boolean isPendingSmsFallback() {
return SmsDatabase.Types.isPendingApprovalType(type); return SmsDatabase.Types.isPendingSmsFallbackType(type);
}
public boolean isPendingSecureSmsFallback() {
return SmsDatabase.Types.isPendingSecureSmsFallbackType(type);
}
public boolean isPendingInsecureSmsFallback() {
return SmsDatabase.Types.isPendingInsecureSmsFallbackType(type);
} }
public boolean isBundleKeyExchange() { public boolean isBundleKeyExchange() {

@ -31,11 +31,12 @@ import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.service.SendReceiveService.ToastHandler; import org.thoughtcrime.securesms.service.SendReceiveService.ToastHandler;
import org.thoughtcrime.securesms.sms.IncomingIdentityUpdateMessage; import org.thoughtcrime.securesms.sms.IncomingIdentityUpdateMessage;
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
import org.thoughtcrime.securesms.transport.RetryLaterException; import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.thoughtcrime.securesms.transport.SecureFallbackApprovalException;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException; import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import org.thoughtcrime.securesms.transport.UniversalTransport; import org.thoughtcrime.securesms.transport.UniversalTransport;
import org.thoughtcrime.securesms.transport.UntrustedIdentityException; import org.thoughtcrime.securesms.transport.UntrustedIdentityException;
import org.thoughtcrime.securesms.transport.UserInterventionRequiredException;
import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.MasterSecret;
import ws.com.google.android.mms.MmsException; import ws.com.google.android.mms.MmsException;
@ -84,16 +85,18 @@ public class MmsSender {
result.getResponseStatus()); result.getResponseStatus());
systemStateListener.unregisterForConnectivityChange(); systemStateListener.unregisterForConnectivityChange();
} catch (UserInterventionRequiredException uire) { } catch (InsecureFallbackApprovalException ifae) {
Log.w("MmsSender", uire); Log.w("MmsSender", ifae);
database.markAsPendingApproval(message.getDatabaseMessageId()); database.markAsPendingInsecureSmsFallback(message.getDatabaseMessageId());
Recipients recipients = threads.getRecipientsForThreadId(threadId); notifyMessageDeliveryFailed(context, threads, threadId);
MessageNotifier.notifyMessageDeliveryFailed(context, recipients, threadId); } catch (SecureFallbackApprovalException sfae) {
Log.w("MmsSender", sfae);
database.markAsPendingSecureSmsFallback(message.getDatabaseMessageId());
notifyMessageDeliveryFailed(context, threads, threadId);
} catch (UndeliverableMessageException e) { } catch (UndeliverableMessageException e) {
Log.w("MmsSender", e); Log.w("MmsSender", e);
database.markAsSentFailed(message.getDatabaseMessageId()); database.markAsSentFailed(message.getDatabaseMessageId());
Recipients recipients = threads.getRecipientsForThreadId(threadId); notifyMessageDeliveryFailed(context, threads, threadId);
MessageNotifier.notifyMessageDeliveryFailed(context, recipients, threadId);
} catch (UntrustedIdentityException uie) { } catch (UntrustedIdentityException uie) {
IncomingIdentityUpdateMessage identityUpdateMessage = IncomingIdentityUpdateMessage.createFor(message.getTo()[0].getString(), uie.getIdentityKey()); IncomingIdentityUpdateMessage identityUpdateMessage = IncomingIdentityUpdateMessage.createFor(message.getTo()[0].getString(), uie.getIdentityKey());
DatabaseFactory.getEncryptingSmsDatabase(context).insertMessageInbox(masterSecret, identityUpdateMessage); 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() { private void scheduleQuickRetryAlarm() {
((AlarmManager)context.getSystemService(Context.ALARM_SERVICE)) ((AlarmManager)context.getSystemService(Context.ALARM_SERVICE))
.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + (30 * 1000), .set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + (30 * 1000),

@ -36,12 +36,12 @@ import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.service.SendReceiveService.ToastHandler; import org.thoughtcrime.securesms.service.SendReceiveService.ToastHandler;
import org.thoughtcrime.securesms.sms.IncomingIdentityUpdateMessage; import org.thoughtcrime.securesms.sms.IncomingIdentityUpdateMessage;
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
import org.thoughtcrime.securesms.transport.RetryLaterException; import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.thoughtcrime.securesms.transport.SecureFallbackApprovalException;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException; import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import org.thoughtcrime.securesms.transport.UniversalTransport; import org.thoughtcrime.securesms.transport.UniversalTransport;
import org.thoughtcrime.securesms.transport.UntrustedIdentityException; 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.crypto.MasterSecret;
import org.whispersystems.textsecure.storage.Session; import org.whispersystems.textsecure.storage.Session;
@ -86,9 +86,13 @@ public class SmsSender {
database.markAsSending(record.getId()); database.markAsSending(record.getId());
transport.deliver(record); transport.deliver(record);
} catch (UserInterventionRequiredException uire) { } catch (InsecureFallbackApprovalException ifae) {
Log.w("SmsSender", uire); Log.w("SmsSender", ifae);
DatabaseFactory.getSmsDatabase(context).markAsPendingApproval(record.getId()); 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()); MessageNotifier.notifyMessageDeliveryFailed(context, record.getRecipients(), record.getThreadId());
} catch (UntrustedIdentityException e) { } catch (UntrustedIdentityException e) {
Log.w("SmsSender", e); Log.w("SmsSender", e);

@ -0,0 +1,7 @@
package org.thoughtcrime.securesms.transport;
public class InsecureFallbackApprovalException extends Exception {
public InsecureFallbackApprovalException(String detailMessage) {
super(detailMessage);
}
}

@ -36,6 +36,7 @@ import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.SessionCipher; import org.whispersystems.textsecure.crypto.SessionCipher;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
import org.whispersystems.textsecure.storage.RecipientDevice; import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.Session;
import org.whispersystems.textsecure.util.Hex; import org.whispersystems.textsecure.util.Hex;
import java.io.IOException; import java.io.IOException;
@ -62,7 +63,9 @@ public class MmsTransport {
this.radio = MmsRadio.getInstance(context); this.radio = MmsRadio.getInstance(context);
} }
public MmsSendResult deliver(SendReq message) throws UndeliverableMessageException { public MmsSendResult deliver(SendReq message) throws UndeliverableMessageException,
InsecureFallbackApprovalException
{
if (TextSecurePreferences.isPushRegistered(context) && if (TextSecurePreferences.isPushRegistered(context) &&
!TextSecurePreferences.isSmsFallbackEnabled(context)) !TextSecurePreferences.isSmsFallbackEnabled(context))
{ {
@ -109,7 +112,7 @@ public class MmsTransport {
} }
private MmsSendResult sendMms(SendReq message, boolean usingMmsRadio, boolean useProxy) 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(); String number = ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE)).getLine1Number();
boolean upgradedSecure = false; 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(); EncodedStringValue[] encodedRecipient = pdu.getTo();
String recipient = encodedRecipient[0].getString(); String recipient = encodedRecipient[0].getString();
byte[] pduBytes = new PduComposer(context, pdu).make(); byte[] pduBytes = new PduComposer(context, pdu).make();
@ -162,11 +165,16 @@ public class MmsTransport {
return encryptedPdu; return encryptedPdu;
} }
private byte[] getEncryptedPdu(MasterSecret masterSecret, String recipientString, byte[] pduBytes) { private byte[] getEncryptedPdu(MasterSecret masterSecret, String recipientString, byte[] pduBytes) throws InsecureFallbackApprovalException {
try { try {
TextTransport transportDetails = new TextTransport(); TextTransport transportDetails = new TextTransport();
Recipient recipient = RecipientFactory.getRecipientsFromString(context, recipientString, false).getPrimaryRecipient(); Recipient recipient = RecipientFactory.getRecipientsFromString(context, recipientString, false).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.");
}
SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipientDevice); SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipientDevice);
CiphertextMessage ciphertextMessage = sessionCipher.encrypt(pduBytes); CiphertextMessage ciphertextMessage = sessionCipher.encrypt(pduBytes);

@ -0,0 +1,7 @@
package org.thoughtcrime.securesms.transport;
public class SecureFallbackApprovalException extends Exception {
public SecureFallbackApprovalException(String detailMessage) {
super(detailMessage);
}
}

@ -33,6 +33,7 @@ import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.SessionCipher; import org.whispersystems.textsecure.crypto.SessionCipher;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
import org.whispersystems.textsecure.storage.RecipientDevice; import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.Session;
import java.util.ArrayList; import java.util.ArrayList;
@ -46,7 +47,9 @@ public class SmsTransport extends BaseTransport {
this.masterSecret = masterSecret; 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)) { if (!TextSecurePreferences.isSmsNonDataOutEnabled(context) && !TextSecurePreferences.isSmsFallbackEnabled(context)) {
throw new UndeliverableMessageException("SMS Transport is not enabled!"); 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(); MultipartSmsMessageHandler multipartMessageHandler = new MultipartSmsMessageHandler();
OutgoingTextMessage transportMessage = OutgoingTextMessage.from(message); OutgoingTextMessage transportMessage = OutgoingTextMessage.from(message);
@ -161,9 +166,16 @@ public class SmsTransport extends BaseTransport {
private OutgoingTextMessage getAsymmetricEncrypt(MasterSecret masterSecret, private OutgoingTextMessage getAsymmetricEncrypt(MasterSecret masterSecret,
OutgoingTextMessage message) OutgoingTextMessage message)
throws InsecureFallbackApprovalException
{ {
Recipient recipient = message.getRecipients().getPrimaryRecipient(); 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(); String body = message.getMessageBody();
SmsTransportDetails transportDetails = new SmsTransportDetails(); SmsTransportDetails transportDetails = new SmsTransportDetails();
SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipientDevice); SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipientDevice);

@ -24,6 +24,7 @@ import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.mms.MmsSendResult; import org.thoughtcrime.securesms.mms.MmsSendResult;
import org.thoughtcrime.securesms.push.PushServiceSocketFactory; import org.thoughtcrime.securesms.push.PushServiceSocketFactory;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException; import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.sms.IncomingGroupMessage; import org.thoughtcrime.securesms.sms.IncomingGroupMessage;
import org.thoughtcrime.securesms.sms.IncomingIdentityUpdateMessage; 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.ContactTokenDetails;
import org.whispersystems.textsecure.push.PushServiceSocket; import org.whispersystems.textsecure.push.PushServiceSocket;
import org.whispersystems.textsecure.push.UnregisteredUserException; import org.whispersystems.textsecure.push.UnregisteredUserException;
import org.whispersystems.textsecure.storage.Session;
import org.whispersystems.textsecure.util.DirectoryUtil; import org.whispersystems.textsecure.util.DirectoryUtil;
import org.whispersystems.textsecure.util.InvalidNumberException; import org.whispersystems.textsecure.util.InvalidNumberException;
@ -60,7 +62,8 @@ public class UniversalTransport {
} }
public void deliver(SmsMessageRecord message) public void deliver(SmsMessageRecord message)
throws UndeliverableMessageException, UntrustedIdentityException, RetryLaterException, UserInterventionRequiredException throws UndeliverableMessageException, UntrustedIdentityException, RetryLaterException,
SecureFallbackApprovalException, InsecureFallbackApprovalException
{ {
if (!TextSecurePreferences.isPushRegistered(context)) { if (!TextSecurePreferences.isPushRegistered(context)) {
smsTransport.deliver(message); smsTransport.deliver(message);
@ -99,7 +102,8 @@ public class UniversalTransport {
} }
public MmsSendResult deliver(SendReq mediaMessage, long threadId) public MmsSendResult deliver(SendReq mediaMessage, long threadId)
throws UndeliverableMessageException, RetryLaterException, UntrustedIdentityException, UserInterventionRequiredException throws UndeliverableMessageException, RetryLaterException, UntrustedIdentityException,
SecureFallbackApprovalException, InsecureFallbackApprovalException
{ {
if (Util.isEmpty(mediaMessage.getTo())) { if (Util.isEmpty(mediaMessage.getTo())) {
return mmsTransport.deliver(mediaMessage); return mmsTransport.deliver(mediaMessage);
@ -155,28 +159,42 @@ public class UniversalTransport {
} }
private MmsSendResult fallbackOrAskApproval(SendReq mediaMessage, String destination) private MmsSendResult fallbackOrAskApproval(SendReq mediaMessage, String destination)
throws UserInterventionRequiredException, UndeliverableMessageException throws SecureFallbackApprovalException, UndeliverableMessageException, InsecureFallbackApprovalException
{ {
boolean isSmsFallbackApprovalRequired = isSmsFallbackApprovalRequired(destination); try {
if (!isSmsFallbackApprovalRequired) { Recipient recipient = RecipientFactory.getRecipientsFromString(context, destination, false).getPrimaryRecipient();
Log.i("UniversalTransport", "Falling back to MMS without user intervention"); boolean isSmsFallbackApprovalRequired = isSmsFallbackApprovalRequired(destination);
return mmsTransport.deliver(mediaMessage);
} else { if (!isSmsFallbackApprovalRequired) {
Log.i("UniversalTransport", "Marking message as pending user approval per their settings"); Log.w("UniversalTransport", "Falling back to MMS");
throw new UserInterventionRequiredException("Pending user approval for fallback to SMS"); 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) 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) { if (!isSmsFallbackApprovalRequired) {
Log.i("UniversalTransport", "Falling back to SMS without user intervention"); Log.w("UniversalTransport", "Falling back to SMS");
smsTransport.deliver(smsMessage); 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 { } else {
Log.i("UniversalTransport", "Marking message as pending user approval per their settings"); Log.w("UniversalTransport", "Marking message as pending secure fallback.");
throw new UserInterventionRequiredException("Pending user approval for fallback to SMS"); throw new SecureFallbackApprovalException("Pending user approval for fallback to secure SMS");
} }
} }
@ -218,7 +236,6 @@ public class UniversalTransport {
} }
} }
public boolean isMultipleRecipients(SendReq mediaMessage) { public boolean isMultipleRecipients(SendReq mediaMessage) {
int recipientCount = 0; int recipientCount = 0;
@ -267,9 +284,9 @@ public class UniversalTransport {
return directory.isActiveNumber(destination); return directory.isActiveNumber(destination);
} catch (NotInDirectoryException e) { } catch (NotInDirectoryException e) {
try { try {
PushServiceSocket socket = PushServiceSocketFactory.create(context); PushServiceSocket socket = PushServiceSocketFactory.create(context);
String contactToken = DirectoryUtil.getDirectoryServerToken(destination); String contactToken = DirectoryUtil.getDirectoryServerToken(destination);
ContactTokenDetails registeredUser = socket.getContactTokenDetails(contactToken); ContactTokenDetails registeredUser = socket.getContactTokenDetails(contactToken);
if (registeredUser == null) { if (registeredUser == null) {
registeredUser = new ContactTokenDetails(); registeredUser = new ContactTokenDetails();

@ -1,7 +0,0 @@
package org.thoughtcrime.securesms.transport;
public class UserInterventionRequiredException extends Exception {
public UserInterventionRequiredException(String detailMessage) {
super(detailMessage);
}
}
Loading…
Cancel
Save