Simplify API, add builders.

This commit is contained in:
Moxie Marlinspike 2015-03-04 18:53:57 -08:00
parent 3399d0feb3
commit 64b1dbfdae
8 changed files with 199 additions and 79 deletions

View File

@ -23,7 +23,6 @@ import org.whispersystems.libaxolotl.AxolotlAddress;
import org.whispersystems.libaxolotl.InvalidKeyException; import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.SessionBuilder; import org.whispersystems.libaxolotl.SessionBuilder;
import org.whispersystems.libaxolotl.logging.Log; import org.whispersystems.libaxolotl.logging.Log;
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
import org.whispersystems.libaxolotl.state.AxolotlStore; import org.whispersystems.libaxolotl.state.AxolotlStore;
import org.whispersystems.libaxolotl.state.PreKeyBundle; import org.whispersystems.libaxolotl.state.PreKeyBundle;
import org.whispersystems.libaxolotl.util.guava.Optional; import org.whispersystems.libaxolotl.util.guava.Optional;
@ -35,18 +34,17 @@ import org.whispersystems.textsecure.api.messages.TextSecureGroup;
import org.whispersystems.textsecure.api.messages.TextSecureMessage; import org.whispersystems.textsecure.api.messages.TextSecureMessage;
import org.whispersystems.textsecure.api.push.TextSecureAddress; import org.whispersystems.textsecure.api.push.TextSecureAddress;
import org.whispersystems.textsecure.api.push.TrustStore; import org.whispersystems.textsecure.api.push.TrustStore;
import org.whispersystems.textsecure.api.push.exceptions.EncapsulatedExceptions;
import org.whispersystems.textsecure.api.push.exceptions.NetworkFailureException; import org.whispersystems.textsecure.api.push.exceptions.NetworkFailureException;
import org.whispersystems.textsecure.api.push.exceptions.PushNetworkException; import org.whispersystems.textsecure.api.push.exceptions.PushNetworkException;
import org.whispersystems.textsecure.api.push.exceptions.UnregisteredUserException;
import org.whispersystems.textsecure.internal.push.MismatchedDevices; import org.whispersystems.textsecure.internal.push.MismatchedDevices;
import org.whispersystems.textsecure.internal.push.OutgoingPushMessage; import org.whispersystems.textsecure.internal.push.OutgoingPushMessage;
import org.whispersystems.textsecure.internal.push.OutgoingPushMessageList; import org.whispersystems.textsecure.internal.push.OutgoingPushMessageList;
import org.whispersystems.textsecure.internal.push.PushAttachmentData; import org.whispersystems.textsecure.internal.push.PushAttachmentData;
import org.whispersystems.textsecure.internal.push.PushBody;
import org.whispersystems.textsecure.internal.push.PushServiceSocket; import org.whispersystems.textsecure.internal.push.PushServiceSocket;
import org.whispersystems.textsecure.internal.push.SendMessageResponse; import org.whispersystems.textsecure.internal.push.SendMessageResponse;
import org.whispersystems.textsecure.internal.push.StaleDevices; import org.whispersystems.textsecure.internal.push.StaleDevices;
import org.whispersystems.textsecure.api.push.exceptions.UnregisteredUserException;
import org.whispersystems.textsecure.api.push.exceptions.EncapsulatedExceptions;
import org.whispersystems.textsecure.internal.push.exceptions.MismatchedDevicesException; import org.whispersystems.textsecure.internal.push.exceptions.MismatchedDevicesException;
import org.whispersystems.textsecure.internal.push.exceptions.StaleDevicesException; import org.whispersystems.textsecure.internal.push.exceptions.StaleDevicesException;
import org.whispersystems.textsecure.internal.util.StaticCredentialsProvider; import org.whispersystems.textsecure.internal.util.StaticCredentialsProvider;
@ -56,7 +54,6 @@ import java.io.IOException;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import static org.whispersystems.textsecure.internal.push.PushMessageProtos.IncomingPushMessageSignal.Type;
import static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent; import static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent;
import static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.AttachmentPointer; import static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.AttachmentPointer;
import static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.GroupContext; import static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.GroupContext;
@ -304,22 +301,21 @@ public class TextSecureMessageSender {
List<OutgoingPushMessage> messages = new LinkedList<>(); List<OutgoingPushMessage> messages = new LinkedList<>();
if (!recipient.equals(syncAddress)) { if (!recipient.equals(syncAddress)) {
PushBody masterBody = getEncryptedMessage(socket, recipient, TextSecureAddress.DEFAULT_DEVICE_ID, plaintext); messages.add(getEncryptedMessage(socket, recipient, TextSecureAddress.DEFAULT_DEVICE_ID, plaintext));
messages.add(new OutgoingPushMessage(recipient, TextSecureAddress.DEFAULT_DEVICE_ID, masterBody));
} }
for (int deviceId : store.getSubDeviceSessions(recipient.getNumber())) { for (int deviceId : store.getSubDeviceSessions(recipient.getNumber())) {
PushBody body = getEncryptedMessage(socket, recipient, deviceId, plaintext); messages.add(getEncryptedMessage(socket, recipient, deviceId, plaintext));
messages.add(new OutgoingPushMessage(recipient, deviceId, body));
} }
return new OutgoingPushMessageList(recipient.getNumber(), timestamp, recipient.getRelay().orNull(), messages); return new OutgoingPushMessageList(recipient.getNumber(), timestamp, recipient.getRelay().orNull(), messages);
} }
private PushBody getEncryptedMessage(PushServiceSocket socket, TextSecureAddress recipient, int deviceId, byte[] plaintext) private OutgoingPushMessage getEncryptedMessage(PushServiceSocket socket, TextSecureAddress recipient, int deviceId, byte[] plaintext)
throws IOException, UntrustedIdentityException throws IOException, UntrustedIdentityException
{ {
AxolotlAddress axolotlAddress = new AxolotlAddress(recipient.getNumber(), deviceId); AxolotlAddress axolotlAddress = new AxolotlAddress(recipient.getNumber(), deviceId);
TextSecureCipher cipher = new TextSecureCipher(store);
if (!store.containsSession(axolotlAddress)) { if (!store.containsSession(axolotlAddress)) {
try { try {
@ -343,17 +339,7 @@ public class TextSecureMessageSender {
} }
} }
TextSecureCipher cipher = new TextSecureCipher(store, axolotlAddress); return cipher.encrypt(axolotlAddress, plaintext);
CiphertextMessage message = cipher.encrypt(plaintext);
int remoteRegistrationId = cipher.getRemoteRegistrationId();
if (message.getType() == CiphertextMessage.PREKEY_TYPE) {
return new PushBody(Type.PREKEY_BUNDLE_VALUE, remoteRegistrationId, message.serialize());
} else if (message.getType() == CiphertextMessage.WHISPER_TYPE) {
return new PushBody(Type.CIPHERTEXT_VALUE, remoteRegistrationId, message.serialize());
} else {
throw new AssertionError("Unknown ciphertext type: " + message.getType());
}
} }
private void handleMismatchedDevices(PushServiceSocket socket, TextSecureAddress recipient, private void handleMismatchedDevices(PushServiceSocket socket, TextSecureAddress recipient,

View File

@ -37,11 +37,15 @@ import org.whispersystems.textsecure.api.messages.TextSecureAttachmentPointer;
import org.whispersystems.textsecure.api.messages.TextSecureEnvelope; import org.whispersystems.textsecure.api.messages.TextSecureEnvelope;
import org.whispersystems.textsecure.api.messages.TextSecureGroup; import org.whispersystems.textsecure.api.messages.TextSecureGroup;
import org.whispersystems.textsecure.api.messages.TextSecureMessage; import org.whispersystems.textsecure.api.messages.TextSecureMessage;
import org.whispersystems.textsecure.internal.push.OutgoingPushMessage;
import org.whispersystems.textsecure.internal.push.PushMessageProtos;
import org.whispersystems.textsecure.internal.push.PushTransportDetails; import org.whispersystems.textsecure.internal.push.PushTransportDetails;
import org.whispersystems.textsecure.internal.util.Base64;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import static org.whispersystems.textsecure.internal.push.PushMessageProtos.IncomingPushMessageSignal.Type;
import static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent; import static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent;
import static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.GroupContext.Type.DELIVER; import static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.GroupContext.Type.DELIVER;
@ -52,15 +56,28 @@ import static org.whispersystems.textsecure.internal.push.PushMessageProtos.Push
*/ */
public class TextSecureCipher { public class TextSecureCipher {
private final SessionCipher sessionCipher; private final AxolotlStore axolotlStore;
public TextSecureCipher(AxolotlStore axolotlStore, AxolotlAddress destination) { public TextSecureCipher(AxolotlStore axolotlStore) {
this.sessionCipher = new SessionCipher(axolotlStore, destination); this.axolotlStore = axolotlStore;
} }
public CiphertextMessage encrypt(byte[] unpaddedMessage) { public OutgoingPushMessage encrypt(AxolotlAddress destination, byte[] unpaddedMessage) {
SessionCipher sessionCipher = new SessionCipher(axolotlStore, destination);
PushTransportDetails transportDetails = new PushTransportDetails(sessionCipher.getSessionVersion()); PushTransportDetails transportDetails = new PushTransportDetails(sessionCipher.getSessionVersion());
return sessionCipher.encrypt(transportDetails.getPaddedMessageBody(unpaddedMessage)); CiphertextMessage message = sessionCipher.encrypt(transportDetails.getPaddedMessageBody(unpaddedMessage));
int remoteRegistrationId = sessionCipher.getRemoteRegistrationId();
String body = Base64.encodeBytes(message.serialize());
int type;
switch (message.getType()) {
case CiphertextMessage.PREKEY_TYPE: type = Type.PREKEY_BUNDLE_VALUE; break;
case CiphertextMessage.WHISPER_TYPE: type = Type.CIPHERTEXT_VALUE; break;
default: throw new AssertionError("Bad type: " + message.getType());
}
return new OutgoingPushMessage(type, destination.getDeviceId(), remoteRegistrationId, body);
} }
/** /**
@ -83,6 +100,9 @@ public class TextSecureCipher {
LegacyMessageException, NoSessionException LegacyMessageException, NoSessionException
{ {
try { try {
AxolotlAddress sourceAddress = new AxolotlAddress(envelope.getSource(), envelope.getSourceDevice());
SessionCipher sessionCipher = new SessionCipher(axolotlStore, sourceAddress);
byte[] paddedMessage; byte[] paddedMessage;
if (envelope.isPreKeyWhisperMessage()) { if (envelope.isPreKeyWhisperMessage()) {
@ -104,10 +124,6 @@ public class TextSecureCipher {
} }
} }
public int getRemoteRegistrationId() {
return sessionCipher.getRemoteRegistrationId();
}
private TextSecureMessage createTextSecureMessage(TextSecureEnvelope envelope, PushMessageContent content) { private TextSecureMessage createTextSecureMessage(TextSecureEnvelope envelope, PushMessageContent content) {
TextSecureGroup groupInfo = createGroupInfo(envelope, content); TextSecureGroup groupInfo = createGroupInfo(envelope, content);
List<TextSecureAttachment> attachments = new LinkedList<>(); List<TextSecureAttachment> attachments = new LinkedList<>();

View File

@ -16,6 +16,8 @@
*/ */
package org.whispersystems.textsecure.api.messages; package org.whispersystems.textsecure.api.messages;
import java.io.InputStream;
public abstract class TextSecureAttachment { public abstract class TextSecureAttachment {
private final String contentType; private final String contentType;
@ -38,4 +40,40 @@ public abstract class TextSecureAttachment {
public TextSecureAttachmentPointer asPointer() { public TextSecureAttachmentPointer asPointer() {
return (TextSecureAttachmentPointer)this; return (TextSecureAttachmentPointer)this;
} }
public static Builder newStreamBuilder() {
return new Builder();
}
public static class Builder {
private InputStream inputStream;
private String contentType;
private long length;
private Builder() {}
public Builder withStream(InputStream inputStream) {
this.inputStream = inputStream;
return this;
}
public Builder withContentType(String contentType) {
this.contentType = contentType;
return this;
}
public Builder withLength(long length) {
this.length = length;
return this;
}
public TextSecureAttachmentStream build() {
if (inputStream == null) throw new IllegalArgumentException("Must specify stream!");
if (contentType == null) throw new IllegalArgumentException("No content type specified!");
if (length == 0) throw new IllegalArgumentException("No length specified!");
return new TextSecureAttachmentStream(inputStream, contentType, length);
}
}
} }

View File

@ -20,6 +20,8 @@ import com.google.protobuf.ByteString;
import org.whispersystems.libaxolotl.InvalidVersionException; import org.whispersystems.libaxolotl.InvalidVersionException;
import org.whispersystems.libaxolotl.logging.Log; import org.whispersystems.libaxolotl.logging.Log;
import org.whispersystems.libaxolotl.util.guava.Optional;
import org.whispersystems.textsecure.api.push.TextSecureAddress;
import org.whispersystems.textsecure.internal.push.PushMessageProtos.IncomingPushMessageSignal; import org.whispersystems.textsecure.internal.push.PushMessageProtos.IncomingPushMessageSignal;
import org.whispersystems.textsecure.internal.util.Base64; import org.whispersystems.textsecure.internal.util.Base64;
import org.whispersystems.textsecure.internal.util.Hex; import org.whispersystems.textsecure.internal.util.Hex;
@ -127,6 +129,15 @@ public class TextSecureEnvelope {
return signal.getSourceDevice(); return signal.getSourceDevice();
} }
/**
* @return The envelope's sender as a TextSecureAddress.
*/
public TextSecureAddress getSourceAddress() {
return new TextSecureAddress(signal.getSource(),
signal.hasRelay() ? Optional.fromNullable(signal.getRelay()) :
Optional.<String>absent());
}
/** /**
* @return The envelope content type. * @return The envelope content type.
*/ */

View File

@ -96,4 +96,56 @@ public class TextSecureGroup {
return avatar; return avatar;
} }
public static Builder newUpdateBuilder() {
return new Builder(Type.UPDATE);
}
public static Builder newBuilder(Type type) {
return new Builder(type);
}
public static class Builder {
private Type type;
private byte[] id;
private String name;
private List<String> members;
private TextSecureAttachment avatar;
private Builder(Type type) {
this.type = type;
}
public Builder withId(byte[] id) {
this.id = id;
return this;
}
public Builder withName(String name) {
this.name = name;
return this;
}
public Builder withMembers(List<String> members) {
this.members = members;
return this;
}
public Builder withAvatar(TextSecureAttachment avatar) {
this.avatar = avatar;
return this;
}
public TextSecureGroup build() {
if (id == null) throw new IllegalArgumentException("No group ID specified!");
if (type == Type.UPDATE && name == null && members == null && avatar == null) {
throw new IllegalArgumentException("Group update with no updates!");
}
return new TextSecureGroup(type, id, name, members, avatar);
}
}
} }

View File

@ -94,6 +94,10 @@ public class TextSecureMessage {
} }
} }
public static Builder newBuilder() {
return new Builder();
}
/** /**
* @return The message timestamp. * @return The message timestamp.
*/ */
@ -133,4 +137,55 @@ public class TextSecureMessage {
public boolean isGroupUpdate() { public boolean isGroupUpdate() {
return group.isPresent() && group.get().getType() != TextSecureGroup.Type.DELIVER; return group.isPresent() && group.get().getType() != TextSecureGroup.Type.DELIVER;
} }
public static class Builder {
private List<TextSecureAttachment> attachments = new LinkedList<>();
private long timestamp;
private TextSecureGroup group;
private String body;
private boolean endSession;
private Builder() {}
public Builder withTimestamp(long timestamp) {
this.timestamp = timestamp;
return this;
}
public Builder asGroupMessage(TextSecureGroup group) {
this.group = group;
return this;
}
public Builder withAttachment(TextSecureAttachment attachment) {
this.attachments.add(attachment);
return this;
}
public Builder withAttachments(List<TextSecureAttachment> attachments) {
this.attachments.addAll(attachments);
return this;
}
public Builder withBody(String body) {
this.body = body;
return this;
}
public Builder asEndSessionMessage() {
this.endSession = true;
return this;
}
public Builder asEndSessionMessage(boolean endSession) {
this.endSession = endSession;
return this;
}
public TextSecureMessage build() {
if (timestamp == 0) timestamp = System.currentTimeMillis();
return new TextSecureMessage(timestamp, group, attachments, body, true, endSession);
}
}
} }

View File

@ -33,11 +33,15 @@ public class OutgoingPushMessage {
@JsonProperty @JsonProperty
private String body; private String body;
public OutgoingPushMessage(TextSecureAddress address, int deviceId, PushBody body) { public OutgoingPushMessage(int type,
this.type = body.getType(); int destinationDeviceId,
this.destinationDeviceId = deviceId; int destinationRegistrationId,
this.destinationRegistrationId = body.getRemoteRegistrationId(); String body)
this.body = Base64.encodeBytes(body.getBody()); {
this.type = type;
this.destinationDeviceId = destinationDeviceId;
this.destinationRegistrationId = destinationRegistrationId;
this.body = body;
} }
public int getDestinationDeviceId() { public int getDestinationDeviceId() {

View File

@ -1,42 +0,0 @@
/**
* Copyright (C) 2014 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.internal.push;
public class PushBody {
private final int type;
private final int remoteRegistrationId;
private final byte[] body;
public PushBody(int type, int remoteRegistrationId, byte[] body) {
this.type = type;
this.remoteRegistrationId = remoteRegistrationId;
this.body = body;
}
public int getType() {
return type;
}
public byte[] getBody() {
return body;
}
public int getRemoteRegistrationId() {
return remoteRegistrationId;
}
}