From e7e5bc08845574f00170cf0de2ea1742dacca7bf Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Sun, 16 Feb 2014 15:23:49 -0800 Subject: [PATCH] Verify identity keys on outgoing messages. If PreKeyEntity identity key doesn't match local DB, fail outgoing message and queue "incoming" identity key update message for manual user approval. --- res/values/strings.xml | 1 + .../securesms/ConversationItem.java | 1 + .../securesms/ReceiveKeyActivity.java | 31 ++++++++++++---- .../crypto/KeyExchangeProcessorV2.java | 4 +++ .../securesms/database/MmsSmsColumns.java | 5 +++ .../securesms/database/SmsDatabase.java | 1 + .../database/model/MessageRecord.java | 4 +++ .../database/model/SmsMessageRecord.java | 2 ++ .../securesms/service/MmsSender.java | 9 +++++ .../securesms/service/SmsSender.java | 23 +++++++++--- .../sms/IncomingIdentityUpdateMessage.java | 18 ++++++++++ .../sms/IncomingPreKeyBundleMessage.java | 2 -- .../securesms/sms/IncomingTextMessage.java | 35 +++++++++++++++++++ .../securesms/transport/PushTransport.java | 23 ++++++++---- .../transport/UniversalTransport.java | 20 ++++++++--- .../transport/UntrustedIdentityException.java | 17 +++++++++ 16 files changed, 172 insertions(+), 24 deletions(-) create mode 100644 src/org/thoughtcrime/securesms/sms/IncomingIdentityUpdateMessage.java create mode 100644 src/org/thoughtcrime/securesms/transport/UntrustedIdentityException.java diff --git a/res/values/strings.xml b/res/values/strings.xml index f32a571bc..f1626a8f9 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -264,6 +264,7 @@ Received message with unknown identity key. Click to process and display. + Received updated but unknown identity information. Tap to validate identity. diff --git a/src/org/thoughtcrime/securesms/ConversationItem.java b/src/org/thoughtcrime/securesms/ConversationItem.java index 897efa644..26b92ad70 100644 --- a/src/org/thoughtcrime/securesms/ConversationItem.java +++ b/src/org/thoughtcrime/securesms/ConversationItem.java @@ -358,6 +358,7 @@ public class ConversationItem extends LinearLayout { intent.putExtra("thread_id", messageRecord.getThreadId()); intent.putExtra("message_id", messageRecord.getId()); intent.putExtra("is_bundle", messageRecord.isBundleKeyExchange()); + intent.putExtra("is_identity_update", messageRecord.isIdentityUpdate()); intent.putExtra("master_secret", masterSecret); intent.putExtra("sent", messageRecord.isOutgoing()); context.startActivity(intent); diff --git a/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java b/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java index 0d236cf89..90bfbb39b 100644 --- a/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java +++ b/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java @@ -47,6 +47,7 @@ import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; import org.whispersystems.textsecure.crypto.protocol.PreKeyWhisperMessage; import org.whispersystems.textsecure.storage.InvalidKeyIdException; import org.whispersystems.textsecure.storage.RecipientDevice; +import org.whispersystems.textsecure.util.Base64; import java.io.IOException; @@ -71,6 +72,7 @@ public class ReceiveKeyActivity extends Activity { private MasterSecret masterSecret; private PreKeyWhisperMessage keyExchangeMessageBundle; private KeyExchangeMessage keyExchangeMessage; + private IdentityKey identityUpdateMessage; @Override protected void onCreate(Bundle state) { @@ -99,8 +101,11 @@ public class ReceiveKeyActivity extends Activity { } private void initializeText() { - if (isTrusted(keyExchangeMessage, keyExchangeMessageBundle)) initializeTrustedText(); - else initializeUntrustedText(); + if (isTrusted(keyExchangeMessage, keyExchangeMessageBundle, identityUpdateMessage)) { + initializeTrustedText(); + } else { + initializeUntrustedText(); + } } private void initializeTrustedText() { @@ -113,12 +118,16 @@ public class ReceiveKeyActivity extends Activity { spannableString.setSpan(new ClickableSpan() { @Override public void onClick(View widget) { + IdentityKey remoteIdentity; + + if (identityUpdateMessage != null) remoteIdentity = identityUpdateMessage; + else if (keyExchangeMessageBundle != null) remoteIdentity = keyExchangeMessageBundle.getIdentityKey(); + else remoteIdentity = keyExchangeMessage.getIdentityKey(); + Intent intent = new Intent(ReceiveKeyActivity.this, VerifyIdentityActivity.class); intent.putExtra("recipient", recipient); intent.putExtra("master_secret", masterSecret); - intent.putExtra("remote_identity", - keyExchangeMessage == null ? - keyExchangeMessageBundle.getIdentityKey() : keyExchangeMessage.getIdentityKey()); + intent.putExtra("remote_identity", remoteIdentity); startActivity(intent); } }, getString(R.string.ReceiveKeyActivity_the_signature_on_this_key_exchange_is_different).length() +1, @@ -128,7 +137,7 @@ public class ReceiveKeyActivity extends Activity { descriptionText.setMovementMethod(LinkMovementMethod.getInstance()); } - private boolean isTrusted(KeyExchangeMessage message, PreKeyWhisperMessage messageBundle) { + private boolean isTrusted(KeyExchangeMessage message, PreKeyWhisperMessage messageBundle, IdentityKey identityUpdateMessage) { RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), recipientDeviceId); if (message != null) { @@ -138,6 +147,9 @@ public class ReceiveKeyActivity extends Activity { } else if (messageBundle != null) { KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(this, masterSecret, recipientDevice); return processor.isTrusted(messageBundle); + } else if (identityUpdateMessage != null) { + KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(this, masterSecret, recipientDevice); + return processor.isTrusted(identityUpdateMessage); } return false; @@ -154,6 +166,8 @@ public class ReceiveKeyActivity extends Activity { byte[] body = transportDetails.getDecodedMessage(messageBody.getBytes()); this.keyExchangeMessageBundle = new PreKeyWhisperMessage(body); + } else if (getIntent().getBooleanExtra("is_identity_update", false)) { + this.identityUpdateMessage = new IdentityKey(Base64.decodeWithoutPadding(messageBody), 0); } else { this.keyExchangeMessage = KeyExchangeMessage.createFor(messageBody); } @@ -232,6 +246,11 @@ public class ReceiveKeyActivity extends Activity { DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this) .markAsCorruptKeyExchange(messageId); } + } else if (identityUpdateMessage != null) { + DatabaseFactory.getIdentityDatabase(ReceiveKeyActivity.this) + .saveIdentity(masterSecret, recipient.getRecipientId(), identityUpdateMessage); + + DatabaseFactory.getSmsDatabase(ReceiveKeyActivity.this).markAsProcessedKeyExchange(messageId); } diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV2.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV2.java index 77849922f..bf7f1a08d 100644 --- a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV2.java +++ b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV2.java @@ -55,6 +55,10 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor { return isTrusted(message.getIdentityKey()); } + public boolean isTrusted(PreKeyEntity entity) { + return isTrusted(entity.getIdentityKey()); + } + public boolean isTrusted(KeyExchangeMessage message) { return message.hasIdentityKey() && isTrusted(message.getIdentityKey()); } diff --git a/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java b/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java index d8955ec61..8bf5d83a4 100644 --- a/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java +++ b/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java @@ -35,6 +35,7 @@ public interface MmsSmsColumns { protected static final long KEY_EXCHANGE_CORRUPTED_BIT = 0x1000; protected static final long KEY_EXCHANGE_INVALID_VERSION_BIT = 0x800; protected static final long KEY_EXCHANGE_BUNDLE_BIT = 0x400; + protected static final long KEY_EXCHANGE_IDENTITY_UPDATE_BIT = 0x200; // Secure Message Information protected static final long SECURE_MESSAGE_BIT = 0x800000; @@ -98,6 +99,10 @@ public interface MmsSmsColumns { return (type & KEY_EXCHANGE_BUNDLE_BIT) != 0; } + public static boolean isIdentityUpdate(long type) { + return (type & KEY_EXCHANGE_IDENTITY_UPDATE_BIT) != 0; + } + public static boolean isSymmetricEncryption(long type) { return (type & ENCRYPTION_SYMMETRIC_BIT) != 0; } diff --git a/src/org/thoughtcrime/securesms/database/SmsDatabase.java b/src/org/thoughtcrime/securesms/database/SmsDatabase.java index 57bf11e59..481a631b6 100644 --- a/src/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -252,6 +252,7 @@ public class SmsDatabase extends Database implements MmsSmsColumns { else if (((IncomingKeyExchangeMessage)message).isCorrupted()) type |= Types.KEY_EXCHANGE_CORRUPTED_BIT; else if (((IncomingKeyExchangeMessage)message).isInvalidVersion()) type |= Types.KEY_EXCHANGE_INVALID_VERSION_BIT; else if (((IncomingKeyExchangeMessage)message).isPreKeyBundle()) type |= Types.KEY_EXCHANGE_BUNDLE_BIT; + else if (((IncomingKeyExchangeMessage)message).isIdentityUpdate()) type |= Types.KEY_EXCHANGE_IDENTITY_UPDATE_BIT; } else if (message.isSecureMessage()) { type |= Types.SECURE_MESSAGE_BIT; type |= Types.ENCRYPTION_REMOTE_BIT; diff --git a/src/org/thoughtcrime/securesms/database/model/MessageRecord.java b/src/org/thoughtcrime/securesms/database/model/MessageRecord.java index 98b57e9a5..7bf7285b1 100644 --- a/src/org/thoughtcrime/securesms/database/model/MessageRecord.java +++ b/src/org/thoughtcrime/securesms/database/model/MessageRecord.java @@ -111,6 +111,10 @@ public abstract class MessageRecord extends DisplayRecord { return SmsDatabase.Types.isBundleKeyExchange(type); } + public boolean isIdentityUpdate() { + return SmsDatabase.Types.isIdentityUpdate(type); + } + public boolean isCorruptedKeyExchange() { return SmsDatabase.Types.isCorruptedKeyExchange(type); } diff --git a/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java b/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java index 3b7e35798..83357bfc2 100644 --- a/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java +++ b/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java @@ -65,6 +65,8 @@ public class SmsMessageRecord extends MessageRecord { return emphasisAdded(context.getString(R.string.SmsMessageRecord_received_key_exchange_message_for_invalid_protocol_version)); } else if (isBundleKeyExchange()) { return emphasisAdded(context.getString(R.string.SmsMessageRecord_received_message_with_unknown_identity_key_click_to_process)); + } else if (isIdentityUpdate()) { + return emphasisAdded(context.getString(R.string.SmsMessageRecord_received_updated_but_unknown_identity_information)); } else if (isKeyExchange() && isOutgoing()) { return new SpannableString(""); } else if (isKeyExchange() && !isOutgoing()) { diff --git a/src/org/thoughtcrime/securesms/service/MmsSender.java b/src/org/thoughtcrime/securesms/service/MmsSender.java index 8ecd3515e..58ff6a1c0 100644 --- a/src/org/thoughtcrime/securesms/service/MmsSender.java +++ b/src/org/thoughtcrime/securesms/service/MmsSender.java @@ -30,10 +30,14 @@ import org.thoughtcrime.securesms.mms.MmsSendResult; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.service.SendReceiveService.ToastHandler; +import org.thoughtcrime.securesms.sms.IncomingIdentityUpdateMessage; +import org.thoughtcrime.securesms.sms.IncomingTextMessage; import org.thoughtcrime.securesms.transport.RetryLaterException; import org.thoughtcrime.securesms.transport.UndeliverableMessageException; import org.thoughtcrime.securesms.transport.UniversalTransport; +import org.thoughtcrime.securesms.transport.UntrustedIdentityException; import org.whispersystems.textsecure.crypto.MasterSecret; +import org.whispersystems.textsecure.util.Base64; import ws.com.google.android.mms.MmsException; import ws.com.google.android.mms.pdu.SendReq; @@ -87,6 +91,11 @@ public class MmsSender { database.markAsSentFailed(message.getDatabaseMessageId()); Recipients recipients = threads.getRecipientsForThreadId(threadId); MessageNotifier.notifyMessageDeliveryFailed(context, recipients, threadId); + } catch (UntrustedIdentityException uie) { + IncomingTextMessage base = new IncomingTextMessage(message); + IncomingIdentityUpdateMessage identityUpdateMessage = new IncomingIdentityUpdateMessage(base, Base64.encodeBytesWithoutPadding(uie.getIdentityKey().serialize())); + DatabaseFactory.getEncryptingSmsDatabase(context).insertMessageInbox(masterSecret, identityUpdateMessage); + database.markAsSentFailed(messageId); } catch (RetryLaterException e) { Log.w("MmsSender", e); database.markAsOutbox(message.getDatabaseMessageId()); diff --git a/src/org/thoughtcrime/securesms/service/SmsSender.java b/src/org/thoughtcrime/securesms/service/SmsSender.java index dcc875271..a08cde0a3 100644 --- a/src/org/thoughtcrime/securesms/service/SmsSender.java +++ b/src/org/thoughtcrime/securesms/service/SmsSender.java @@ -30,9 +30,13 @@ import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.service.SendReceiveService.ToastHandler; +import org.thoughtcrime.securesms.sms.IncomingIdentityUpdateMessage; +import org.thoughtcrime.securesms.sms.IncomingTextMessage; import org.thoughtcrime.securesms.transport.UndeliverableMessageException; import org.thoughtcrime.securesms.transport.UniversalTransport; +import org.thoughtcrime.securesms.transport.UntrustedIdentityException; import org.whispersystems.textsecure.crypto.MasterSecret; +import org.whispersystems.textsecure.util.Base64; public class SmsSender { @@ -71,12 +75,21 @@ public class SmsSender { else reader = database.getOutgoingMessages(masterSecret); while (reader != null && (record = reader.getNext()) != null) { - database.markAsSending(record.getId()); - transport.deliver(record); + try { + database.markAsSending(record.getId()); + + transport.deliver(record); + } catch (UntrustedIdentityException e) { + Log.w("SmsSender", e); + IncomingTextMessage base = new IncomingTextMessage(record); + IncomingIdentityUpdateMessage identityUpdateMessage = new IncomingIdentityUpdateMessage(base, Base64.encodeBytesWithoutPadding(e.getIdentityKey().serialize())); + DatabaseFactory.getEncryptingSmsDatabase(context).insertMessageInbox(masterSecret, identityUpdateMessage); + DatabaseFactory.getSmsDatabase(context).markAsSentFailed(messageId); + } catch (UndeliverableMessageException ude) { + Log.w("SmsSender", ude); + DatabaseFactory.getSmsDatabase(context).markAsSentFailed(messageId); + } } - } catch (UndeliverableMessageException ude) { - Log.w("SmsSender", ude); - DatabaseFactory.getSmsDatabase(context).markAsSentFailed(messageId); } finally { if (reader != null) reader.close(); diff --git a/src/org/thoughtcrime/securesms/sms/IncomingIdentityUpdateMessage.java b/src/org/thoughtcrime/securesms/sms/IncomingIdentityUpdateMessage.java new file mode 100644 index 000000000..e494e691e --- /dev/null +++ b/src/org/thoughtcrime/securesms/sms/IncomingIdentityUpdateMessage.java @@ -0,0 +1,18 @@ +package org.thoughtcrime.securesms.sms; + +public class IncomingIdentityUpdateMessage extends IncomingKeyExchangeMessage { + + public IncomingIdentityUpdateMessage(IncomingTextMessage base, String newBody) { + super(base, newBody); + } + + @Override + public IncomingIdentityUpdateMessage withMessageBody(String messageBody) { + return new IncomingIdentityUpdateMessage(this, messageBody); + } + + @Override + public boolean isIdentityUpdate() { + return true; + } +} diff --git a/src/org/thoughtcrime/securesms/sms/IncomingPreKeyBundleMessage.java b/src/org/thoughtcrime/securesms/sms/IncomingPreKeyBundleMessage.java index c4103564b..30ad71dae 100644 --- a/src/org/thoughtcrime/securesms/sms/IncomingPreKeyBundleMessage.java +++ b/src/org/thoughtcrime/securesms/sms/IncomingPreKeyBundleMessage.java @@ -1,7 +1,5 @@ package org.thoughtcrime.securesms.sms; -import org.whispersystems.textsecure.push.IncomingPushMessage; - public class IncomingPreKeyBundleMessage extends IncomingKeyExchangeMessage { public IncomingPreKeyBundleMessage(IncomingTextMessage base, String newBody) { diff --git a/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java b/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java index 5c1ec29a5..71e3ebd86 100644 --- a/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java +++ b/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java @@ -4,12 +4,15 @@ import android.os.Parcel; import android.os.Parcelable; import android.telephony.SmsMessage; +import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.util.GroupUtil; import org.whispersystems.textsecure.push.IncomingPushMessage; import org.whispersystems.textsecure.storage.RecipientDevice; import java.util.List; +import ws.com.google.android.mms.pdu.SendReq; + import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext; public class IncomingTextMessage implements Parcelable { @@ -121,6 +124,34 @@ public class IncomingTextMessage implements Parcelable { this.groupActionArgument = fragments.get(0).getGroupActionArgument(); } + public IncomingTextMessage(SendReq record) { + this.message = ""; + this.sender = record.getTo()[0].getString(); + this.senderDeviceId = RecipientDevice.DEFAULT_DEVICE_ID; + this.protocol = 31338; + this.serviceCenterAddress = "Outgoing"; + this.replyPathPresent = true; + this.pseudoSubject = ""; + this.sentTimestampMillis = System.currentTimeMillis(); + this.groupId = null; + this.groupAction = -1; + this.groupActionArgument = null; + } + + public IncomingTextMessage(SmsMessageRecord record) { + this.message = record.getBody().getBody(); + this.sender = record.getIndividualRecipient().getNumber(); + this.senderDeviceId = RecipientDevice.DEFAULT_DEVICE_ID; + this.protocol = 31338; + this.serviceCenterAddress = "Outgoing"; + this.replyPathPresent = true; + this.pseudoSubject = ""; + this.sentTimestampMillis = System.currentTimeMillis(); + this.groupId = null; + this.groupAction = -1; + this.groupActionArgument = null; + } + public long getSentTimestampMillis() { return sentTimestampMillis; } @@ -169,6 +200,10 @@ public class IncomingTextMessage implements Parcelable { return false; } + public boolean isIdentityUpdate() { + return false; + } + public String getGroupId() { return groupId; } diff --git a/src/org/thoughtcrime/securesms/transport/PushTransport.java b/src/org/thoughtcrime/securesms/transport/PushTransport.java index 34ae9c859..4c2406450 100644 --- a/src/org/thoughtcrime/securesms/transport/PushTransport.java +++ b/src/org/thoughtcrime/securesms/transport/PushTransport.java @@ -73,7 +73,9 @@ public class PushTransport extends BaseTransport { this.masterSecret = masterSecret; } - public void deliver(SmsMessageRecord message) throws IOException { + public void deliver(SmsMessageRecord message) + throws IOException, UntrustedIdentityException + { try { Recipient recipient = message.getIndividualRecipient(); long threadId = message.getThreadId(); @@ -97,7 +99,9 @@ public class PushTransport extends BaseTransport { } } - public void deliver(SendReq message, long threadId) throws IOException { + public void deliver(SendReq message, long threadId) + throws IOException, UntrustedIdentityException + { PushServiceSocket socket = PushServiceSocketFactory.create(context); byte[] plaintext = getPlaintextMessage(socket, message); String destination = message.getTo()[0].getString(); @@ -147,6 +151,9 @@ public class PushTransport extends BaseTransport { } catch (IOException e) { Log.w("PushTransport", e); failures.add(recipient); + } catch (UntrustedIdentityException e) { + Log.w("PushTransport", e); + failures.add(recipient); } } @@ -165,7 +172,7 @@ public class PushTransport extends BaseTransport { } private void deliver(PushServiceSocket socket, Recipient recipient, long threadId, byte[] plaintext) - throws IOException, InvalidNumberException + throws IOException, InvalidNumberException, UntrustedIdentityException { for (int i=0;i<3;i++) { try { @@ -274,7 +281,7 @@ public class PushTransport extends BaseTransport { private OutgoingPushMessageList getEncryptedMessages(PushServiceSocket socket, long threadId, Recipient recipient, byte[] plaintext) - throws IOException, InvalidNumberException + throws IOException, InvalidNumberException, UntrustedIdentityException { String e164number = Util.canonicalizeNumber(context, recipient.getNumber()); long recipientId = recipient.getRecipientId(); @@ -296,7 +303,7 @@ public class PushTransport extends BaseTransport { private PushBody getEncryptedMessage(PushServiceSocket socket, long threadId, PushAddress pushAddress, byte[] plaintext) - throws IOException + throws IOException, UntrustedIdentityException { if (!SessionRecordV2.hasSession(context, masterSecret, pushAddress)) { try { @@ -306,7 +313,11 @@ public class PushTransport extends BaseTransport { PushAddress device = PushAddress.create(context, pushAddress.getRecipientId(), pushAddress.getNumber(), preKey.getDeviceId()); KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(context, masterSecret, device); - processor.processKeyExchangeMessage(preKey, threadId); + if (processor.isTrusted(preKey)) { + processor.processKeyExchangeMessage(preKey, threadId); + } else { + throw new UntrustedIdentityException("Untrusted identity key!", preKey.getIdentityKey()); + } } } catch (InvalidKeyException e) { throw new IOException(e); diff --git a/src/org/thoughtcrime/securesms/transport/UniversalTransport.java b/src/org/thoughtcrime/securesms/transport/UniversalTransport.java index 3554364c5..d948c7741 100644 --- a/src/org/thoughtcrime/securesms/transport/UniversalTransport.java +++ b/src/org/thoughtcrime/securesms/transport/UniversalTransport.java @@ -18,7 +18,6 @@ package org.thoughtcrime.securesms.transport; import android.content.Context; import android.util.Log; -import android.widget.Toast; import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.mms.MmsSendResult; @@ -54,7 +53,9 @@ public class UniversalTransport { this.mmsTransport = new MmsTransport(context, masterSecret); } - public void deliver(SmsMessageRecord message) throws UndeliverableMessageException { + public void deliver(SmsMessageRecord message) + throws UndeliverableMessageException, UntrustedIdentityException + { if (!TextSecurePreferences.isPushRegistered(context)) { smsTransport.deliver(message); return; @@ -83,7 +84,7 @@ public class UniversalTransport { } public MmsSendResult deliver(SendReq mediaMessage, long threadId) - throws UndeliverableMessageException, RetryLaterException + throws UndeliverableMessageException, RetryLaterException, UntrustedIdentityException { if (Util.isEmpty(mediaMessage.getTo())) { throw new UndeliverableMessageException("No destination specified"); @@ -97,14 +98,23 @@ public class UniversalTransport { return mmsTransport.deliver(mediaMessage); } - if (isPushTransport(mediaMessage.getTo()[0].getString())) { + String destination; + + try { + destination = Util.canonicalizeNumber(context, mediaMessage.getTo()[0].getString()); + } catch (InvalidNumberException ine) { + Log.w("UniversalTransport", ine); + return mmsTransport.deliver(mediaMessage); + } + + if (isPushTransport(destination)) { try { Log.w("UniversalTransport", "Delivering media message with GCM..."); pushTransport.deliver(mediaMessage, threadId); return new MmsSendResult("push".getBytes("UTF-8"), 0, true); } catch (IOException ioe) { Log.w("UniversalTransport", ioe); - if (!GroupUtil.isEncodedGroup(mediaMessage.getTo()[0].getString())) { + if (!GroupUtil.isEncodedGroup(destination)) { return mmsTransport.deliver(mediaMessage); } else { throw new RetryLaterException(); diff --git a/src/org/thoughtcrime/securesms/transport/UntrustedIdentityException.java b/src/org/thoughtcrime/securesms/transport/UntrustedIdentityException.java new file mode 100644 index 000000000..e14f79332 --- /dev/null +++ b/src/org/thoughtcrime/securesms/transport/UntrustedIdentityException.java @@ -0,0 +1,17 @@ +package org.thoughtcrime.securesms.transport; + +import org.whispersystems.textsecure.crypto.IdentityKey; + +public class UntrustedIdentityException extends Exception { + + private final IdentityKey identityKey; + + public UntrustedIdentityException(String s, IdentityKey identityKey) { + super(s); + this.identityKey = identityKey; + } + + public IdentityKey getIdentityKey() { + return identityKey; + } +}