Make UI responsive to UniversalTransport upgrades.

This commit is contained in:
Moxie Marlinspike 2013-11-18 13:16:18 -08:00
parent 07b7696937
commit dadabdfaa8
9 changed files with 50 additions and 26 deletions

View File

@ -146,7 +146,7 @@ public class KeyExchangeProcessor {
.saveIdentity(masterSecret, recipient, remoteIdentity); .saveIdentity(masterSecret, recipient, remoteIdentity);
} }
public void processKeyExchangeMessage(PreKeyEntity message) { public void processKeyExchangeMessage(PreKeyEntity message, long threadId) {
PublicKey remoteKey = new PublicKey(message.getKeyId(), message.getPublicKey()); PublicKey remoteKey = new PublicKey(message.getKeyId(), message.getPublicKey());
remoteKeyRecord.setCurrentRemoteKey(remoteKey); remoteKeyRecord.setCurrentRemoteKey(remoteKey);
remoteKeyRecord.setLastRemoteKey(remoteKey); remoteKeyRecord.setLastRemoteKey(remoteKey);
@ -166,6 +166,8 @@ public class KeyExchangeProcessor {
DatabaseFactory.getIdentityDatabase(context) DatabaseFactory.getIdentityDatabase(context)
.saveIdentity(masterSecret, recipient, message.getIdentityKey()); .saveIdentity(masterSecret, recipient, message.getIdentityKey());
broadcastSecurityUpdateEvent(context, threadId);
} }
public void processKeyExchangeMessage(KeyExchangeMessage message, long threadId) { public void processKeyExchangeMessage(KeyExchangeMessage message, long threadId) {
@ -202,6 +204,10 @@ public class KeyExchangeProcessor {
DecryptingQueue.scheduleRogueMessages(context, masterSecret, recipient); DecryptingQueue.scheduleRogueMessages(context, masterSecret, recipient);
broadcastSecurityUpdateEvent(context, threadId);
}
private static void broadcastSecurityUpdateEvent(Context context, long threadId) {
Intent intent = new Intent(SECURITY_UPDATE_EVENT); Intent intent = new Intent(SECURITY_UPDATE_EVENT);
intent.putExtra("thread_id", threadId); intent.putExtra("thread_id", threadId);
intent.setPackage(context.getPackageName()); intent.setPackage(context.getPackageName());

View File

@ -166,6 +166,10 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
updateTypeBitmask(id, 0, Types.KEY_EXCHANGE_CORRUPTED_BIT); updateTypeBitmask(id, 0, Types.KEY_EXCHANGE_CORRUPTED_BIT);
} }
public void markAsSecure(long id) {
updateTypeBitmask(id, 0, Types.SECURE_MESSAGE_BIT);
}
public void markAsDecryptFailed(long id) { public void markAsDecryptFailed(long id) {
updateTypeBitmask(id, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_FAILED_BIT); updateTypeBitmask(id, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_FAILED_BIT);
} }

View File

@ -21,7 +21,6 @@ import android.content.Intent;
import android.util.Log; import android.util.Log;
import android.util.Pair; import android.util.Pair;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase;
@ -30,6 +29,7 @@ import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.service.SendReceiveService.ToastHandler; import org.thoughtcrime.securesms.service.SendReceiveService.ToastHandler;
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.whispersystems.textsecure.crypto.MasterSecret;
import ws.com.google.android.mms.MmsException; import ws.com.google.android.mms.MmsException;
import ws.com.google.android.mms.pdu.SendReq; import ws.com.google.android.mms.pdu.SendReq;
@ -37,16 +37,14 @@ import ws.com.google.android.mms.pdu.SendReq;
public class MmsSender { public class MmsSender {
private final Context context; private final Context context;
private final ToastHandler toastHandler;
public MmsSender(Context context, ToastHandler toastHandler) { public MmsSender(Context context, ToastHandler toastHandler) {
this.context = context; this.context = context;
this.toastHandler = toastHandler;
} }
public void process(MasterSecret masterSecret, Intent intent) { public void process(MasterSecret masterSecret, Intent intent) {
Log.w("MmsSender", "Got intent action: " + intent.getAction()); Log.w("MmsSender", "Got intent action: " + intent.getAction());
if (intent.getAction().equals(SendReceiveService.SEND_MMS_ACTION)) { if (SendReceiveService.SEND_MMS_ACTION.equals(intent.getAction())) {
handleSendMms(masterSecret, intent); handleSendMms(masterSecret, intent);
} }
} }
@ -61,15 +59,16 @@ public class MmsSender {
SendReq[] messages = database.getOutgoingMessages(masterSecret, messageId); SendReq[] messages = database.getOutgoingMessages(masterSecret, messageId);
for (SendReq message : messages) { for (SendReq message : messages) {
long threadId = database.getThreadIdForMessage(message.getDatabaseMessageId());
try { try {
Log.w("MmsSender", "Passing to MMS transport: " + message.getDatabaseMessageId()); Log.w("MmsSender", "Passing to MMS transport: " + message.getDatabaseMessageId());
database.markAsSending(message.getDatabaseMessageId()); database.markAsSending(message.getDatabaseMessageId());
Pair<byte[], Integer> result = transport.deliver(message); Pair<byte[], Integer> result = transport.deliver(message, threadId);
database.markAsSent(message.getDatabaseMessageId(), result.first, result.second); database.markAsSent(message.getDatabaseMessageId(), result.first, result.second);
} catch (UndeliverableMessageException e) { } catch (UndeliverableMessageException e) {
Log.w("MmsSender", e); Log.w("MmsSender", e);
database.markAsSentFailed(message.getDatabaseMessageId()); database.markAsSentFailed(message.getDatabaseMessageId());
long threadId = database.getThreadIdForMessage(messageId);
Recipients recipients = threads.getRecipientsForThreadId(threadId); Recipients recipients = threads.getRecipientsForThreadId(threadId);
MessageNotifier.notifyMessageDeliveryFailed(context, recipients, threadId); MessageNotifier.notifyMessageDeliveryFailed(context, recipients, threadId);
} }

View File

@ -85,12 +85,18 @@ public class SmsSender {
private void handleSentMessage(Intent intent) { private void handleSentMessage(Intent intent) {
long messageId = intent.getLongExtra("message_id", -1); long messageId = intent.getLongExtra("message_id", -1);
int result = intent.getIntExtra("ResultCode", -31337); int result = intent.getIntExtra("ResultCode", -31337);
boolean upgraded = intent.getBooleanExtra("upgraded", false);
Log.w("SMSReceiverService", "Intent resultcode: " + result); Log.w("SMSReceiverService", "Intent resultcode: " + result);
Log.w("SMSReceiverService", "Running sent callback: " + messageId); Log.w("SMSReceiverService", "Running sent callback: " + messageId);
if (result == Activity.RESULT_OK) { if (result == Activity.RESULT_OK) {
DatabaseFactory.getSmsDatabase(context).markAsSent(messageId); DatabaseFactory.getSmsDatabase(context).markAsSent(messageId);
if (upgraded) {
DatabaseFactory.getSmsDatabase(context).markAsSecure(messageId);
}
unregisterForRadioChanges(); unregisterForRadioChanges();
} else if (result == SmsManager.RESULT_ERROR_NO_SERVICE || result == SmsManager.RESULT_ERROR_RADIO_OFF) { } else if (result == SmsManager.RESULT_ERROR_NO_SERVICE || result == SmsManager.RESULT_ERROR_RADIO_OFF) {
DatabaseFactory.getSmsDatabase(context).markAsOutbox(messageId); DatabaseFactory.getSmsDatabase(context).markAsOutbox(messageId);

View File

@ -133,6 +133,7 @@ public class MessageSender {
Intent intent = new Intent(SendReceiveService.SEND_MMS_ACTION, null, Intent intent = new Intent(SendReceiveService.SEND_MMS_ACTION, null,
context, SendReceiveService.class); context, SendReceiveService.class);
intent.putExtra("message_id", messageId); intent.putExtra("message_id", messageId);
intent.putExtra("thread_id", threadId);
context.startService(intent); context.startService(intent);
} }

View File

@ -9,13 +9,14 @@ import org.thoughtcrime.securesms.service.SmsListener;
public abstract class BaseTransport { public abstract class BaseTransport {
protected Intent constructSentIntent(Context context, long messageId, long type) { protected Intent constructSentIntent(Context context, long messageId, long type, boolean upgraded) {
Intent pending = new Intent(SendReceiveService.SENT_SMS_ACTION, Intent pending = new Intent(SendReceiveService.SENT_SMS_ACTION,
Uri.parse("custom://" + messageId + System.currentTimeMillis()), Uri.parse("custom://" + messageId + System.currentTimeMillis()),
context, SmsListener.class); context, SmsListener.class);
pending.putExtra("type", type); pending.putExtra("type", type);
pending.putExtra("message_id", messageId); pending.putExtra("message_id", messageId);
pending.putExtra("upgraded", upgraded);
return pending; return pending;
} }

View File

@ -73,24 +73,27 @@ public class PushTransport extends BaseTransport {
try { try {
TextSecurePushCredentials credentials = TextSecurePushCredentials.getInstance(); TextSecurePushCredentials credentials = TextSecurePushCredentials.getInstance();
Recipient recipient = message.getIndividualRecipient(); Recipient recipient = message.getIndividualRecipient();
long threadId = message.getThreadId();
PushServiceSocket socket = new PushServiceSocket(context, credentials); PushServiceSocket socket = new PushServiceSocket(context, credentials);
PushDestination destination = PushDestination.create(context, credentials, PushDestination destination = PushDestination.create(context, credentials,
recipient.getNumber()); recipient.getNumber());
String plaintextBody = message.getBody().getBody(); String plaintextBody = message.getBody().getBody();
byte[] plaintext = PushMessageContent.newBuilder().setBody(plaintextBody).build().toByteArray(); byte[] plaintext = PushMessageContent.newBuilder().setBody(plaintextBody).build().toByteArray();
PushBody pushBody = getEncryptedMessage(socket, recipient, destination, plaintext); PushBody pushBody = getEncryptedMessage(socket, threadId, recipient, destination, plaintext);
socket.sendMessage(destination, pushBody); socket.sendMessage(destination, pushBody);
context.sendBroadcast(constructSentIntent(context, message.getId(), message.getType())); context.sendBroadcast(constructSentIntent(context, message.getId(), message.getType(), true));
} catch (RateLimitException e) { } catch (RateLimitException e) {
Log.w("PushTransport", e); Log.w("PushTransport", e);
throw new IOException("Rate limit exceeded."); throw new IOException("Rate limit exceeded.");
} }
} }
public void deliver(SendReq message, List<PushDestination> destinations) throws IOException { public void deliver(SendReq message, List<PushDestination> destinations, long threadId)
throws IOException
{
try { try {
TextSecurePushCredentials credentials = TextSecurePushCredentials.getInstance(); TextSecurePushCredentials credentials = TextSecurePushCredentials.getInstance();
PushServiceSocket socket = new PushServiceSocket(context, credentials); PushServiceSocket socket = new PushServiceSocket(context, credentials);
@ -118,7 +121,7 @@ public class PushTransport extends BaseTransport {
} }
byte[] plaintext = builder.build().toByteArray(); byte[] plaintext = builder.build().toByteArray();
PushBody pushBody = getEncryptedMessage(socket, recipients.getPrimaryRecipient(), destination, plaintext); PushBody pushBody = getEncryptedMessage(socket, threadId, recipients.getPrimaryRecipient(), destination, plaintext);
pushBodies.add(pushBody); pushBodies.add(pushBody);
} }
@ -158,7 +161,7 @@ public class PushTransport extends BaseTransport {
return attachments; return attachments;
} }
private PushBody getEncryptedMessage(PushServiceSocket socket, Recipient recipient, private PushBody getEncryptedMessage(PushServiceSocket socket, long threadId, Recipient recipient,
PushDestination pushDestination, byte[] plaintext) PushDestination pushDestination, byte[] plaintext)
throws IOException throws IOException
{ {
@ -172,7 +175,7 @@ public class PushTransport extends BaseTransport {
return new PushBody(OutgoingPushMessage.TYPE_MESSAGE_PREKEY_BUNDLE, ciphertext); return new PushBody(OutgoingPushMessage.TYPE_MESSAGE_PREKEY_BUNDLE, ciphertext);
} else { } else {
Log.w("PushTransport", "Sending prekeybundle ciphertext message for new session..."); Log.w("PushTransport", "Sending prekeybundle ciphertext message for new session...");
byte[] ciphertext = getEncryptedPrekeyBundleMessageForNewSession(socket, recipient, pushDestination, plaintext); byte[] ciphertext = getEncryptedPrekeyBundleMessageForNewSession(socket, threadId, recipient, pushDestination, plaintext);
return new PushBody(OutgoingPushMessage.TYPE_MESSAGE_PREKEY_BUNDLE, ciphertext); return new PushBody(OutgoingPushMessage.TYPE_MESSAGE_PREKEY_BUNDLE, ciphertext);
} }
} }
@ -190,6 +193,7 @@ public class PushTransport extends BaseTransport {
} }
private byte[] getEncryptedPrekeyBundleMessageForNewSession(PushServiceSocket socket, private byte[] getEncryptedPrekeyBundleMessageForNewSession(PushServiceSocket socket,
long threadId,
Recipient recipient, Recipient recipient,
PushDestination pushDestination, PushDestination pushDestination,
byte[] plaintext) byte[] plaintext)
@ -200,7 +204,7 @@ public class PushTransport extends BaseTransport {
PreKeyEntity preKey = socket.getPreKey(pushDestination); PreKeyEntity preKey = socket.getPreKey(pushDestination);
KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipient); KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipient);
processor.processKeyExchangeMessage(preKey); processor.processKeyExchangeMessage(preKey, threadId);
MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKeyPair); MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKeyPair);
CiphertextMessage ciphertextMessage = messageCipher.encrypt(recipient, plaintext); CiphertextMessage ciphertextMessage = messageCipher.encrypt(recipient, plaintext);

View File

@ -37,7 +37,6 @@ import org.whispersystems.textsecure.crypto.KeyUtil;
import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.MessageCipher; import org.whispersystems.textsecure.crypto.MessageCipher;
import org.whispersystems.textsecure.crypto.ecc.Curve; import org.whispersystems.textsecure.crypto.ecc.Curve;
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage; import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage;
@ -70,7 +69,7 @@ public class SmsTransport extends BaseTransport {
} }
ArrayList<String> messages = multipartMessageHandler.divideMessage(transportMessage); ArrayList<String> messages = multipartMessageHandler.divideMessage(transportMessage);
ArrayList<PendingIntent> sentIntents = constructSentIntents(message.getId(), message.getType(), messages); ArrayList<PendingIntent> sentIntents = constructSentIntents(message.getId(), message.getType(), messages, true);
ArrayList<PendingIntent> deliveredIntents = constructDeliveredIntents(message.getId(), message.getType(), messages); ArrayList<PendingIntent> deliveredIntents = constructDeliveredIntents(message.getId(), message.getType(), messages);
Log.w("SmsTransport", "Secure divide into message parts: " + messages.size()); Log.w("SmsTransport", "Secure divide into message parts: " + messages.size());
@ -103,7 +102,7 @@ public class SmsTransport extends BaseTransport {
throws UndeliverableMessageException throws UndeliverableMessageException
{ {
ArrayList<String> messages = SmsManager.getDefault().divideMessage(message.getBody().getBody()); ArrayList<String> messages = SmsManager.getDefault().divideMessage(message.getBody().getBody());
ArrayList<PendingIntent> sentIntents = constructSentIntents(message.getId(), message.getType(), messages); ArrayList<PendingIntent> sentIntents = constructSentIntents(message.getId(), message.getType(), messages, false);
ArrayList<PendingIntent> deliveredIntents = constructDeliveredIntents(message.getId(), message.getType(), messages); ArrayList<PendingIntent> deliveredIntents = constructDeliveredIntents(message.getId(), message.getType(), messages);
String recipient = message.getIndividualRecipient().getNumber(); String recipient = message.getIndividualRecipient().getNumber();
@ -132,12 +131,14 @@ public class SmsTransport extends BaseTransport {
} }
} }
private ArrayList<PendingIntent> constructSentIntents(long messageId, long type, ArrayList<String> messages) { private ArrayList<PendingIntent> constructSentIntents(long messageId, long type,
ArrayList<String> messages, boolean secure)
{
ArrayList<PendingIntent> sentIntents = new ArrayList<PendingIntent>(messages.size()); ArrayList<PendingIntent> sentIntents = new ArrayList<PendingIntent>(messages.size());
for (String ignored : messages) { for (String ignored : messages) {
sentIntents.add(PendingIntent.getBroadcast(context, 0, sentIntents.add(PendingIntent.getBroadcast(context, 0,
constructSentIntent(context, messageId, type), constructSentIntent(context, messageId, type, secure),
0)); 0));
} }

View File

@ -76,7 +76,9 @@ public class UniversalTransport {
} }
} }
public Pair<byte[], Integer> deliver(SendReq mediaMessage) throws UndeliverableMessageException { public Pair<byte[], Integer> deliver(SendReq mediaMessage, long threadId)
throws UndeliverableMessageException
{
if (!TextSecurePreferences.isPushRegistered(context)) { if (!TextSecurePreferences.isPushRegistered(context)) {
return mmsTransport.deliver(mediaMessage); return mmsTransport.deliver(mediaMessage);
} }
@ -86,7 +88,7 @@ public class UniversalTransport {
if (isPushTransport(destinations)) { if (isPushTransport(destinations)) {
try { try {
Log.w("UniversalTransport", "Delivering media message with GCM..."); Log.w("UniversalTransport", "Delivering media message with GCM...");
pushTransport.deliver(mediaMessage, destinations); pushTransport.deliver(mediaMessage, destinations, threadId);
return new Pair<byte[], Integer>("push".getBytes("UTF-8"), 0); return new Pair<byte[], Integer>("push".getBytes("UTF-8"), 0);
} catch (IOException ioe) { } catch (IOException ioe) {
Log.w("UniversalTransport", ioe); Log.w("UniversalTransport", ioe);