Support for multi-device.

1) In addition to the Recipient interface, there is now
   RecipientDevice.  A Recipient can have multiple corresponding
   RecipientDevices.  All addressing is done to a Recipient, but
   crypto sessions and transport delivery are done to
   RecipientDevice.

2) The Push transport handles the discovery and session setup
   of additional Recipient devices.

3) Some internal rejiggering of Groups.
This commit is contained in:
Moxie Marlinspike
2014-02-02 19:38:06 -08:00
parent 49daa45dca
commit 0ace469d74
70 changed files with 1118 additions and 668 deletions

View File

@@ -33,7 +33,7 @@ public class AvatarDownloader {
if (!SendReceiveService.DOWNLOAD_AVATAR_ACTION.equals(intent.getAction()))
return;
String groupId = intent.getStringExtra("group_id");
byte[] groupId = intent.getByteArrayExtra("group_id");
GroupDatabase database = DatabaseFactory.getGroupDatabase(context);
GroupDatabase.Reader reader = database.getGroup(groupId);

View File

@@ -17,12 +17,14 @@ import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage;
import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage;
import org.thoughtcrime.securesms.sms.IncomingPreKeyBundleMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.sms.SmsTransportDetails;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.InvalidMessageException;
@@ -32,6 +34,7 @@ import org.whispersystems.textsecure.crypto.protocol.PreKeyWhisperMessage;
import org.whispersystems.textsecure.push.IncomingPushMessage;
import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent;
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.util.Hex;
import ws.com.google.android.mms.MmsException;
@@ -106,9 +109,10 @@ public class PushReceiver {
}
try {
Recipient recipient = new Recipient(null, message.getSource(), null, null);
KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(context, masterSecret, recipient);
PreKeyWhisperMessage preKeyExchange = new PreKeyWhisperMessage(message.getBody());
Recipient recipient = RecipientFactory.getRecipientsFromString(context, message.getSource(), false).getPrimaryRecipient();
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), message.getSourceDevice());
KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(context, masterSecret, recipientDevice);
PreKeyWhisperMessage preKeyExchange = new PreKeyWhisperMessage(message.getBody());
if (processor.isTrusted(preKeyExchange)) {
processor.processKeyExchangeMessage(preKeyExchange);
@@ -135,6 +139,9 @@ public class PushReceiver {
} catch (InvalidMessageException e) {
Log.w("PushReceiver", e);
handleReceivedCorruptedKey(masterSecret, message, false);
} catch (RecipientFormattingException e) {
Log.w("PushReceiver", e);
handleReceivedCorruptedKey(masterSecret, message, false);
}
}
@@ -151,10 +158,10 @@ public class PushReceiver {
handleReceivedGroupMessage(masterSecret, message, messageContent, secure);
} else if (messageContent.getAttachmentsCount() > 0) {
Log.w("PushReceiver", "Received push media message...");
handleReceivedMediaMessage(masterSecret, message, messageContent, secure, null);
handleReceivedMediaMessage(masterSecret, message, messageContent, secure);
} else {
Log.w("PushReceiver", "Received push text message...");
handleReceivedTextMessage(masterSecret, message, messageContent, secure, null);
handleReceivedTextMessage(masterSecret, message, messageContent, secure);
}
} catch (InvalidProtocolBufferException e) {
Log.w("PushReceiver", e);
@@ -207,30 +214,28 @@ public class PushReceiver {
if (group.hasAvatar()) {
Intent intent = new Intent(context, SendReceiveService.class);
intent.setAction(SendReceiveService.DOWNLOAD_AVATAR_ACTION);
intent.putExtra("group_id", group.getId().toByteArray());
context.startService(intent);
}
String groupId = "g_" + Hex.toString(group.getId().toByteArray());
if (messageContent.getAttachmentsCount() > 0) {
handleReceivedMediaMessage(masterSecret, message, messageContent, secure, groupId);
handleReceivedMediaMessage(masterSecret, message, messageContent, secure);
} else if (messageContent.hasBody()) {
handleReceivedTextMessage(masterSecret, message, messageContent, secure, groupId);
handleReceivedTextMessage(masterSecret, message, messageContent, secure);
}
}
private void handleReceivedMediaMessage(MasterSecret masterSecret,
IncomingPushMessage message,
PushMessageContent messageContent,
boolean secure, String groupId)
boolean secure)
{
try {
String localNumber = TextSecurePreferences.getLocalNumber(context);
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(masterSecret, localNumber,
message, messageContent,
groupId);
message, messageContent);
Pair<Long, Long> messageAndThreadId;
@@ -255,9 +260,10 @@ public class PushReceiver {
private void handleReceivedTextMessage(MasterSecret masterSecret,
IncomingPushMessage message,
PushMessageContent messageContent,
boolean secure, String groupId)
boolean secure)
{
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
String groupId = messageContent.hasGroup() ? GroupUtil.getEncodedId(messageContent.getGroup().getId().toByteArray()) : null;
IncomingTextMessage textMessage = new IncomingTextMessage(message, "", groupId);
if (secure) {

View File

@@ -32,6 +32,8 @@ import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.protocol.WirePrefix;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage;
import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage;
import org.thoughtcrime.securesms.sms.IncomingPreKeyBundleMessage;
@@ -46,6 +48,7 @@ import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.protocol.PreKeyWhisperMessage;
import org.whispersystems.textsecure.crypto.protocol.WhisperMessageV2;
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
import org.whispersystems.textsecure.storage.RecipientDevice;
import java.io.IOException;
import java.util.List;
@@ -80,8 +83,9 @@ public class SmsReceiver {
if (masterSecret != null) {
DecryptingQueue.scheduleDecryption(context, masterSecret, messageAndThreadId.first,
messageAndThreadId.second,
message.getSender(), message.getMessageBody(),
message.isSecureMessage(), message.isKeyExchange());
message.getSender(), message.getSenderDeviceId(),
message.getMessageBody(), message.isSecureMessage(),
message.isKeyExchange());
}
return messageAndThreadId;
@@ -106,8 +110,9 @@ public class SmsReceiver {
Log.w("SmsReceiver", "Processing prekey message...");
try {
Recipient recipient = new Recipient(null, message.getSender(), null, null);
KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(context, masterSecret, recipient);
Recipient recipient = RecipientFactory.getRecipientsFromString(context, message.getSender(), false).getPrimaryRecipient();
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), message.getSenderDeviceId());
KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(context, masterSecret, recipientDevice);
SmsTransportDetails transportDetails = new SmsTransportDetails();
byte[] rawMessage = transportDetails.getDecodedMessage(message.getMessageBody().getBytes());
PreKeyWhisperMessage preKeyExchange = new PreKeyWhisperMessage(rawMessage);
@@ -142,6 +147,9 @@ public class SmsReceiver {
} catch (InvalidMessageException e) {
Log.w("SmsReceiver", e);
message.setCorrupted(true);
} catch (RecipientFormattingException e) {
Log.w("SmsReceiver", e);
message.setCorrupted(true);
}
return storeStandardMessage(masterSecret, message);
@@ -152,9 +160,10 @@ public class SmsReceiver {
{
if (masterSecret != null && TextSecurePreferences.isAutoRespondKeyExchangeEnabled(context)) {
try {
Recipient recipient = new Recipient(null, message.getSender(), null, null);
Recipient recipient = RecipientFactory.getRecipientsFromString(context, message.getSender(), false).getPrimaryRecipient();
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), message.getSenderDeviceId());
KeyExchangeMessage exchangeMessage = KeyExchangeMessage.createFor(message.getMessageBody());
KeyExchangeProcessor processor = KeyExchangeProcessor.createFor(context, masterSecret, recipient, exchangeMessage);
KeyExchangeProcessor processor = KeyExchangeProcessor.createFor(context, masterSecret, recipientDevice, exchangeMessage);
if (processor.isStale(exchangeMessage)) {
message.setStale(true);
@@ -175,6 +184,9 @@ public class SmsReceiver {
} catch (InvalidMessageException e) {
Log.w("SmsReceiver", e);
message.setCorrupted(true);
} catch (RecipientFormattingException e) {
Log.w("SmsReceiver", e);
message.setCorrupted(true);
}
}

View File

@@ -46,11 +46,11 @@ public class SmsSender {
}
public void process(MasterSecret masterSecret, Intent intent) {
if (intent.getAction().equals(SendReceiveService.SEND_SMS_ACTION)) {
if (SendReceiveService.SEND_SMS_ACTION.equals(intent.getAction())) {
handleSendMessage(masterSecret, intent);
} else if (intent.getAction().equals(SendReceiveService.SENT_SMS_ACTION)) {
} else if (SendReceiveService.SENT_SMS_ACTION.equals(intent.getAction())) {
handleSentMessage(intent);
} else if (intent.getAction().equals(SendReceiveService.DELIVERED_SMS_ACTION)) {
} else if (SendReceiveService.DELIVERED_SMS_ACTION.equals(intent.getAction())) {
handleDeliveredMessage(intent);
}
}