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.
This commit is contained in:
Moxie Marlinspike 2014-02-16 15:23:49 -08:00
parent e2989373cd
commit e7e5bc0884
16 changed files with 172 additions and 24 deletions

View File

@ -264,6 +264,7 @@
<string name="SmsMessageRecord_received_message_with_unknown_identity_key_click_to_process"> <string name="SmsMessageRecord_received_message_with_unknown_identity_key_click_to_process">
Received message with unknown identity key. Click to process and display. Received message with unknown identity key. Click to process and display.
</string> </string>
<string name="SmsMessageRecord_received_updated_but_unknown_identity_information">Received updated but unknown identity information. Tap to validate identity.</string>
<!-- VerifyIdentityActivity --> <!-- VerifyIdentityActivity -->

View File

@ -358,6 +358,7 @@ public class ConversationItem extends LinearLayout {
intent.putExtra("thread_id", messageRecord.getThreadId()); intent.putExtra("thread_id", messageRecord.getThreadId());
intent.putExtra("message_id", messageRecord.getId()); intent.putExtra("message_id", messageRecord.getId());
intent.putExtra("is_bundle", messageRecord.isBundleKeyExchange()); intent.putExtra("is_bundle", messageRecord.isBundleKeyExchange());
intent.putExtra("is_identity_update", messageRecord.isIdentityUpdate());
intent.putExtra("master_secret", masterSecret); intent.putExtra("master_secret", masterSecret);
intent.putExtra("sent", messageRecord.isOutgoing()); intent.putExtra("sent", messageRecord.isOutgoing());
context.startActivity(intent); context.startActivity(intent);

View File

@ -47,6 +47,7 @@ import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
import org.whispersystems.textsecure.crypto.protocol.PreKeyWhisperMessage; import org.whispersystems.textsecure.crypto.protocol.PreKeyWhisperMessage;
import org.whispersystems.textsecure.storage.InvalidKeyIdException; import org.whispersystems.textsecure.storage.InvalidKeyIdException;
import org.whispersystems.textsecure.storage.RecipientDevice; import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.util.Base64;
import java.io.IOException; import java.io.IOException;
@ -71,6 +72,7 @@ public class ReceiveKeyActivity extends Activity {
private MasterSecret masterSecret; private MasterSecret masterSecret;
private PreKeyWhisperMessage keyExchangeMessageBundle; private PreKeyWhisperMessage keyExchangeMessageBundle;
private KeyExchangeMessage keyExchangeMessage; private KeyExchangeMessage keyExchangeMessage;
private IdentityKey identityUpdateMessage;
@Override @Override
protected void onCreate(Bundle state) { protected void onCreate(Bundle state) {
@ -99,8 +101,11 @@ public class ReceiveKeyActivity extends Activity {
} }
private void initializeText() { private void initializeText() {
if (isTrusted(keyExchangeMessage, keyExchangeMessageBundle)) initializeTrustedText(); if (isTrusted(keyExchangeMessage, keyExchangeMessageBundle, identityUpdateMessage)) {
else initializeUntrustedText(); initializeTrustedText();
} else {
initializeUntrustedText();
}
} }
private void initializeTrustedText() { private void initializeTrustedText() {
@ -113,12 +118,16 @@ public class ReceiveKeyActivity extends Activity {
spannableString.setSpan(new ClickableSpan() { spannableString.setSpan(new ClickableSpan() {
@Override @Override
public void onClick(View widget) { 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 intent = new Intent(ReceiveKeyActivity.this, VerifyIdentityActivity.class);
intent.putExtra("recipient", recipient); intent.putExtra("recipient", recipient);
intent.putExtra("master_secret", masterSecret); intent.putExtra("master_secret", masterSecret);
intent.putExtra("remote_identity", intent.putExtra("remote_identity", remoteIdentity);
keyExchangeMessage == null ?
keyExchangeMessageBundle.getIdentityKey() : keyExchangeMessage.getIdentityKey());
startActivity(intent); startActivity(intent);
} }
}, getString(R.string.ReceiveKeyActivity_the_signature_on_this_key_exchange_is_different).length() +1, }, 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()); 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); RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), recipientDeviceId);
if (message != null) { if (message != null) {
@ -138,6 +147,9 @@ public class ReceiveKeyActivity extends Activity {
} else if (messageBundle != null) { } else if (messageBundle != null) {
KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(this, masterSecret, recipientDevice); KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(this, masterSecret, recipientDevice);
return processor.isTrusted(messageBundle); return processor.isTrusted(messageBundle);
} else if (identityUpdateMessage != null) {
KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(this, masterSecret, recipientDevice);
return processor.isTrusted(identityUpdateMessage);
} }
return false; return false;
@ -154,6 +166,8 @@ public class ReceiveKeyActivity extends Activity {
byte[] body = transportDetails.getDecodedMessage(messageBody.getBytes()); byte[] body = transportDetails.getDecodedMessage(messageBody.getBytes());
this.keyExchangeMessageBundle = new PreKeyWhisperMessage(body); this.keyExchangeMessageBundle = new PreKeyWhisperMessage(body);
} else if (getIntent().getBooleanExtra("is_identity_update", false)) {
this.identityUpdateMessage = new IdentityKey(Base64.decodeWithoutPadding(messageBody), 0);
} else { } else {
this.keyExchangeMessage = KeyExchangeMessage.createFor(messageBody); this.keyExchangeMessage = KeyExchangeMessage.createFor(messageBody);
} }
@ -232,6 +246,11 @@ public class ReceiveKeyActivity extends Activity {
DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this) DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
.markAsCorruptKeyExchange(messageId); .markAsCorruptKeyExchange(messageId);
} }
} else if (identityUpdateMessage != null) {
DatabaseFactory.getIdentityDatabase(ReceiveKeyActivity.this)
.saveIdentity(masterSecret, recipient.getRecipientId(), identityUpdateMessage);
DatabaseFactory.getSmsDatabase(ReceiveKeyActivity.this).markAsProcessedKeyExchange(messageId);
} }

View File

@ -55,6 +55,10 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
return isTrusted(message.getIdentityKey()); return isTrusted(message.getIdentityKey());
} }
public boolean isTrusted(PreKeyEntity entity) {
return isTrusted(entity.getIdentityKey());
}
public boolean isTrusted(KeyExchangeMessage message) { public boolean isTrusted(KeyExchangeMessage message) {
return message.hasIdentityKey() && isTrusted(message.getIdentityKey()); return message.hasIdentityKey() && isTrusted(message.getIdentityKey());
} }

View File

@ -35,6 +35,7 @@ public interface MmsSmsColumns {
protected static final long KEY_EXCHANGE_CORRUPTED_BIT = 0x1000; 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_INVALID_VERSION_BIT = 0x800;
protected static final long KEY_EXCHANGE_BUNDLE_BIT = 0x400; protected static final long KEY_EXCHANGE_BUNDLE_BIT = 0x400;
protected static final long KEY_EXCHANGE_IDENTITY_UPDATE_BIT = 0x200;
// Secure Message Information // Secure Message Information
protected static final long SECURE_MESSAGE_BIT = 0x800000; protected static final long SECURE_MESSAGE_BIT = 0x800000;
@ -98,6 +99,10 @@ public interface MmsSmsColumns {
return (type & KEY_EXCHANGE_BUNDLE_BIT) != 0; 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) { public static boolean isSymmetricEncryption(long type) {
return (type & ENCRYPTION_SYMMETRIC_BIT) != 0; return (type & ENCRYPTION_SYMMETRIC_BIT) != 0;
} }

View File

@ -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).isCorrupted()) type |= Types.KEY_EXCHANGE_CORRUPTED_BIT;
else if (((IncomingKeyExchangeMessage)message).isInvalidVersion()) type |= Types.KEY_EXCHANGE_INVALID_VERSION_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).isPreKeyBundle()) type |= Types.KEY_EXCHANGE_BUNDLE_BIT;
else if (((IncomingKeyExchangeMessage)message).isIdentityUpdate()) type |= Types.KEY_EXCHANGE_IDENTITY_UPDATE_BIT;
} else if (message.isSecureMessage()) { } else if (message.isSecureMessage()) {
type |= Types.SECURE_MESSAGE_BIT; type |= Types.SECURE_MESSAGE_BIT;
type |= Types.ENCRYPTION_REMOTE_BIT; type |= Types.ENCRYPTION_REMOTE_BIT;

View File

@ -111,6 +111,10 @@ public abstract class MessageRecord extends DisplayRecord {
return SmsDatabase.Types.isBundleKeyExchange(type); return SmsDatabase.Types.isBundleKeyExchange(type);
} }
public boolean isIdentityUpdate() {
return SmsDatabase.Types.isIdentityUpdate(type);
}
public boolean isCorruptedKeyExchange() { public boolean isCorruptedKeyExchange() {
return SmsDatabase.Types.isCorruptedKeyExchange(type); return SmsDatabase.Types.isCorruptedKeyExchange(type);
} }

View File

@ -65,6 +65,8 @@ public class SmsMessageRecord extends MessageRecord {
return emphasisAdded(context.getString(R.string.SmsMessageRecord_received_key_exchange_message_for_invalid_protocol_version)); return emphasisAdded(context.getString(R.string.SmsMessageRecord_received_key_exchange_message_for_invalid_protocol_version));
} else if (isBundleKeyExchange()) { } else if (isBundleKeyExchange()) {
return emphasisAdded(context.getString(R.string.SmsMessageRecord_received_message_with_unknown_identity_key_click_to_process)); 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()) { } else if (isKeyExchange() && isOutgoing()) {
return new SpannableString(""); return new SpannableString("");
} else if (isKeyExchange() && !isOutgoing()) { } else if (isKeyExchange() && !isOutgoing()) {

View File

@ -30,10 +30,14 @@ import org.thoughtcrime.securesms.mms.MmsSendResult;
import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.service.SendReceiveService.ToastHandler; 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.RetryLaterException;
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.thoughtcrime.securesms.transport.UntrustedIdentityException;
import org.whispersystems.textsecure.crypto.MasterSecret; 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.MmsException;
import ws.com.google.android.mms.pdu.SendReq; import ws.com.google.android.mms.pdu.SendReq;
@ -87,6 +91,11 @@ public class MmsSender {
database.markAsSentFailed(message.getDatabaseMessageId()); database.markAsSentFailed(message.getDatabaseMessageId());
Recipients recipients = threads.getRecipientsForThreadId(threadId); Recipients recipients = threads.getRecipientsForThreadId(threadId);
MessageNotifier.notifyMessageDeliveryFailed(context, recipients, 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) { } catch (RetryLaterException e) {
Log.w("MmsSender", e); Log.w("MmsSender", e);
database.markAsOutbox(message.getDatabaseMessageId()); database.markAsOutbox(message.getDatabaseMessageId());

View File

@ -30,9 +30,13 @@ import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.service.SendReceiveService.ToastHandler; 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.UndeliverableMessageException;
import org.thoughtcrime.securesms.transport.UniversalTransport; import org.thoughtcrime.securesms.transport.UniversalTransport;
import org.thoughtcrime.securesms.transport.UntrustedIdentityException;
import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.util.Base64;
public class SmsSender { public class SmsSender {
@ -71,12 +75,21 @@ public class SmsSender {
else reader = database.getOutgoingMessages(masterSecret); else reader = database.getOutgoingMessages(masterSecret);
while (reader != null && (record = reader.getNext()) != null) { while (reader != null && (record = reader.getNext()) != null) {
database.markAsSending(record.getId()); try {
transport.deliver(record); 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 { } finally {
if (reader != null) if (reader != null)
reader.close(); reader.close();

View File

@ -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;
}
}

View File

@ -1,7 +1,5 @@
package org.thoughtcrime.securesms.sms; package org.thoughtcrime.securesms.sms;
import org.whispersystems.textsecure.push.IncomingPushMessage;
public class IncomingPreKeyBundleMessage extends IncomingKeyExchangeMessage { public class IncomingPreKeyBundleMessage extends IncomingKeyExchangeMessage {
public IncomingPreKeyBundleMessage(IncomingTextMessage base, String newBody) { public IncomingPreKeyBundleMessage(IncomingTextMessage base, String newBody) {

View File

@ -4,12 +4,15 @@ import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import android.telephony.SmsMessage; import android.telephony.SmsMessage;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.GroupUtil;
import org.whispersystems.textsecure.push.IncomingPushMessage; import org.whispersystems.textsecure.push.IncomingPushMessage;
import org.whispersystems.textsecure.storage.RecipientDevice; import org.whispersystems.textsecure.storage.RecipientDevice;
import java.util.List; import java.util.List;
import ws.com.google.android.mms.pdu.SendReq;
import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext; import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext;
public class IncomingTextMessage implements Parcelable { public class IncomingTextMessage implements Parcelable {
@ -121,6 +124,34 @@ public class IncomingTextMessage implements Parcelable {
this.groupActionArgument = fragments.get(0).getGroupActionArgument(); 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() { public long getSentTimestampMillis() {
return sentTimestampMillis; return sentTimestampMillis;
} }
@ -169,6 +200,10 @@ public class IncomingTextMessage implements Parcelable {
return false; return false;
} }
public boolean isIdentityUpdate() {
return false;
}
public String getGroupId() { public String getGroupId() {
return groupId; return groupId;
} }

View File

@ -73,7 +73,9 @@ public class PushTransport extends BaseTransport {
this.masterSecret = masterSecret; this.masterSecret = masterSecret;
} }
public void deliver(SmsMessageRecord message) throws IOException { public void deliver(SmsMessageRecord message)
throws IOException, UntrustedIdentityException
{
try { try {
Recipient recipient = message.getIndividualRecipient(); Recipient recipient = message.getIndividualRecipient();
long threadId = message.getThreadId(); 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); PushServiceSocket socket = PushServiceSocketFactory.create(context);
byte[] plaintext = getPlaintextMessage(socket, message); byte[] plaintext = getPlaintextMessage(socket, message);
String destination = message.getTo()[0].getString(); String destination = message.getTo()[0].getString();
@ -147,6 +151,9 @@ public class PushTransport extends BaseTransport {
} catch (IOException e) { } catch (IOException e) {
Log.w("PushTransport", e); Log.w("PushTransport", e);
failures.add(recipient); 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) 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++) { for (int i=0;i<3;i++) {
try { try {
@ -274,7 +281,7 @@ public class PushTransport extends BaseTransport {
private OutgoingPushMessageList getEncryptedMessages(PushServiceSocket socket, long threadId, private OutgoingPushMessageList getEncryptedMessages(PushServiceSocket socket, long threadId,
Recipient recipient, byte[] plaintext) Recipient recipient, byte[] plaintext)
throws IOException, InvalidNumberException throws IOException, InvalidNumberException, UntrustedIdentityException
{ {
String e164number = Util.canonicalizeNumber(context, recipient.getNumber()); String e164number = Util.canonicalizeNumber(context, recipient.getNumber());
long recipientId = recipient.getRecipientId(); long recipientId = recipient.getRecipientId();
@ -296,7 +303,7 @@ public class PushTransport extends BaseTransport {
private PushBody getEncryptedMessage(PushServiceSocket socket, long threadId, private PushBody getEncryptedMessage(PushServiceSocket socket, long threadId,
PushAddress pushAddress, byte[] plaintext) PushAddress pushAddress, byte[] plaintext)
throws IOException throws IOException, UntrustedIdentityException
{ {
if (!SessionRecordV2.hasSession(context, masterSecret, pushAddress)) { if (!SessionRecordV2.hasSession(context, masterSecret, pushAddress)) {
try { try {
@ -306,7 +313,11 @@ public class PushTransport extends BaseTransport {
PushAddress device = PushAddress.create(context, pushAddress.getRecipientId(), pushAddress.getNumber(), preKey.getDeviceId()); PushAddress device = PushAddress.create(context, pushAddress.getRecipientId(), pushAddress.getNumber(), preKey.getDeviceId());
KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(context, masterSecret, device); 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) { } catch (InvalidKeyException e) {
throw new IOException(e); throw new IOException(e);

View File

@ -18,7 +18,6 @@ package org.thoughtcrime.securesms.transport;
import android.content.Context; import android.content.Context;
import android.util.Log; import android.util.Log;
import android.widget.Toast;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.mms.MmsSendResult; import org.thoughtcrime.securesms.mms.MmsSendResult;
@ -54,7 +53,9 @@ public class UniversalTransport {
this.mmsTransport = new MmsTransport(context, masterSecret); 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)) { if (!TextSecurePreferences.isPushRegistered(context)) {
smsTransport.deliver(message); smsTransport.deliver(message);
return; return;
@ -83,7 +84,7 @@ public class UniversalTransport {
} }
public MmsSendResult deliver(SendReq mediaMessage, long threadId) public MmsSendResult deliver(SendReq mediaMessage, long threadId)
throws UndeliverableMessageException, RetryLaterException throws UndeliverableMessageException, RetryLaterException, UntrustedIdentityException
{ {
if (Util.isEmpty(mediaMessage.getTo())) { if (Util.isEmpty(mediaMessage.getTo())) {
throw new UndeliverableMessageException("No destination specified"); throw new UndeliverableMessageException("No destination specified");
@ -97,14 +98,23 @@ public class UniversalTransport {
return mmsTransport.deliver(mediaMessage); 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 { try {
Log.w("UniversalTransport", "Delivering media message with GCM..."); Log.w("UniversalTransport", "Delivering media message with GCM...");
pushTransport.deliver(mediaMessage, threadId); pushTransport.deliver(mediaMessage, threadId);
return new MmsSendResult("push".getBytes("UTF-8"), 0, true); return new MmsSendResult("push".getBytes("UTF-8"), 0, true);
} catch (IOException ioe) { } catch (IOException ioe) {
Log.w("UniversalTransport", ioe); Log.w("UniversalTransport", ioe);
if (!GroupUtil.isEncodedGroup(mediaMessage.getTo()[0].getString())) { if (!GroupUtil.isEncodedGroup(destination)) {
return mmsTransport.deliver(mediaMessage); return mmsTransport.deliver(mediaMessage);
} else { } else {
throw new RetryLaterException(); throw new RetryLaterException();

View File

@ -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;
}
}