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

View File

@ -16,13 +16,16 @@
*/
package org.thoughtcrime.securesms.recipients;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import org.thoughtcrime.securesms.crypto.KeyUtil;
import org.thoughtcrime.securesms.util.NumberUtil;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
public class Recipients implements Parcelable {
@ -44,6 +47,12 @@ public class Recipients implements Parcelable {
this.recipients = recipients;
}
public Recipients(final Recipient recipient) {
this.recipients = new LinkedList<Recipient>() {{
add(recipient);
}};
}
public Recipients(Parcel in) {
this.recipients = new ArrayList<Recipient>();
in.readTypedList(recipients, Recipient.CREATOR);
@ -68,6 +77,30 @@ public class Recipients implements Parcelable {
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() {
return this.recipients.isEmpty();
}

View File

@ -1,6 +1,6 @@
/**
/**
* Copyright (C) 2011 Whisper Systems
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
@ -10,86 +10,116 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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.database.DatabaseFactory;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.mms.TextSlide;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.service.MmsSender;
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.MmsException;
import ws.com.google.android.mms.pdu.EncodedStringValue;
import ws.com.google.android.mms.pdu.PduBody;
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 static long sendMms(Context context, MasterSecret masterSecret, Recipients recipients,
long threadId, SlideDeck slideDeck, String message, boolean isSecure) throws MmsException
public static long sendMms(Context context, MasterSecret masterSecret, Recipients recipients,
long threadId, SlideDeck slideDeck, String message, boolean forcePlaintext)
throws MmsException
{
if (threadId == -1)
threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);
if (message.trim().length() > 0)
slideDeck.addSlide(new TextSlide(context, message));
SendReq sendRequest = new SendReq();
String[] recipientsArray = recipients.toNumberStringArray();
EncodedStringValue[] encodedNumbers = EncodedStringValue.encodeStrings(recipientsArray);
PduBody body = slideDeck.toPduBody();
sendRequest.setTo(encodedNumbers);
SendReq sendRequest = new SendReq();
PduBody body = slideDeck.toPduBody();
sendRequest.setDate(System.currentTimeMillis() / 1000L);
sendRequest.setBody(body);
sendRequest.setContentType(ContentType.MULTIPART_MIXED.getBytes());
long messageId = DatabaseFactory.getEncryptingMmsDatabase(context, masterSecret).insertMessageSent(sendRequest, threadId, isSecure);
Intent intent = new Intent(SendReceiveService.SEND_MMS_ACTION, null, context, SendReceiveService.class);
intent.putExtra("message_id", messageId);
context.startService(intent);
Recipients secureRecipients = recipients.getSecureSessionRecipients(context);
Recipients insecureRecipients = recipients.getInsecureSessionRecipients(context);
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;
}
public static long send(Context context, MasterSecret masterSecret, Recipients recipients,
long threadId, String message, boolean isSecure)
{
public static long send(Context context, MasterSecret masterSecret, Recipients recipients,
long threadId, String message, boolean forcePlaintext)
{
if (threadId == -1)
threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);
Iterator<Recipient> i = recipients.getRecipientsList().iterator();
while (i.hasNext()) {
Recipient recipient = i.next();
for (Recipient recipient : recipients.getRecipientsList()) {
boolean isSecure = KeyUtil.isSessionFor(context, recipient) && !forcePlaintext;
long messageId;
if (!isSecure) 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());
if (!isSecure) {
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);
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);
context.startService(intent);
}
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);
}
}