Basic support for encrypted push-based attachments.

1) Move the attachment structures into the encrypted message body.

2) Encrypt attachments with symmetric keys transmitted in the
   encryptd attachment pointer structure.

3) Correctly handle asynchronous decryption and categorization of
   encrypted push messages.

TODO: Correct notification process and network/interruption
      retries.
This commit is contained in:
Moxie Marlinspike
2013-09-08 18:19:05 -07:00
parent cddba2738f
commit 0dd36c64a4
47 changed files with 2381 additions and 1003 deletions

View File

@@ -1,12 +1,26 @@
/**
* Copyright (C) 2013 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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.Parcelable;
import org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal;
import java.util.LinkedList;
import java.util.List;
@@ -24,12 +38,20 @@ public class IncomingPushMessage implements PushMessage, Parcelable {
}
};
private int type;
private String source;
private List<String> destinations;
private byte[] message;
private List<PushAttachmentPointer> attachments;
private long timestamp;
private int type;
private String source;
private List<String> destinations;
private byte[] message;
private long timestamp;
private IncomingPushMessage(IncomingPushMessage message, byte[] body) {
this.type = message.type;
this.source = message.source;
this.destinations = new LinkedList<String>();
this.destinations.addAll(message.destinations);
this.message = body;
this.timestamp = message.timestamp;
}
public IncomingPushMessage(IncomingPushMessageSignal signal) {
this.type = signal.getType();
@@ -37,25 +59,15 @@ public class IncomingPushMessage implements PushMessage, Parcelable {
this.destinations = signal.getDestinationsList();
this.message = signal.getMessage().toByteArray();
this.timestamp = signal.getTimestamp();
this.attachments = new LinkedList<PushAttachmentPointer>();
List<AttachmentPointer> attachmentPointers = signal.getAttachmentsList();
for (AttachmentPointer pointer : attachmentPointers) {
this.attachments.add(new PushAttachmentPointer(pointer.getContentType(), pointer.getKey()));
}
}
public IncomingPushMessage(Parcel in) {
this.destinations = new LinkedList<String>();
this.attachments = new LinkedList<PushAttachmentPointer>();
this.type = in.readInt();
this.source = in.readString();
in.readStringList(destinations);
this.message = new byte[in.readInt()];
in.readByteArray(this.message);
in.readList(attachments, PushAttachmentPointer.class.getClassLoader());
this.timestamp = in.readLong();
}
@@ -67,10 +79,6 @@ public class IncomingPushMessage implements PushMessage, Parcelable {
return source;
}
public List<PushAttachmentPointer> getAttachments() {
return attachments;
}
public byte[] getBody() {
return message;
}
@@ -79,10 +87,6 @@ public class IncomingPushMessage implements PushMessage, Parcelable {
return destinations;
}
public boolean hasAttachments() {
return getAttachments() != null && !getAttachments().isEmpty();
}
@Override
public int describeContents() {
return 0;
@@ -95,11 +99,22 @@ public class IncomingPushMessage implements PushMessage, Parcelable {
dest.writeStringList(destinations);
dest.writeInt(message.length);
dest.writeByteArray(message);
dest.writeList(attachments);
dest.writeLong(timestamp);
}
public IncomingPushMessage withBody(byte[] body) {
return new IncomingPushMessage(this, body);
}
public int getType() {
return type;
}
public boolean isSecureMessage() {
return getType() == PushMessage.TYPE_MESSAGE_CIPHERTEXT;
}
public boolean isPreKeyBundle() {
return getType() == PushMessage.TYPE_MESSAGE_PREKEY_BUNDLE;
}
}

View File

@@ -1,34 +1,35 @@
/**
* Copyright (C) 2013 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.push;
import org.whispersystems.textsecure.util.Base64;
import java.util.LinkedList;
import java.util.List;
public class OutgoingPushMessage implements PushMessage {
private int type;
private String destination;
private String body;
private List<PushAttachmentPointer> attachments;
private int type;
private String destination;
private String body;
public OutgoingPushMessage(String destination, byte[] body, int type) {
this.attachments = new LinkedList<PushAttachmentPointer>();
this.destination = destination;
this.body = Base64.encodeBytes(body);
this.type = type;
}
public OutgoingPushMessage(String destination, byte[] body,
List<PushAttachmentPointer> attachments,
int type)
{
this.destination = destination;
this.body = Base64.encodeBytes(body);
this.attachments = attachments;
this.type = type;
}
public String getDestination() {
return destination;
}
@@ -37,10 +38,6 @@ public class OutgoingPushMessage implements PushMessage {
return body;
}
public List<PushAttachmentPointer> getAttachments() {
return attachments;
}
public int getType() {
return type;
}

View File

@@ -18,23 +18,33 @@ public class PushAttachmentPointer implements Parcelable {
};
private final String contentType;
private final String key;
private final long id;
private final byte[] key;
public PushAttachmentPointer(String contentType, String key) {
public PushAttachmentPointer(String contentType, long id, byte[] key) {
this.contentType = contentType;
this.id = id;
this.key = key;
}
public PushAttachmentPointer(Parcel in) {
this.contentType = in.readString();
this.key = in.readString();
this.id = in.readLong();
int keyLength = in.readInt();
this.key = new byte[keyLength];
in.readByteArray(this.key);
}
public String getContentType() {
return contentType;
}
public String getKey() {
public long getId() {
return id;
}
public byte[] getKey() {
return key;
}
@@ -46,6 +56,8 @@ public class PushAttachmentPointer implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(contentType);
dest.writeString(key);
dest.writeLong(id);
dest.writeInt(this.key.length);
dest.writeByteArray(this.key);
}
}

View File

@@ -84,31 +84,21 @@ public class PushServiceSocket {
sendMessage(new OutgoingPushMessageList(message));
}
public void sendMessage(List<String> recipients, List<byte[]> bodies,
List<List<PushAttachmentData>> attachmentsList, int type)
public void sendMessage(List<String> recipients, List<byte[]> bodies, List<Integer> types)
throws IOException
{
List<OutgoingPushMessage> messages = new LinkedList<OutgoingPushMessage>();
Iterator<String> recipientsIterator = recipients.iterator();
Iterator<byte[]> bodiesIterator = bodies.iterator();
Iterator<List<PushAttachmentData>> attachmentsIterator = attachmentsList.iterator();
Iterator<String> recipientsIterator = recipients.iterator();
Iterator<byte[]> bodiesIterator = bodies.iterator();
Iterator<Integer> typesIterator = types.iterator();
while (recipientsIterator.hasNext()) {
String recipient = recipientsIterator.next();
byte[] body = bodiesIterator.next();
List<PushAttachmentData> attachments = attachmentsIterator.next();
String recipient = recipientsIterator.next();
byte[] body = bodiesIterator.next();
int type = typesIterator.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);
messages.add(new OutgoingPushMessage(recipient, body, type));
}
sendMessage(new OutgoingPushMessageList(messages));
@@ -149,20 +139,7 @@ public class PushServiceSocket {
return PreKeyEntity.fromJson(responseText);
}
private List<PushAttachmentPointer> sendAttachments(List<PushAttachmentData> attachments)
throws IOException
{
List<PushAttachmentPointer> attachmentIds = new LinkedList<PushAttachmentPointer>();
for (PushAttachmentData attachment : attachments) {
attachmentIds.add(new PushAttachmentPointer(attachment.getContentType(),
sendAttachment(attachment)));
}
return attachmentIds;
}
private String sendAttachment(PushAttachmentData attachment) throws IOException {
public long sendAttachment(PushAttachmentData attachment) throws IOException {
Pair<String, String> response = makeRequestForResponseHeader(String.format(ATTACHMENT_PATH, ""),
"GET", null, "Content-Location");
@@ -178,25 +155,18 @@ public class PushServiceSocket {
return new Gson().fromJson(response.second, AttachmentKey.class).getId();
}
public List<Pair<File,String>> retrieveAttachments(List<PushAttachmentPointer> attachmentIds)
throws IOException
{
List<Pair<File,String>> attachments = new LinkedList<Pair<File,String>>();
public File retrieveAttachment(long attachmentId) throws IOException {
Pair<String, String> response = makeRequestForResponseHeader(String.format(ATTACHMENT_PATH, String.valueOf(attachmentId)),
"GET", null, "Content-Location");
for (PushAttachmentPointer attachmentId : attachmentIds) {
Pair<String, String> response = makeRequestForResponseHeader(String.format(ATTACHMENT_PATH, attachmentId.getKey()),
"GET", null, "Content-Location");
Log.w("PushServiceSocket", "Attachment: " + attachmentId + " is at: " + response.first);
Log.w("PushServiceSocket", "Attachment: " + attachmentId.getKey() + " is at: " + response.first);
File attachment = File.createTempFile("attachment", ".tmp", context.getFilesDir());
attachment.deleteOnExit();
File attachment = File.createTempFile("attachment", ".tmp", context.getFilesDir());
attachment.deleteOnExit();
downloadExternalFile(response.first, attachment);
downloadExternalFile(response.first, attachment);
attachments.add(new Pair<File, String>(attachment, attachmentId.getContentType()));
}
return attachments;
return attachment;
}
public Pair<DirectoryDescriptor, File> retrieveDirectory() {
@@ -394,13 +364,13 @@ public class PushServiceSocket {
}
private static class AttachmentKey {
private String id;
private long id;
public AttachmentKey(String id) {
public AttachmentKey(long id) {
this.id = id;
}
public String getId() {
public long getId() {
return id;
}
}