Support encrypted transport, properly handle multiple recipients.

1) Add encryption support for the transport layer.  This obscures
   metadata from the push messaging provider.

2) Better support the direction multiple destination messages is
   headed (one unique message per recipient).
This commit is contained in:
Moxie Marlinspike 2013-08-29 17:01:30 -07:00
parent 68ec0a3727
commit 0cc5837d7f
17 changed files with 1798 additions and 107 deletions

View file

@ -0,0 +1,19 @@
package textsecure;
option java_package = "org.whispersystems.textsecure.push";
option java_outer_classname = "PushMessageProtos";
message IncomingPushMessageSignal {
optional uint32 type = 1;
optional string source = 2;
repeated string destinations = 3;
optional bytes message = 4;
message AttachmentPointer {
optional string contentType = 1;
optional string key = 2;
}
repeated AttachmentPointer attachments = 5;
optional uint64 timestamp = 6;
}

View file

@ -1,3 +1,3 @@
all: all:
protoc --java_out=../src/ PreKeyEntity.proto protoc --java_out=../src/ IncomingPushMessageSignal.proto

View file

@ -1,10 +0,0 @@
package textsecure;
option java_package = "org.whispersystems.textsecure.encoded";
option java_outer_classname = "PreKeyProtos";
message PreKeyEntity {
optional uint64 id = 1;
optional bytes public_key = 2;
optional bytes identity_key = 3;
}

View file

@ -96,8 +96,8 @@ public class PreKeyBundleMessage {
} }
} }
public String serialize() { public byte[] serialize() {
return Base64.encodeBytesWithoutPadding(this.messageBytes); return this.messageBytes;
} }
public int getSupportedVersion() { public int getSupportedVersion() {

View file

@ -0,0 +1,136 @@
package org.whispersystems.textsecure.push;
import android.util.Log;
import org.whispersystems.textsecure.crypto.InvalidVersionException;
import org.whispersystems.textsecure.util.Base64;
import org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal;
import org.whispersystems.textsecure.util.Hex;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
public class IncomingEncryptedPushMessage {
private static final int SUPPORTED_VERSION = 1;
private static final int CIPHER_KEY_SIZE = 32;
private static final int MAC_KEY_SIZE = 20;
private static final int MAC_SIZE = 10;
private static final int VERSION_OFFSET = 0;
private static final int VERSION_LENGTH = 1;
private static final int IV_OFFSET = VERSION_OFFSET + VERSION_LENGTH;
private static final int IV_LENGTH = 16;
private static final int CIPHERTEXT_OFFSET = IV_OFFSET + IV_LENGTH;
private final IncomingPushMessage incomingPushMessage;
public IncomingEncryptedPushMessage(String message, String signalingKey)
throws IOException, InvalidVersionException
{
byte[] ciphertext = Base64.decode(message);
if (ciphertext.length < VERSION_LENGTH || ciphertext[VERSION_OFFSET] != SUPPORTED_VERSION)
throw new InvalidVersionException("Unsupported version!");
SecretKeySpec cipherKey = getCipherKey(signalingKey);
SecretKeySpec macKey = getMacKey(signalingKey);
verifyMac(ciphertext, macKey);
byte[] plaintext = getPlaintext(ciphertext, cipherKey);
IncomingPushMessageSignal signal = IncomingPushMessageSignal.parseFrom(plaintext);
this.incomingPushMessage = new IncomingPushMessage(signal);
}
public IncomingPushMessage getIncomingPushMessage() {
return incomingPushMessage;
}
private byte[] getPlaintext(byte[] ciphertext, SecretKeySpec cipherKey) throws IOException {
try {
byte[] ivBytes = new byte[IV_LENGTH];
System.arraycopy(ciphertext, IV_OFFSET, ivBytes, 0, ivBytes.length);
IvParameterSpec iv = new IvParameterSpec(ivBytes);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, cipherKey, iv);
return cipher.doFinal(ciphertext, CIPHERTEXT_OFFSET,
ciphertext.length - VERSION_LENGTH - IV_LENGTH - MAC_SIZE);
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
} catch (NoSuchPaddingException e) {
throw new AssertionError(e);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
} catch (InvalidAlgorithmParameterException e) {
throw new AssertionError(e);
} catch (IllegalBlockSizeException e) {
throw new AssertionError(e);
} catch (BadPaddingException e) {
Log.w("IncomingEncryptedPushMessage", e);
throw new IOException("Bad padding?");
}
}
private void verifyMac(byte[] ciphertext, SecretKeySpec macKey) throws IOException {
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(macKey);
if (ciphertext.length < MAC_SIZE + 1)
throw new IOException("Invalid MAC!");
mac.update(ciphertext, 0, ciphertext.length - MAC_SIZE);
byte[] ourMacFull = mac.doFinal();
byte[] ourMacBytes = new byte[MAC_SIZE];
System.arraycopy(ourMacFull, 0, ourMacBytes, 0, ourMacBytes.length);
byte[] theirMacBytes = new byte[MAC_SIZE];
System.arraycopy(ciphertext, ciphertext.length-MAC_SIZE, theirMacBytes, 0, theirMacBytes.length);
Log.w("IncomingEncryptedPushMessage", "Our MAC: " + Hex.toString(ourMacBytes));
Log.w("IncomingEncryptedPushMessage", "Thr MAC: " + Hex.toString(theirMacBytes));
if (!Arrays.equals(ourMacBytes, theirMacBytes)) {
throw new IOException("Invalid MAC compare!");
}
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}
}
private SecretKeySpec getCipherKey(String signalingKey) throws IOException {
byte[] signalingKeyBytes = Base64.decode(signalingKey);
byte[] cipherKey = new byte[CIPHER_KEY_SIZE];
System.arraycopy(signalingKeyBytes, 0, cipherKey, 0, cipherKey.length);
return new SecretKeySpec(cipherKey, "AES");
}
private SecretKeySpec getMacKey(String signalingKey) throws IOException {
byte[] signalingKeyBytes = Base64.decode(signalingKey);
byte[] macKey = new byte[MAC_KEY_SIZE];
System.arraycopy(signalingKeyBytes, CIPHER_KEY_SIZE, macKey, 0, macKey.length);
return new SecretKeySpec(macKey, "HmacSHA256");
}
}

View file

@ -1,12 +1,16 @@
package org.whispersystems.textsecure.push; package org.whispersystems.textsecure.push;
import org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal;
import org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer;
import org.whispersystems.textsecure.util.Base64;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
public class IncomingPushMessage implements Parcelable { public class IncomingPushMessage implements PushMessage, Parcelable {
public static final Parcelable.Creator<IncomingPushMessage> CREATOR = new Parcelable.Creator<IncomingPushMessage>() { public static final Parcelable.Creator<IncomingPushMessage> CREATOR = new Parcelable.Creator<IncomingPushMessage>() {
@Override @Override
@ -23,19 +27,23 @@ public class IncomingPushMessage implements Parcelable {
private int type; private int type;
private String source; private String source;
private List<String> destinations; private List<String> destinations;
private String messageText; private byte[] message;
private List<PushAttachmentPointer> attachments; private List<PushAttachmentPointer> attachments;
private long timestamp; private long timestamp;
public IncomingPushMessage(String source, List<String> destinations, String messageText, public IncomingPushMessage(IncomingPushMessageSignal signal) {
int type, List<PushAttachmentPointer> attachments, long timestamp) this.type = signal.getType();
{ this.source = signal.getSource();
this.type = type; this.destinations = signal.getDestinationsList();
this.source = source; this.message = signal.getMessage().toByteArray();
this.destinations = destinations; this.timestamp = signal.getTimestamp();
this.messageText = messageText; this.attachments = new LinkedList<PushAttachmentPointer>();
this.attachments = attachments;
this.timestamp = timestamp; List<AttachmentPointer> attachmentPointers = signal.getAttachmentsList();
for (AttachmentPointer pointer : attachmentPointers) {
this.attachments.add(new PushAttachmentPointer(pointer.getContentType(), pointer.getKey()));
}
} }
public IncomingPushMessage(Parcel in) { public IncomingPushMessage(Parcel in) {
@ -44,7 +52,8 @@ public class IncomingPushMessage implements Parcelable {
this.source = in.readString(); this.source = in.readString();
in.readStringList(destinations); in.readStringList(destinations);
this.messageText = in.readString(); this.message = new byte[in.readInt()];
in.readByteArray(this.message);
in.readList(attachments, PushAttachmentPointer.class.getClassLoader()); in.readList(attachments, PushAttachmentPointer.class.getClassLoader());
this.timestamp = in.readLong(); this.timestamp = in.readLong();
} }
@ -62,7 +71,14 @@ public class IncomingPushMessage implements Parcelable {
} }
public String getMessageText() { public String getMessageText() {
return messageText; if (type == TYPE_MESSAGE_CIPHERTEXT ||
type == TYPE_MESSAGE_KEY_EXCHANGE ||
type == TYPE_MESSAGE_PREKEY_BUNDLE)
{
return Base64.encodeBytesWithoutPadding(message);
}
return new String(message);
} }
public List<String> getDestinations() { public List<String> getDestinations() {
@ -82,7 +98,8 @@ public class IncomingPushMessage implements Parcelable {
public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(Parcel dest, int flags) {
dest.writeString(source); dest.writeString(source);
dest.writeStringList(destinations); dest.writeStringList(destinations);
dest.writeString(messageText); dest.writeInt(message.length);
dest.writeByteArray(message);
dest.writeList(attachments); dest.writeList(attachments);
dest.writeLong(timestamp); dest.writeLong(timestamp);
} }

View file

@ -1,50 +1,40 @@
package org.whispersystems.textsecure.push; package org.whispersystems.textsecure.push;
import org.whispersystems.textsecure.util.Base64;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
public class OutgoingPushMessage { public class OutgoingPushMessage implements PushMessage {
public static final int TYPE_MESSAGE_PLAINTEXT = 0;
public static final int TYPE_MESSAGE_CIPHERTEXT = 1;
public static final int TYPE_MESSAGE_KEY_EXCHANGE = 2;
public static final int TYPE_MESSAGE_PREKEY_BUNDLE = 3;
private int type; private int type;
private List<String> destinations; private String destination;
private String messageText; private String body;
private List<PushAttachmentPointer> attachments; private List<PushAttachmentPointer> attachments;
public OutgoingPushMessage(String destination, String messageText, int type) { public OutgoingPushMessage(String destination, byte[] body, int type) {
this.destinations = new LinkedList<String>(); this.attachments = new LinkedList<PushAttachmentPointer>();
this.attachments = new LinkedList<PushAttachmentPointer>(); this.destination = destination;
this.messageText = messageText; this.body = Base64.encodeBytes(body);
this.destinations.add(destination); this.type = type;
this.type = type;
} }
public OutgoingPushMessage(List<String> destinations, String messageText, int type) { public OutgoingPushMessage(String destination, byte[] body,
this.destinations = destinations; List<PushAttachmentPointer> attachments,
this.messageText = messageText; int type)
this.attachments = new LinkedList<PushAttachmentPointer>();
this.type = type;
}
public OutgoingPushMessage(List<String> destinations, String messageText,
List<PushAttachmentPointer> attachments, int type)
{ {
this.destinations = destinations; this.destination = destination;
this.messageText = messageText; this.body = Base64.encodeBytes(body);
this.attachments = attachments; this.attachments = attachments;
this.type = type; this.type = type;
} }
public List<String> getDestinations() { public String getDestination() {
return destinations; return destination;
} }
public String getMessageText() { public String getBody() {
return messageText; return body;
} }
public List<PushAttachmentPointer> getAttachments() { public List<PushAttachmentPointer> getAttachments() {

View file

@ -0,0 +1,22 @@
package org.whispersystems.textsecure.push;
import java.util.LinkedList;
import java.util.List;
public class OutgoingPushMessageList {
private List<OutgoingPushMessage> messages;
public OutgoingPushMessageList(OutgoingPushMessage message) {
this.messages = new LinkedList<OutgoingPushMessage>();
this.messages.add(message);
}
public OutgoingPushMessageList(List<OutgoingPushMessage> messages) {
this.messages = messages;
}
public List<OutgoingPushMessage> getMessages() {
return messages;
}
}

View file

@ -0,0 +1,9 @@
package org.whispersystems.textsecure.push;
public interface PushMessage {
public static final int TYPE_MESSAGE_PLAINTEXT = 0;
public static final int TYPE_MESSAGE_CIPHERTEXT = 1;
public static final int TYPE_MESSAGE_KEY_EXCHANGE = 2;
public static final int TYPE_MESSAGE_PREKEY_BUNDLE = 3;
}

File diff suppressed because it is too large Load diff

View file

@ -4,13 +4,10 @@ import android.content.Context;
import android.util.Log; import android.util.Log;
import android.util.Pair; import android.util.Pair;
import com.google.protobuf.ByteString;
import com.google.thoughtcrimegson.Gson; import com.google.thoughtcrimegson.Gson;
import org.whispersystems.textsecure.R; import org.whispersystems.textsecure.R;
import org.whispersystems.textsecure.Release; import org.whispersystems.textsecure.Release;
import org.whispersystems.textsecure.crypto.IdentityKey; import org.whispersystems.textsecure.crypto.IdentityKey;
import org.whispersystems.textsecure.crypto.PreKeyPair;
import org.whispersystems.textsecure.crypto.PreKeyPublic;
import org.whispersystems.textsecure.directory.DirectoryDescriptor; import org.whispersystems.textsecure.directory.DirectoryDescriptor;
import org.whispersystems.textsecure.storage.PreKeyRecord; import org.whispersystems.textsecure.storage.PreKeyRecord;
import org.whispersystems.textsecure.util.Base64; import org.whispersystems.textsecure.util.Base64;
@ -33,6 +30,7 @@ import java.security.KeyStore;
import java.security.KeyStoreException; import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -79,31 +77,45 @@ public class PushServiceSocket {
makeRequest(REGISTER_GCM_PATH, "DELETE", null); makeRequest(REGISTER_GCM_PATH, "DELETE", null);
} }
public void sendMessage(String recipient, String messageText, int type) public void sendMessage(String recipient, byte[] body, int type)
throws IOException throws IOException
{ {
OutgoingPushMessage message = new OutgoingPushMessage(recipient, messageText, type); OutgoingPushMessage message = new OutgoingPushMessage(recipient, body, type);
sendMessage(message); sendMessage(new OutgoingPushMessageList(message));
} }
public void sendMessage(List<String> recipients, String messageText, int type) public void sendMessage(List<String> recipients, List<byte[]> bodies,
List<List<PushAttachmentData>> attachmentsList, int type)
throws IOException throws IOException
{ {
OutgoingPushMessage message = new OutgoingPushMessage(recipients, messageText, type); List<OutgoingPushMessage> messages = new LinkedList<OutgoingPushMessage>();
sendMessage(message);
Iterator<String> recipientsIterator = recipients.iterator();
Iterator<byte[]> bodiesIterator = bodies.iterator();
Iterator<List<PushAttachmentData>> attachmentsIterator = attachmentsList.iterator();
while (recipientsIterator.hasNext()) {
String recipient = recipientsIterator.next();
byte[] body = bodiesIterator.next();
List<PushAttachmentData> attachments = attachmentsIterator.next();
OutgoingPushMessage message;
if (!attachments.isEmpty()) {
List<PushAttachmentPointer> attachmentIds = sendAttachments(attachments);
message = new OutgoingPushMessage(recipient, body, attachmentIds, type);
} else {
message = new OutgoingPushMessage(recipient, body, type);
}
messages.add(message);
}
sendMessage(new OutgoingPushMessageList(messages));
} }
public void sendMessage(List<String> recipients, String messageText, private void sendMessage(OutgoingPushMessageList messages) throws IOException {
List<PushAttachmentData> attachments, int type) String responseText = makeRequest(MESSAGE_PATH, "POST", new Gson().toJson(messages));
throws IOException
{
List<PushAttachmentPointer> attachmentIds = sendAttachments(attachments);
OutgoingPushMessage message = new OutgoingPushMessage(recipients, messageText, attachmentIds, type);
sendMessage(message);
}
private void sendMessage(OutgoingPushMessage message) throws IOException {
String responseText = makeRequest(MESSAGE_PATH, "POST", new Gson().toJson(message));
PushMessageResponse response = new Gson().fromJson(responseText, PushMessageResponse.class); PushMessageResponse response = new Gson().fromJson(responseText, PushMessageResponse.class);
if (response.getFailure().size() != 0) if (response.getFailure().size() != 0)

View file

@ -34,11 +34,11 @@ public class PushTransportDetails implements TransportDetails {
@Override @Override
public byte[] getEncodedMessage(byte[] messageWithMac) { public byte[] getEncodedMessage(byte[] messageWithMac) {
return Base64.encodeBytesWithoutPadding(messageWithMac).getBytes(); return messageWithMac;
} }
@Override @Override
public byte[] getDecodedMessage(byte[] encodedMessageBytes) throws IOException { public byte[] getDecodedMessage(byte[] encodedMessageBytes) throws IOException {
return Base64.decodeWithoutPadding(new String(encodedMessageBytes)); return encodedMessageBytes;
} }
} }

View file

@ -5,11 +5,12 @@ import android.content.Intent;
import android.util.Log; import android.util.Log;
import com.google.android.gcm.GCMBaseIntentService; import com.google.android.gcm.GCMBaseIntentService;
import com.google.thoughtcrimegson.Gson;
import org.thoughtcrime.securesms.service.RegistrationService; import org.thoughtcrime.securesms.service.RegistrationService;
import org.thoughtcrime.securesms.service.SendReceiveService; import org.thoughtcrime.securesms.service.SendReceiveService;
import org.thoughtcrime.securesms.sms.IncomingTextMessage; import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.textsecure.crypto.InvalidVersionException;
import org.whispersystems.textsecure.push.IncomingEncryptedPushMessage;
import org.whispersystems.textsecure.push.IncomingPushMessage; import org.whispersystems.textsecure.push.IncomingPushMessage;
import org.whispersystems.textsecure.push.PushServiceSocket; import org.whispersystems.textsecure.push.PushServiceSocket;
import org.whispersystems.textsecure.util.Util; import org.whispersystems.textsecure.util.Util;
@ -48,16 +49,24 @@ public class GcmIntentService extends GCMBaseIntentService {
@Override @Override
protected void onMessage(Context context, Intent intent) { protected void onMessage(Context context, Intent intent) {
String data = intent.getStringExtra("message"); try {
Log.w("GcmIntentService", "GCM message: " + data); String data = intent.getStringExtra("message");
Log.w("GcmIntentService", "GCM message: " + data);
if (Util.isEmpty(data)) if (Util.isEmpty(data))
return; return;
IncomingPushMessage message = new Gson().fromJson(data, IncomingPushMessage.class); String sessionKey = TextSecurePreferences.getSignalingKey(context);
IncomingEncryptedPushMessage encryptedMessage = new IncomingEncryptedPushMessage(data, sessionKey);
IncomingPushMessage message = encryptedMessage.getIncomingPushMessage();
if (!message.hasAttachments()) handleIncomingTextMessage(context, message); if (!message.hasAttachments()) handleIncomingTextMessage(context, message);
else handleIncomingMediaMessage(context, message); else handleIncomingMediaMessage(context, message);
} catch (IOException e) {
Log.w("GcmIntentService", e);
} catch (InvalidVersionException e) {
Log.w("GcmIntentService", e);
}
} }
@Override @Override

View file

@ -42,6 +42,7 @@ import org.whispersystems.textsecure.crypto.InvalidVersionException;
import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage; import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage;
import org.whispersystems.textsecure.push.OutgoingPushMessage; import org.whispersystems.textsecure.push.OutgoingPushMessage;
import org.whispersystems.textsecure.push.PushMessage;
import org.whispersystems.textsecure.storage.InvalidKeyIdException; import org.whispersystems.textsecure.storage.InvalidKeyIdException;
import java.util.List; import java.util.List;
@ -62,11 +63,11 @@ public class SmsReceiver {
IncomingTextMessage message = messages.get(0); IncomingTextMessage message = messages.get(0);
switch (pushType) { switch (pushType) {
case OutgoingPushMessage.TYPE_MESSAGE_CIPHERTEXT: case PushMessage.TYPE_MESSAGE_CIPHERTEXT:
return new IncomingEncryptedMessage(message, message.getMessageBody()); return new IncomingEncryptedMessage(message, message.getMessageBody());
case OutgoingPushMessage.TYPE_MESSAGE_PREKEY_BUNDLE: case PushMessage.TYPE_MESSAGE_PREKEY_BUNDLE:
return new IncomingPreKeyBundleMessage(message, message.getMessageBody()); return new IncomingPreKeyBundleMessage(message, message.getMessageBody());
case OutgoingPushMessage.TYPE_MESSAGE_KEY_EXCHANGE: case PushMessage.TYPE_MESSAGE_KEY_EXCHANGE:
return new IncomingKeyExchangeMessage(message, message.getMessageBody()); return new IncomingKeyExchangeMessage(message, message.getMessageBody());
} }

View file

@ -55,7 +55,7 @@ public class PushTransport extends BaseTransport {
String recipientCanonicalNumber = PhoneNumberFormatter.formatNumber(recipient.getNumber(), String recipientCanonicalNumber = PhoneNumberFormatter.formatNumber(recipient.getNumber(),
localNumber); localNumber);
Pair<Integer, String> typeAndCiphertext = getEncryptedMessage(socket, recipient, recipientCanonicalNumber, plaintext); Pair<Integer, byte[]> typeAndCiphertext = getEncryptedMessage(socket, recipient, recipientCanonicalNumber, plaintext);
socket.sendMessage(recipientCanonicalNumber, typeAndCiphertext.second, typeAndCiphertext.first); socket.sendMessage(recipientCanonicalNumber, typeAndCiphertext.second, typeAndCiphertext.first);
@ -71,11 +71,19 @@ public class PushTransport extends BaseTransport {
String localNumber = TextSecurePreferences.getLocalNumber(context); String localNumber = TextSecurePreferences.getLocalNumber(context);
String password = TextSecurePreferences.getPushServerPassword(context); String password = TextSecurePreferences.getPushServerPassword(context);
PushServiceSocket socket = new PushServiceSocket(context, localNumber, password); PushServiceSocket socket = new PushServiceSocket(context, localNumber, password);
String messageText = PartParser.getMessageText(message.getBody()); byte[] messageText = PartParser.getMessageText(message.getBody()).getBytes();
List<PushAttachmentData> attachments = getAttachmentsFromBody(message.getBody()); List<PushAttachmentData> attachments = getAttachmentsFromBody(message.getBody());
if (attachments.isEmpty()) socket.sendMessage(destinations, messageText, OutgoingPushMessage.TYPE_MESSAGE_PLAINTEXT); List<byte[]> messagesList = new LinkedList<byte[]>();
else socket.sendMessage(destinations, messageText, attachments, OutgoingPushMessage.TYPE_MESSAGE_PLAINTEXT); List<List<PushAttachmentData>> attachmentsList = new LinkedList<List<PushAttachmentData>>();
for (String recipient : destinations) {
messagesList.add(messageText);
attachmentsList.add(attachments);
}
socket.sendMessage(destinations, messagesList, attachmentsList,
OutgoingPushMessage.TYPE_MESSAGE_PLAINTEXT);
} 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.");
@ -99,26 +107,26 @@ public class PushTransport extends BaseTransport {
return attachments; return attachments;
} }
private Pair<Integer, String> getEncryptedMessage(PushServiceSocket socket, Recipient recipient, private Pair<Integer, byte[]> getEncryptedMessage(PushServiceSocket socket, Recipient recipient,
String canonicalRecipientNumber, String plaintext) String canonicalRecipientNumber, String plaintext)
throws IOException throws IOException
{ {
if (KeyUtil.isNonPrekeySessionFor(context, masterSecret, recipient)) { if (KeyUtil.isNonPrekeySessionFor(context, masterSecret, recipient)) {
Log.w("PushTransport", "Sending standard ciphertext message..."); Log.w("PushTransport", "Sending standard ciphertext message...");
String ciphertext = getEncryptedMessageForExistingSession(recipient, plaintext); byte[] ciphertext = getEncryptedMessageForExistingSession(recipient, plaintext);
return new Pair<Integer, String>(OutgoingPushMessage.TYPE_MESSAGE_CIPHERTEXT, ciphertext); return new Pair<Integer, byte[]>(OutgoingPushMessage.TYPE_MESSAGE_CIPHERTEXT, ciphertext);
} else if (KeyUtil.isSessionFor(context, recipient)) { } else if (KeyUtil.isSessionFor(context, recipient)) {
Log.w("PushTransport", "Sending prekeybundle ciphertext message for existing session..."); Log.w("PushTransport", "Sending prekeybundle ciphertext message for existing session...");
String ciphertext = getEncryptedPrekeyBundleMessageForExistingSession(recipient, plaintext); byte[] ciphertext = getEncryptedPrekeyBundleMessageForExistingSession(recipient, plaintext);
return new Pair<Integer, String>(OutgoingPushMessage.TYPE_MESSAGE_PREKEY_BUNDLE, ciphertext); return new Pair<Integer, byte[]>(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...");
String ciphertext = getEncryptedPrekeyBundleMessageForNewSession(socket, recipient, canonicalRecipientNumber, plaintext); byte[] ciphertext = getEncryptedPrekeyBundleMessageForNewSession(socket, recipient, canonicalRecipientNumber, plaintext);
return new Pair<Integer, String>(OutgoingPushMessage.TYPE_MESSAGE_PREKEY_BUNDLE, ciphertext); return new Pair<Integer, byte[]>(OutgoingPushMessage.TYPE_MESSAGE_PREKEY_BUNDLE, ciphertext);
} }
} }
private String getEncryptedPrekeyBundleMessageForExistingSession(Recipient recipient, private byte[] getEncryptedPrekeyBundleMessageForExistingSession(Recipient recipient,
String plaintext) String plaintext)
{ {
IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
@ -131,7 +139,7 @@ public class PushTransport extends BaseTransport {
return preKeyBundleMessage.serialize(); return preKeyBundleMessage.serialize();
} }
private String getEncryptedPrekeyBundleMessageForNewSession(PushServiceSocket socket, private byte[] getEncryptedPrekeyBundleMessageForNewSession(PushServiceSocket socket,
Recipient recipient, Recipient recipient,
String canonicalRecipientNumber, String canonicalRecipientNumber,
String plaintext) String plaintext)
@ -151,14 +159,14 @@ public class PushTransport extends BaseTransport {
return preKeyBundleMessage.serialize(); return preKeyBundleMessage.serialize();
} }
private String getEncryptedMessageForExistingSession(Recipient recipient, String plaintext) private byte[] getEncryptedMessageForExistingSession(Recipient recipient, String plaintext)
throws IOException throws IOException
{ {
IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKeyPair, MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKeyPair,
new PushTransportDetails()); new PushTransportDetails());
return new String(messageCipher.encrypt(recipient, plaintext.getBytes())); return messageCipher.encrypt(recipient, plaintext.getBytes());
} }
} }

View file

@ -21,6 +21,7 @@ import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.sms.SmsTransportDetails; import org.thoughtcrime.securesms.sms.SmsTransportDetails;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage; import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage;
import org.whispersystems.textsecure.util.Base64;
import java.util.ArrayList; import java.util.ArrayList;
@ -160,7 +161,7 @@ public class SmsTransport extends BaseTransport {
byte[] bundledMessage = messageCipher.encrypt(recipient, body.getBytes()); byte[] bundledMessage = messageCipher.encrypt(recipient, body.getBytes());
PreKeyBundleMessage preKeyBundleMessage = new PreKeyBundleMessage(identityKey.getPublicKey(), bundledMessage); PreKeyBundleMessage preKeyBundleMessage = new PreKeyBundleMessage(identityKey.getPublicKey(), bundledMessage);
return new OutgoingPrekeyBundleMessage(message, preKeyBundleMessage.serialize()); return new OutgoingPrekeyBundleMessage(message, Base64.encodeBytesWithoutPadding(preKeyBundleMessage.serialize()));
} }
} }
} }

View file

@ -59,6 +59,10 @@ public class TextSecurePreferences {
setStringPreference(context, SIGNALING_KEY_PREF, signalingKey); setStringPreference(context, SIGNALING_KEY_PREF, signalingKey);
} }
public static String getSignalingKey(Context context) {
return getStringPreference(context, SIGNALING_KEY_PREF, null);
}
public static boolean isEnterImeKeyEnabled(Context context) { public static boolean isEnterImeKeyEnabled(Context context) {
return getBooleanPreference(context, ENTER_PRESENT_PREF, false); return getBooleanPreference(context, ENTER_PRESENT_PREF, false);
} }