Fix "Group Threads" so that messages are encrypted when possible.

1) Change the MessageSender logic so that individual SMS messages
are encrypted whenever there is a secure session, unless the UI
explicitly specifies otherwise.

2) Change the MMS logic so that messages to a recipient with a
secure session are all sent individually, instead of including
those recipients into the batch plaintext message.
This commit is contained in:
Moxie Marlinspike 2012-10-21 17:41:44 -07:00
parent 1bd260b981
commit c13a3a8181
3 changed files with 118 additions and 55 deletions

View File

@ -106,7 +106,7 @@ public class ConversationActivity extends SherlockFragmentActivity
private Recipients recipients; private Recipients recipients;
private long threadId; private long threadId;
private boolean sendEncrypted; private boolean isEncryptedConversation;
private CharacterCalculator characterCalculator = new CharacterCalculator(); private CharacterCalculator characterCalculator = new CharacterCalculator();
@ -188,7 +188,7 @@ public class ConversationActivity extends SherlockFragmentActivity
MenuInflater inflater = this.getSupportMenuInflater(); MenuInflater inflater = this.getSupportMenuInflater();
menu.clear(); menu.clear();
if (isSingleConversation() && sendEncrypted) if (isSingleConversation() && isEncryptedConversation)
{ {
if (isAuthenticatedSession()) { if (isAuthenticatedSession()) {
inflater.inflate(R.menu.conversation_secure_verified, menu); inflater.inflate(R.menu.conversation_secure_verified, menu);
@ -230,7 +230,7 @@ public class ConversationActivity extends SherlockFragmentActivity
@Override @Override
public void onCreateContextMenu (ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { public void onCreateContextMenu (ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
if (sendEncrypted) { if (isEncryptedConversation) {
android.view.MenuInflater inflater = getMenuInflater(); android.view.MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.conversation_button_context, menu); inflater.inflate(R.menu.conversation_button_context, menu);
} }
@ -239,7 +239,7 @@ public class ConversationActivity extends SherlockFragmentActivity
@Override @Override
public boolean onContextItemSelected(android.view.MenuItem item) { public boolean onContextItemSelected(android.view.MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.menu_context_send_unencrypted: sendMessage(false); return true; case R.id.menu_context_send_unencrypted: sendMessage(true); return true;
} }
return false; return false;
@ -362,7 +362,7 @@ public class ConversationActivity extends SherlockFragmentActivity
if (isSingleConversation()) { if (isSingleConversation()) {
if (sendEncrypted) { if (isEncryptedConversation) {
title = AuthenticityCalculator.getAuthenticatedName(this, title = AuthenticityCalculator.getAuthenticatedName(this,
getRecipients().getPrimaryRecipient(), getRecipients().getPrimaryRecipient(),
masterSecret); masterSecret);
@ -400,11 +400,11 @@ public class ConversationActivity extends SherlockFragmentActivity
{ {
sendButton.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_menu_lock_holo_light, 0); sendButton.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_menu_lock_holo_light, 0);
sendButton.setCompoundDrawablePadding(15); sendButton.setCompoundDrawablePadding(15);
this.sendEncrypted = true; this.isEncryptedConversation = true;
this.characterCalculator = new EncryptedCharacterCalculator(); this.characterCalculator = new EncryptedCharacterCalculator();
} else { } else {
sendButton.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); sendButton.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
this.sendEncrypted = false; this.isEncryptedConversation = false;
this.characterCalculator = new CharacterCalculator(); this.characterCalculator = new CharacterCalculator();
} }
@ -577,7 +577,7 @@ public class ConversationActivity extends SherlockFragmentActivity
if (rawText.length() < 1 && !attachmentManager.isAttachmentPresent()) if (rawText.length() < 1 && !attachmentManager.isAttachmentPresent())
throw new InvalidMessageException(getString(R.string.ConversationActivity_message_is_empty_exclamation)); throw new InvalidMessageException(getString(R.string.ConversationActivity_message_is_empty_exclamation));
if (!sendEncrypted && Tag.isTaggable(this, rawText)) if (!isEncryptedConversation && Tag.isTaggable(this, rawText))
rawText = Tag.getTaggedMessage(rawText); rawText = Tag.getTaggedMessage(rawText);
return rawText; return rawText;
@ -604,7 +604,7 @@ public class ConversationActivity extends SherlockFragmentActivity
} }
} }
private void sendMessage(boolean sendEncrypted) { private void sendMessage(boolean forcePlaintext) {
try { try {
Recipients recipients = getRecipients(); Recipients recipients = getRecipients();
@ -617,13 +617,13 @@ public class ConversationActivity extends SherlockFragmentActivity
if (attachmentManager.isAttachmentPresent()) { if (attachmentManager.isAttachmentPresent()) {
allocatedThreadId = MessageSender.sendMms(ConversationActivity.this, masterSecret, recipients, allocatedThreadId = MessageSender.sendMms(ConversationActivity.this, masterSecret, recipients,
threadId, attachmentManager.getSlideDeck(), message, threadId, attachmentManager.getSlideDeck(), message,
sendEncrypted); forcePlaintext);
} else if (recipients.isEmailRecipient()) { } else if (recipients.isEmailRecipient()) {
allocatedThreadId = MessageSender.sendMms(ConversationActivity.this, masterSecret, recipients, allocatedThreadId = MessageSender.sendMms(ConversationActivity.this, masterSecret, recipients,
threadId, new SlideDeck(), message, sendEncrypted); threadId, new SlideDeck(), message, forcePlaintext);
} else { } else {
allocatedThreadId = MessageSender.send(ConversationActivity.this, masterSecret, recipients, allocatedThreadId = MessageSender.send(ConversationActivity.this, masterSecret, recipients,
threadId, message, sendEncrypted); threadId, message, forcePlaintext);
} }
sendComplete(recipients, allocatedThreadId); sendComplete(recipients, allocatedThreadId);
@ -679,7 +679,7 @@ public class ConversationActivity extends SherlockFragmentActivity
private class SendButtonListener implements OnClickListener, TextView.OnEditorActionListener { private class SendButtonListener implements OnClickListener, TextView.OnEditorActionListener {
public void onClick(View v) { public void onClick(View v) {
sendMessage(sendEncrypted); sendMessage(false);
} }
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {

View File

@ -16,13 +16,16 @@
*/ */
package org.thoughtcrime.securesms.recipients; package org.thoughtcrime.securesms.recipients;
import android.content.Context;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import org.thoughtcrime.securesms.crypto.KeyUtil;
import org.thoughtcrime.securesms.util.NumberUtil; import org.thoughtcrime.securesms.util.NumberUtil;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList;
import java.util.List; import java.util.List;
public class Recipients implements Parcelable { public class Recipients implements Parcelable {
@ -44,6 +47,12 @@ public class Recipients implements Parcelable {
this.recipients = recipients; this.recipients = recipients;
} }
public Recipients(final Recipient recipient) {
this.recipients = new LinkedList<Recipient>() {{
add(recipient);
}};
}
public Recipients(Parcel in) { public Recipients(Parcel in) {
this.recipients = new ArrayList<Recipient>(); this.recipients = new ArrayList<Recipient>();
in.readTypedList(recipients, Recipient.CREATOR); in.readTypedList(recipients, Recipient.CREATOR);
@ -68,6 +77,30 @@ public class Recipients implements Parcelable {
return false; return false;
} }
public Recipients getSecureSessionRecipients(Context context) {
List<Recipient> secureRecipients = new LinkedList<Recipient>();
for (Recipient recipient : recipients) {
if (KeyUtil.isSessionFor(context, recipient)) {
secureRecipients.add(recipient);
}
}
return new Recipients(secureRecipients);
}
public Recipients getInsecureSessionRecipients(Context context) {
List<Recipient> insecureRecipients = new LinkedList<Recipient>();
for (Recipient recipient : recipients) {
if (!KeyUtil.isSessionFor(context, recipient)) {
insecureRecipients.add(recipient);
}
}
return new Recipients(insecureRecipients);
}
public boolean isEmpty() { public boolean isEmpty() {
return this.recipients.isEmpty(); return this.recipients.isEmpty();
} }

View File

@ -16,32 +16,31 @@
*/ */
package org.thoughtcrime.securesms.sms; package org.thoughtcrime.securesms.sms;
import java.util.Iterator; import android.content.Context;
import android.content.Intent;
import android.telephony.PhoneNumberUtils;
import android.util.Log;
import org.thoughtcrime.securesms.crypto.KeyUtil;
import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.mms.TextSlide; import org.thoughtcrime.securesms.mms.TextSlide;
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.service.MmsSender;
import org.thoughtcrime.securesms.service.SendReceiveService; import org.thoughtcrime.securesms.service.SendReceiveService;
import org.thoughtcrime.securesms.service.SmsSender;
import ws.com.google.android.mms.ContentType; import ws.com.google.android.mms.ContentType;
import ws.com.google.android.mms.MmsException; import ws.com.google.android.mms.MmsException;
import ws.com.google.android.mms.pdu.EncodedStringValue; import ws.com.google.android.mms.pdu.EncodedStringValue;
import ws.com.google.android.mms.pdu.PduBody; import ws.com.google.android.mms.pdu.PduBody;
import ws.com.google.android.mms.pdu.SendReq; import ws.com.google.android.mms.pdu.SendReq;
import android.content.Context;
import android.content.Intent;
import android.telephony.PhoneNumberUtils;
import android.util.Log;
public class MessageSender { public class MessageSender {
public static long sendMms(Context context, MasterSecret masterSecret, Recipients recipients, public static long sendMms(Context context, MasterSecret masterSecret, Recipients recipients,
long threadId, SlideDeck slideDeck, String message, boolean isSecure) throws MmsException long threadId, SlideDeck slideDeck, String message, boolean forcePlaintext)
throws MmsException
{ {
if (threadId == -1) if (threadId == -1)
threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients); threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);
@ -50,41 +49,54 @@ public class MessageSender {
slideDeck.addSlide(new TextSlide(context, message)); slideDeck.addSlide(new TextSlide(context, message));
SendReq sendRequest = new SendReq(); SendReq sendRequest = new SendReq();
String[] recipientsArray = recipients.toNumberStringArray();
EncodedStringValue[] encodedNumbers = EncodedStringValue.encodeStrings(recipientsArray);
PduBody body = slideDeck.toPduBody(); PduBody body = slideDeck.toPduBody();
sendRequest.setTo(encodedNumbers);
sendRequest.setDate(System.currentTimeMillis() / 1000L); sendRequest.setDate(System.currentTimeMillis() / 1000L);
sendRequest.setBody(body); sendRequest.setBody(body);
sendRequest.setContentType(ContentType.MULTIPART_MIXED.getBytes()); sendRequest.setContentType(ContentType.MULTIPART_MIXED.getBytes());
long messageId = DatabaseFactory.getEncryptingMmsDatabase(context, masterSecret).insertMessageSent(sendRequest, threadId, isSecure); Recipients secureRecipients = recipients.getSecureSessionRecipients(context);
Intent intent = new Intent(SendReceiveService.SEND_MMS_ACTION, null, context, SendReceiveService.class); Recipients insecureRecipients = recipients.getInsecureSessionRecipients(context);
intent.putExtra("message_id", messageId);
context.startService(intent); for (Recipient secureRecipient : secureRecipients.getRecipientsList()) {
sendMms(context, new Recipients(secureRecipient), masterSecret,
sendRequest, threadId, !forcePlaintext);
}
if (!insecureRecipients.isEmpty()) {
sendMms(context, insecureRecipients, masterSecret, sendRequest, threadId, false);
}
return threadId; return threadId;
} }
public static long send(Context context, MasterSecret masterSecret, Recipients recipients, public static long send(Context context, MasterSecret masterSecret, Recipients recipients,
long threadId, String message, boolean isSecure) long threadId, String message, boolean forcePlaintext)
{ {
if (threadId == -1) if (threadId == -1)
threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients); threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);
Iterator<Recipient> i = recipients.getRecipientsList().iterator(); for (Recipient recipient : recipients.getRecipientsList()) {
boolean isSecure = KeyUtil.isSessionFor(context, recipient) && !forcePlaintext;
while (i.hasNext()) {
Recipient recipient = i.next();
long messageId; long messageId;
if (!isSecure) messageId = DatabaseFactory.getEncryptingSmsDatabase(context).insertMessageSent(masterSecret, PhoneNumberUtils.formatNumber(recipient.getNumber()), threadId, message, System.currentTimeMillis()); if (!isSecure) {
else messageId = DatabaseFactory.getEncryptingSmsDatabase(context).insertSecureMessageSent(masterSecret, PhoneNumberUtils.formatNumber(recipient.getNumber()), threadId, message, System.currentTimeMillis()); messageId = DatabaseFactory.getEncryptingSmsDatabase(context)
.insertMessageSent(masterSecret,
PhoneNumberUtils.formatNumber(recipient.getNumber()),
threadId, message, System.currentTimeMillis());
} else {
messageId = DatabaseFactory.getEncryptingSmsDatabase(context)
.insertSecureMessageSent(masterSecret,
PhoneNumberUtils.formatNumber(recipient.getNumber()),
threadId, message, System.currentTimeMillis());
}
Log.w("SMSSender", "Got message id for new message: " + messageId); Log.w("SMSSender", "Got message id for new message: " + messageId);
Intent intent = new Intent(SendReceiveService.SEND_SMS_ACTION, null, context, SendReceiveService.class);
Intent intent = new Intent(SendReceiveService.SEND_SMS_ACTION, null,
context, SendReceiveService.class);
intent.putExtra("message_id", messageId); intent.putExtra("message_id", messageId);
context.startService(intent); context.startService(intent);
} }
@ -92,4 +104,22 @@ public class MessageSender {
return threadId; return threadId;
} }
private static void sendMms(Context context, Recipients recipients, MasterSecret masterSecret,
SendReq sendRequest, long threadId, boolean secure)
throws MmsException
{
String[] recipientsArray = recipients.toNumberStringArray();
EncodedStringValue[] encodedNumbers = EncodedStringValue.encodeStrings(recipientsArray);
sendRequest.setTo(encodedNumbers);
long messageId = DatabaseFactory.getEncryptingMmsDatabase(context, masterSecret)
.insertMessageSent(sendRequest, threadId, secure);
Intent intent = new Intent(SendReceiveService.SEND_MMS_ACTION, null,
context, SendReceiveService.class);
intent.putExtra("message_id", messageId);
context.startService(intent);
}
} }