From c13a3a8181e6f28598c5e1002675384fc9753723 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Sun, 21 Oct 2012 17:41:44 -0700 Subject: [PATCH] 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. --- .../securesms/ConversationActivity.java | 30 ++--- .../securesms/recipients/Recipients.java | 33 ++++++ .../securesms/sms/MessageSender.java | 110 +++++++++++------- 3 files changed, 118 insertions(+), 55 deletions(-) diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java index d6657315c0..33a62a999f 100644 --- a/src/org/thoughtcrime/securesms/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationActivity.java @@ -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) { diff --git a/src/org/thoughtcrime/securesms/recipients/Recipients.java b/src/org/thoughtcrime/securesms/recipients/Recipients.java index 540313b9aa..30a1d166b8 100644 --- a/src/org/thoughtcrime/securesms/recipients/Recipients.java +++ b/src/org/thoughtcrime/securesms/recipients/Recipients.java @@ -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() {{ + add(recipient); + }}; + } + public Recipients(Parcel in) { this.recipients = new ArrayList(); in.readTypedList(recipients, Recipient.CREATOR); @@ -68,6 +77,30 @@ public class Recipients implements Parcelable { return false; } + public Recipients getSecureSessionRecipients(Context context) { + List secureRecipients = new LinkedList(); + + for (Recipient recipient : recipients) { + if (KeyUtil.isSessionFor(context, recipient)) { + secureRecipients.add(recipient); + } + } + + return new Recipients(secureRecipients); + } + + public Recipients getInsecureSessionRecipients(Context context) { + List insecureRecipients = new LinkedList(); + + for (Recipient recipient : recipients) { + if (!KeyUtil.isSessionFor(context, recipient)) { + insecureRecipients.add(recipient); + } + } + + return new Recipients(insecureRecipients); + } + public boolean isEmpty() { return this.recipients.isEmpty(); } diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index c84cdfb5e4..baa1b4a4c1 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -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 . */ 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 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); + } }