2013-07-12 17:40:41 -07:00
|
|
|
package org.thoughtcrime.securesms.transport;
|
|
|
|
|
|
|
|
import android.content.Context;
|
|
|
|
import android.util.Log;
|
2013-08-21 19:34:11 -07:00
|
|
|
import android.util.Pair;
|
2013-07-12 17:40:41 -07:00
|
|
|
|
2013-09-08 18:19:05 -07:00
|
|
|
import com.google.protobuf.ByteString;
|
2013-08-21 17:25:19 -07:00
|
|
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
2013-08-19 10:07:07 -07:00
|
|
|
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
|
2013-07-12 17:40:41 -07:00
|
|
|
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
2013-07-17 15:13:00 -07:00
|
|
|
import org.thoughtcrime.securesms.mms.PartParser;
|
2013-08-21 17:25:19 -07:00
|
|
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
2013-09-08 18:19:05 -07:00
|
|
|
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
|
|
|
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
|
|
|
import org.thoughtcrime.securesms.recipients.Recipients;
|
2013-07-12 17:40:41 -07:00
|
|
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
2013-10-19 11:15:45 -07:00
|
|
|
import org.thoughtcrime.securesms.util.TextSecurePushCredentials;
|
2013-07-17 15:13:00 -07:00
|
|
|
import org.thoughtcrime.securesms.util.Util;
|
2013-09-09 16:46:03 -07:00
|
|
|
import org.whispersystems.textsecure.crypto.AttachmentCipher;
|
2013-08-21 17:25:19 -07:00
|
|
|
import org.whispersystems.textsecure.crypto.IdentityKey;
|
|
|
|
import org.whispersystems.textsecure.crypto.IdentityKeyPair;
|
2013-08-21 19:34:11 -07:00
|
|
|
import org.whispersystems.textsecure.crypto.KeyUtil;
|
2013-08-21 17:25:19 -07:00
|
|
|
import org.whispersystems.textsecure.crypto.MasterSecret;
|
2013-08-21 17:32:54 -07:00
|
|
|
import org.whispersystems.textsecure.crypto.MessageCipher;
|
2013-08-21 17:25:19 -07:00
|
|
|
import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage;
|
2013-10-18 22:45:27 -07:00
|
|
|
import org.whispersystems.textsecure.directory.Directory;
|
2013-08-22 20:23:05 -07:00
|
|
|
import org.whispersystems.textsecure.push.OutgoingPushMessage;
|
2013-08-19 10:07:07 -07:00
|
|
|
import org.whispersystems.textsecure.push.PreKeyEntity;
|
2013-07-18 17:42:45 -07:00
|
|
|
import org.whispersystems.textsecure.push.PushAttachmentData;
|
2013-09-09 16:46:03 -07:00
|
|
|
import org.whispersystems.textsecure.push.PushAttachmentPointer;
|
2013-10-19 16:42:25 -07:00
|
|
|
import org.whispersystems.textsecure.push.PushDestination;
|
2013-09-09 16:46:03 -07:00
|
|
|
import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent;
|
2013-07-12 17:40:41 -07:00
|
|
|
import org.whispersystems.textsecure.push.PushServiceSocket;
|
2013-08-21 19:34:11 -07:00
|
|
|
import org.whispersystems.textsecure.push.PushTransportDetails;
|
2013-07-12 17:40:41 -07:00
|
|
|
import org.whispersystems.textsecure.push.RateLimitException;
|
2013-09-09 16:46:03 -07:00
|
|
|
import org.whispersystems.textsecure.push.RawTransportDetails;
|
2013-07-12 17:40:41 -07:00
|
|
|
import org.whispersystems.textsecure.util.PhoneNumberFormatter;
|
|
|
|
|
|
|
|
import java.io.IOException;
|
2013-07-17 15:13:00 -07:00
|
|
|
import java.util.LinkedList;
|
|
|
|
import java.util.List;
|
|
|
|
|
|
|
|
import ws.com.google.android.mms.ContentType;
|
|
|
|
import ws.com.google.android.mms.pdu.PduBody;
|
|
|
|
import ws.com.google.android.mms.pdu.SendReq;
|
2013-07-12 17:40:41 -07:00
|
|
|
|
|
|
|
public class PushTransport extends BaseTransport {
|
|
|
|
|
|
|
|
private final Context context;
|
|
|
|
private final MasterSecret masterSecret;
|
|
|
|
|
|
|
|
public PushTransport(Context context, MasterSecret masterSecret) {
|
|
|
|
this.context = context.getApplicationContext();
|
|
|
|
this.masterSecret = masterSecret;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void deliver(SmsMessageRecord message) throws IOException {
|
|
|
|
try {
|
2013-10-19 16:42:25 -07:00
|
|
|
TextSecurePushCredentials credentials = TextSecurePushCredentials.getInstance();
|
|
|
|
Recipient recipient = message.getIndividualRecipient();
|
|
|
|
PushServiceSocket socket = new PushServiceSocket(context, credentials);
|
2013-10-20 12:15:36 -07:00
|
|
|
PushDestination destination = PushDestination.create(context, credentials,
|
|
|
|
recipient.getNumber());
|
2013-07-12 17:40:41 -07:00
|
|
|
|
2013-10-19 16:42:25 -07:00
|
|
|
String plaintextBody = message.getBody().getBody();
|
|
|
|
byte[] plaintext = PushMessageContent.newBuilder()
|
|
|
|
.setBody(plaintextBody)
|
|
|
|
.build().toByteArray();
|
2013-08-21 19:34:11 -07:00
|
|
|
|
2013-10-19 16:42:25 -07:00
|
|
|
Pair<Integer, byte[]> typeAndCiphertext = getEncryptedMessage(socket, recipient, destination, plaintext);
|
2013-08-21 19:34:11 -07:00
|
|
|
|
2013-10-19 16:42:25 -07:00
|
|
|
socket.sendMessage(destination, typeAndCiphertext.second, typeAndCiphertext.first);
|
2013-07-12 17:40:41 -07:00
|
|
|
|
|
|
|
context.sendBroadcast(constructSentIntent(context, message.getId(), message.getType()));
|
|
|
|
} catch (RateLimitException e) {
|
|
|
|
Log.w("PushTransport", e);
|
|
|
|
throw new IOException("Rate limit exceeded.");
|
|
|
|
}
|
|
|
|
}
|
2013-07-17 15:13:00 -07:00
|
|
|
|
2013-10-19 16:42:25 -07:00
|
|
|
public void deliver(SendReq message, List<PushDestination> destinations) throws IOException {
|
2013-07-17 15:13:00 -07:00
|
|
|
try {
|
2013-10-19 16:42:25 -07:00
|
|
|
TextSecurePushCredentials credentials = TextSecurePushCredentials.getInstance();
|
|
|
|
PushServiceSocket socket = new PushServiceSocket(context, credentials);
|
|
|
|
String messageBody = PartParser.getMessageText(message.getBody());
|
|
|
|
|
|
|
|
List<byte[]> ciphertext = new LinkedList<byte[]>();
|
|
|
|
List<Integer> types = new LinkedList<Integer>();
|
|
|
|
|
|
|
|
for (PushDestination destination : destinations) {
|
|
|
|
Recipients recipients = RecipientFactory.getRecipientsFromString(context, destination.getNumber(), false);
|
2013-09-08 18:19:05 -07:00
|
|
|
List<PushAttachmentPointer> attachments = getPushAttachmentPointers(socket, message.getBody());
|
|
|
|
PushMessageContent.Builder builder = PushMessageContent.newBuilder();
|
|
|
|
|
|
|
|
if (messageBody != null) {
|
|
|
|
builder.setBody(messageBody);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (PushAttachmentPointer attachment : attachments) {
|
|
|
|
PushMessageContent.AttachmentPointer.Builder attachmentBuilder =
|
|
|
|
PushMessageContent.AttachmentPointer.newBuilder();
|
|
|
|
|
|
|
|
attachmentBuilder.setId(attachment.getId());
|
|
|
|
attachmentBuilder.setContentType(attachment.getContentType());
|
|
|
|
attachmentBuilder.setKey(ByteString.copyFrom(attachment.getKey()));
|
|
|
|
|
|
|
|
builder.addAttachments(attachmentBuilder.build());
|
|
|
|
}
|
|
|
|
|
|
|
|
byte[] plaintext = builder.build().toByteArray();
|
|
|
|
Pair<Integer, byte[]> typeAndCiphertext = getEncryptedMessage(socket, recipients.getPrimaryRecipient(),
|
|
|
|
destination, plaintext);
|
|
|
|
|
|
|
|
types.add(typeAndCiphertext.first);
|
|
|
|
ciphertext.add(typeAndCiphertext.second);
|
2013-08-29 17:01:30 -07:00
|
|
|
}
|
|
|
|
|
2013-10-19 16:42:25 -07:00
|
|
|
socket.sendMessage(destinations, ciphertext, types);
|
2013-09-08 18:19:05 -07:00
|
|
|
|
2013-07-17 15:13:00 -07:00
|
|
|
} catch (RateLimitException e) {
|
|
|
|
Log.w("PushTransport", e);
|
|
|
|
throw new IOException("Rate limit exceeded.");
|
2013-09-08 18:19:05 -07:00
|
|
|
} catch (RecipientFormattingException e) {
|
|
|
|
Log.w("PushTransport", e);
|
|
|
|
throw new IOException("Bad destination!");
|
2013-07-17 15:13:00 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-09-08 18:19:05 -07:00
|
|
|
private List<PushAttachmentPointer> getPushAttachmentPointers(PushServiceSocket socket, PduBody body)
|
|
|
|
throws IOException
|
|
|
|
{
|
|
|
|
List<PushAttachmentPointer> attachments = new LinkedList<PushAttachmentPointer>();
|
2013-07-17 15:13:00 -07:00
|
|
|
|
|
|
|
for (int i=0;i<body.getPartsNum();i++) {
|
|
|
|
String contentType = Util.toIsoString(body.getPart(i).getContentType());
|
|
|
|
if (ContentType.isImageType(contentType) ||
|
|
|
|
ContentType.isAudioType(contentType) ||
|
|
|
|
ContentType.isVideoType(contentType))
|
|
|
|
{
|
2013-09-08 18:19:05 -07:00
|
|
|
AttachmentCipher cipher = new AttachmentCipher();
|
|
|
|
byte[] key = cipher.getCombinedKeyMaterial();
|
|
|
|
byte[] ciphertextAttachment = cipher.encrypt(body.getPart(i).getData());
|
|
|
|
PushAttachmentData attachmentData = new PushAttachmentData(contentType, ciphertextAttachment);
|
|
|
|
long attachmentId = socket.sendAttachment(attachmentData);
|
|
|
|
|
|
|
|
attachments.add(new PushAttachmentPointer(contentType, attachmentId, key));
|
2013-07-17 15:13:00 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return attachments;
|
|
|
|
}
|
2013-08-19 10:07:07 -07:00
|
|
|
|
2013-10-19 16:42:25 -07:00
|
|
|
private Pair<Integer, byte[]> getEncryptedMessage(PushServiceSocket socket,
|
|
|
|
Recipient recipient,
|
|
|
|
PushDestination pushDestination,
|
|
|
|
byte[] plaintext)
|
2013-08-21 19:34:11 -07:00
|
|
|
throws IOException
|
|
|
|
{
|
|
|
|
if (KeyUtil.isNonPrekeySessionFor(context, masterSecret, recipient)) {
|
|
|
|
Log.w("PushTransport", "Sending standard ciphertext message...");
|
2013-08-29 17:01:30 -07:00
|
|
|
byte[] ciphertext = getEncryptedMessageForExistingSession(recipient, plaintext);
|
|
|
|
return new Pair<Integer, byte[]>(OutgoingPushMessage.TYPE_MESSAGE_CIPHERTEXT, ciphertext);
|
2013-08-21 19:34:11 -07:00
|
|
|
} else if (KeyUtil.isSessionFor(context, recipient)) {
|
|
|
|
Log.w("PushTransport", "Sending prekeybundle ciphertext message for existing session...");
|
2013-08-29 17:01:30 -07:00
|
|
|
byte[] ciphertext = getEncryptedPrekeyBundleMessageForExistingSession(recipient, plaintext);
|
|
|
|
return new Pair<Integer, byte[]>(OutgoingPushMessage.TYPE_MESSAGE_PREKEY_BUNDLE, ciphertext);
|
2013-08-21 19:34:11 -07:00
|
|
|
} else {
|
|
|
|
Log.w("PushTransport", "Sending prekeybundle ciphertext message for new session...");
|
2013-10-19 16:42:25 -07:00
|
|
|
byte[] ciphertext = getEncryptedPrekeyBundleMessageForNewSession(socket, recipient, pushDestination, plaintext);
|
2013-08-29 17:01:30 -07:00
|
|
|
return new Pair<Integer, byte[]>(OutgoingPushMessage.TYPE_MESSAGE_PREKEY_BUNDLE, ciphertext);
|
2013-08-21 19:34:11 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-29 17:01:30 -07:00
|
|
|
private byte[] getEncryptedPrekeyBundleMessageForExistingSession(Recipient recipient,
|
2013-09-08 18:19:05 -07:00
|
|
|
byte[] plaintext)
|
2013-08-21 19:34:11 -07:00
|
|
|
{
|
|
|
|
IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
|
|
|
|
IdentityKey identityKey = identityKeyPair.getPublicKey();
|
|
|
|
|
|
|
|
MessageCipher message = new MessageCipher(context, masterSecret, identityKeyPair, new RawTransportDetails());
|
2013-09-08 18:19:05 -07:00
|
|
|
byte[] bundledMessage = message.encrypt(recipient, plaintext);
|
2013-08-21 19:34:11 -07:00
|
|
|
|
|
|
|
PreKeyBundleMessage preKeyBundleMessage = new PreKeyBundleMessage(identityKey, bundledMessage);
|
|
|
|
return preKeyBundleMessage.serialize();
|
|
|
|
}
|
|
|
|
|
2013-08-29 17:01:30 -07:00
|
|
|
private byte[] getEncryptedPrekeyBundleMessageForNewSession(PushServiceSocket socket,
|
2013-08-21 19:34:11 -07:00
|
|
|
Recipient recipient,
|
2013-10-19 16:42:25 -07:00
|
|
|
PushDestination pushDestination,
|
2013-09-08 18:19:05 -07:00
|
|
|
byte[] plaintext)
|
2013-08-21 17:25:19 -07:00
|
|
|
throws IOException
|
|
|
|
{
|
|
|
|
IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
|
|
|
|
IdentityKey identityKey = identityKeyPair.getPublicKey();
|
2013-10-19 16:42:25 -07:00
|
|
|
PreKeyEntity preKey = socket.getPreKey(pushDestination);
|
2013-08-21 17:25:19 -07:00
|
|
|
KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipient);
|
|
|
|
|
2013-08-19 10:07:07 -07:00
|
|
|
processor.processKeyExchangeMessage(preKey);
|
|
|
|
|
2013-08-21 17:32:54 -07:00
|
|
|
MessageCipher message = new MessageCipher(context, masterSecret, identityKeyPair, new RawTransportDetails());
|
2013-09-08 18:19:05 -07:00
|
|
|
byte[] bundledMessage = message.encrypt(recipient, plaintext);
|
2013-08-21 17:25:19 -07:00
|
|
|
|
|
|
|
PreKeyBundleMessage preKeyBundleMessage = new PreKeyBundleMessage(identityKey, bundledMessage);
|
2013-08-21 19:34:11 -07:00
|
|
|
return preKeyBundleMessage.serialize();
|
2013-08-19 10:07:07 -07:00
|
|
|
}
|
|
|
|
|
2013-09-08 18:19:05 -07:00
|
|
|
private byte[] getEncryptedMessageForExistingSession(Recipient recipient, byte[] plaintext)
|
2013-08-21 17:25:19 -07:00
|
|
|
throws IOException
|
|
|
|
{
|
2013-08-21 19:34:11 -07:00
|
|
|
IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
|
|
|
|
MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKeyPair,
|
|
|
|
new PushTransportDetails());
|
|
|
|
|
2013-09-08 18:19:05 -07:00
|
|
|
return messageCipher.encrypt(recipient, plaintext);
|
2013-08-19 10:07:07 -07:00
|
|
|
}
|
|
|
|
|
2013-07-12 17:40:41 -07:00
|
|
|
}
|