From e7f1c52eb24c87e315f7bbcfc91f4b794d8ab8fb Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Thu, 25 Jun 2015 16:04:16 -0700 Subject: [PATCH 01/19] Add progress listener for attachments. // FREEBIE --- .../api/TextSecureMessageReceiver.java | 6 ++- .../api/TextSecureMessageSender.java | 1 + .../api/messages/TextSecureAttachment.java | 18 ++++++-- .../messages/TextSecureAttachmentStream.java | 12 ++++-- .../DeviceContactsInputStream.java | 2 +- .../multidevice/DeviceGroupsInputStream.java | 2 +- .../internal/push/PushAttachmentData.java | 20 ++++++--- .../internal/push/PushServiceSocket.java | 42 ++++++++++++++----- .../textsecure/internal/util/Util.java | 16 ------- 9 files changed, 76 insertions(+), 43 deletions(-) diff --git a/java/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageReceiver.java b/java/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageReceiver.java index c1b239abfb..6684eb2991 100644 --- a/java/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageReceiver.java +++ b/java/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageReceiver.java @@ -18,6 +18,8 @@ import org.whispersystems.libaxolotl.InvalidMessageException; import org.whispersystems.textsecure.api.crypto.AttachmentCipherInputStream; +import org.whispersystems.textsecure.api.messages.TextSecureAttachment; +import org.whispersystems.textsecure.api.messages.TextSecureAttachment.ProgressListener; import org.whispersystems.textsecure.api.messages.TextSecureAttachmentPointer; import org.whispersystems.textsecure.api.messages.TextSecureDataMessage; import org.whispersystems.textsecure.api.messages.TextSecureEnvelope; @@ -88,10 +90,10 @@ public TextSecureMessageReceiver(String url, TrustStore trustStore, CredentialsP * @throws IOException * @throws InvalidMessageException */ - public InputStream retrieveAttachment(TextSecureAttachmentPointer pointer, File destination) + public InputStream retrieveAttachment(TextSecureAttachmentPointer pointer, File destination, ProgressListener listener) throws IOException, InvalidMessageException { - socket.retrieveAttachment(pointer.getRelay().orNull(), pointer.getId(), destination); + socket.retrieveAttachment(pointer.getRelay().orNull(), pointer.getId(), destination, listener); return new AttachmentCipherInputStream(destination, pointer.getKey()); } diff --git a/java/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageSender.java b/java/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageSender.java index 00f0cd9353..959d7570fc 100644 --- a/java/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageSender.java +++ b/java/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageSender.java @@ -335,6 +335,7 @@ private AttachmentPointer createAttachmentPointer(TextSecureAttachmentStream att PushAttachmentData attachmentData = new PushAttachmentData(attachment.getContentType(), attachment.getInputStream(), attachment.getLength(), + attachment.getListener(), attachmentKey); long attachmentId = socket.sendAttachment(attachmentData); diff --git a/java/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureAttachment.java b/java/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureAttachment.java index 03b6437a6e..cb1cab82aa 100644 --- a/java/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureAttachment.java +++ b/java/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureAttachment.java @@ -47,9 +47,10 @@ public static Builder newStreamBuilder() { public static class Builder { - private InputStream inputStream; - private String contentType; - private long length; + private InputStream inputStream; + private String contentType; + private long length; + private ProgressListener listener; private Builder() {} @@ -68,12 +69,21 @@ public Builder withLength(long length) { return this; } + public Builder withListener(ProgressListener listener) { + this.listener = listener; + 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); + return new TextSecureAttachmentStream(inputStream, contentType, length, listener); } } + + public interface ProgressListener { + public void onAttachmentProgress(long total, long progress); + } } diff --git a/java/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureAttachmentStream.java b/java/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureAttachmentStream.java index 2f57a1bcc8..81fae9ed54 100644 --- a/java/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureAttachmentStream.java +++ b/java/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureAttachmentStream.java @@ -23,13 +23,15 @@ */ public class TextSecureAttachmentStream extends TextSecureAttachment { - private final InputStream inputStream; - private final long length; + private final InputStream inputStream; + private final long length; + private final ProgressListener listener; - public TextSecureAttachmentStream(InputStream inputStream, String contentType, long length) { + public TextSecureAttachmentStream(InputStream inputStream, String contentType, long length, ProgressListener listener) { super(contentType); this.inputStream = inputStream; this.length = length; + this.listener = listener; } @Override @@ -49,4 +51,8 @@ public InputStream getInputStream() { public long getLength() { return length; } + + public ProgressListener getListener() { + return listener; + } } diff --git a/java/src/main/java/org/whispersystems/textsecure/api/messages/multidevice/DeviceContactsInputStream.java b/java/src/main/java/org/whispersystems/textsecure/api/messages/multidevice/DeviceContactsInputStream.java index c1ef46868c..8cf8dca07d 100644 --- a/java/src/main/java/org/whispersystems/textsecure/api/messages/multidevice/DeviceContactsInputStream.java +++ b/java/src/main/java/org/whispersystems/textsecure/api/messages/multidevice/DeviceContactsInputStream.java @@ -29,7 +29,7 @@ public DeviceContact read() throws IOException { InputStream avatarStream = new LimitedInputStream(in, avatarLength); String avatarContentType = details.getAvatar().getContentType(); - avatar = Optional.of(new TextSecureAttachmentStream(avatarStream, avatarContentType, avatarLength)); + avatar = Optional.of(new TextSecureAttachmentStream(avatarStream, avatarContentType, avatarLength, null)); } return new DeviceContact(number, name, avatar); diff --git a/java/src/main/java/org/whispersystems/textsecure/api/messages/multidevice/DeviceGroupsInputStream.java b/java/src/main/java/org/whispersystems/textsecure/api/messages/multidevice/DeviceGroupsInputStream.java index 1f82df2bf2..f15fa82b8e 100644 --- a/java/src/main/java/org/whispersystems/textsecure/api/messages/multidevice/DeviceGroupsInputStream.java +++ b/java/src/main/java/org/whispersystems/textsecure/api/messages/multidevice/DeviceGroupsInputStream.java @@ -36,7 +36,7 @@ public DeviceGroup read() throws IOException { InputStream avatarStream = new ChunkedInputStream.LimitedInputStream(in, avatarLength); String avatarContentType = details.getAvatar().getContentType(); - avatar = Optional.of(new TextSecureAttachmentStream(avatarStream, avatarContentType, avatarLength)); + avatar = Optional.of(new TextSecureAttachmentStream(avatarStream, avatarContentType, avatarLength, null)); } return new DeviceGroup(id, name, members, avatar); diff --git a/java/src/main/java/org/whispersystems/textsecure/internal/push/PushAttachmentData.java b/java/src/main/java/org/whispersystems/textsecure/internal/push/PushAttachmentData.java index 11b0bcf15c..59c5c172f5 100644 --- a/java/src/main/java/org/whispersystems/textsecure/internal/push/PushAttachmentData.java +++ b/java/src/main/java/org/whispersystems/textsecure/internal/push/PushAttachmentData.java @@ -16,20 +16,26 @@ */ package org.whispersystems.textsecure.internal.push; +import org.whispersystems.textsecure.api.messages.TextSecureAttachment.ProgressListener; + import java.io.InputStream; public class PushAttachmentData { - private final String contentType; - private final InputStream data; - private final long dataSize; - private final byte[] key; + private final String contentType; + private final InputStream data; + private final long dataSize; + private final byte[] key; + private final ProgressListener listener; - public PushAttachmentData(String contentType, InputStream data, long dataSize, byte[] key) { + public PushAttachmentData(String contentType, InputStream data, long dataSize, + ProgressListener listener, byte[] key) + { this.contentType = contentType; this.data = data; this.dataSize = dataSize; this.key = key; + this.listener = listener; } public String getContentType() { @@ -47,4 +53,8 @@ public long getDataSize() { public byte[] getKey() { return key; } + + public ProgressListener getListener() { + return listener; + } } diff --git a/java/src/main/java/org/whispersystems/textsecure/internal/push/PushServiceSocket.java b/java/src/main/java/org/whispersystems/textsecure/internal/push/PushServiceSocket.java index d62c9b7b72..ebe7f35de0 100644 --- a/java/src/main/java/org/whispersystems/textsecure/internal/push/PushServiceSocket.java +++ b/java/src/main/java/org/whispersystems/textsecure/internal/push/PushServiceSocket.java @@ -27,10 +27,11 @@ import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; import org.whispersystems.libaxolotl.util.guava.Optional; import org.whispersystems.textsecure.api.crypto.AttachmentCipherOutputStream; +import org.whispersystems.textsecure.api.messages.TextSecureAttachment.ProgressListener; import org.whispersystems.textsecure.api.messages.multidevice.DeviceInfo; import org.whispersystems.textsecure.api.push.ContactTokenDetails; -import org.whispersystems.textsecure.api.push.TextSecureAddress; import org.whispersystems.textsecure.api.push.SignedPreKeyEntity; +import org.whispersystems.textsecure.api.push.TextSecureAddress; import org.whispersystems.textsecure.api.push.TrustStore; import org.whispersystems.textsecure.api.push.exceptions.AuthorizationFailedException; import org.whispersystems.textsecure.api.push.exceptions.ExpectationFailedException; @@ -335,12 +336,12 @@ public long sendAttachment(PushAttachmentData attachment) throws IOException { Log.w(TAG, "Got attachment content location: " + attachmentKey.getLocation()); uploadAttachment("PUT", attachmentKey.getLocation(), attachment.getData(), - attachment.getDataSize(), attachment.getKey()); + attachment.getDataSize(), attachment.getKey(), attachment.getListener()); return attachmentKey.getId(); } - public void retrieveAttachment(String relay, long attachmentId, File destination) throws IOException { + public void retrieveAttachment(String relay, long attachmentId, File destination, ProgressListener listener) throws IOException { String path = String.format(ATTACHMENT_PATH, String.valueOf(attachmentId)); if (!Util.isEmpty(relay)) { @@ -352,7 +353,7 @@ public void retrieveAttachment(String relay, long attachmentId, File destination Log.w(TAG, "Attachment: " + attachmentId + " is at: " + descriptor.getLocation()); - downloadExternalFile(descriptor.getLocation(), destination); + downloadExternalFile(descriptor.getLocation(), destination, listener); } public List retrieveDirectory(Set contactTokens) @@ -374,7 +375,7 @@ public ContactTokenDetails getContactTokenDetails(String contactToken) throws IO } } - private void downloadExternalFile(String url, File localDestination) + private void downloadExternalFile(String url, File localDestination, ProgressListener listener) throws IOException { URL downloadUrl = new URL(url); @@ -388,13 +389,19 @@ private void downloadExternalFile(String url, File localDestination) throw new NonSuccessfulResponseCodeException("Bad response: " + connection.getResponseCode()); } - OutputStream output = new FileOutputStream(localDestination); - InputStream input = connection.getInputStream(); - byte[] buffer = new byte[4096]; - int read; + OutputStream output = new FileOutputStream(localDestination); + InputStream input = connection.getInputStream(); + byte[] buffer = new byte[4096]; + int contentLength = connection.getContentLength(); + int read,totalRead = 0; while ((read = input.read(buffer)) != -1) { output.write(buffer, 0, read); + totalRead += read; + + if (listener != null) { + listener.onAttachmentProgress(contentLength, totalRead); + } } output.close(); @@ -406,7 +413,8 @@ private void downloadExternalFile(String url, File localDestination) } } - private void uploadAttachment(String method, String url, InputStream data, long dataSize, byte[] key) + private void uploadAttachment(String method, String url, InputStream data, + long dataSize, byte[] key, ProgressListener listener) throws IOException { URL uploadUrl = new URL(url); @@ -427,9 +435,21 @@ private void uploadAttachment(String method, String url, InputStream data, long try { OutputStream stream = connection.getOutputStream(); AttachmentCipherOutputStream out = new AttachmentCipherOutputStream(key, stream); + byte[] buffer = new byte[4096]; + int read, written = 0; + + while ((read = data.read(buffer)) != -1) { + out.write(buffer, 0, read); + written += read; + + if (listener != null) { + listener.onAttachmentProgress(dataSize, written); + } + } - Util.copy(data, out); + data.close(); out.flush(); + out.close(); if (connection.getResponseCode() != 200) { throw new IOException("Bad response: " + connection.getResponseCode() + " " + connection.getResponseMessage()); diff --git a/java/src/main/java/org/whispersystems/textsecure/internal/util/Util.java b/java/src/main/java/org/whispersystems/textsecure/internal/util/Util.java index 15917f987a..d08d033f94 100644 --- a/java/src/main/java/org/whispersystems/textsecure/internal/util/Util.java +++ b/java/src/main/java/org/whispersystems/textsecure/internal/util/Util.java @@ -96,7 +96,6 @@ public static void readFully(InputStream in, byte[] buffer) throws IOException { } } - public static void copy(InputStream in, OutputStream out) throws IOException { byte[] buffer = new byte[4096]; int read; @@ -124,19 +123,4 @@ public static void wait(Object lock, long millis) { throw new AssertionError(e); } } - - public static byte[] toVarint64(long value) { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - - while (true) { - if ((value & ~0x7FL) == 0) { - out.write((int) value); - return out.toByteArray(); - } else { - out.write(((int) value & 0x7F) | 0x80); - value >>>= 7; - } - } - } - } From c36d13057c8ceb2c525d43f6c450caeb98b9ba18 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Fri, 26 Jun 2015 10:15:41 -0700 Subject: [PATCH 02/19] Add some javadoc for ProgressListener changes. // FREEBIE --- .../api/TextSecureMessageReceiver.java | 19 +++++++++++++++++++ .../api/messages/TextSecureAttachment.java | 10 ++++++++++ 2 files changed, 29 insertions(+) diff --git a/java/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageReceiver.java b/java/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageReceiver.java index 6684eb2991..093cb1f5e8 100644 --- a/java/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageReceiver.java +++ b/java/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageReceiver.java @@ -90,6 +90,25 @@ public TextSecureMessageReceiver(String url, TrustStore trustStore, CredentialsP * @throws IOException * @throws InvalidMessageException */ + public InputStream retrieveAttachment(TextSecureAttachmentPointer pointer, File destination) + throws IOException, InvalidMessageException + { + return retrieveAttachment(pointer, destination, null); + } + + + /** + * Retrieves a TextSecure attachment. + * + * @param pointer The {@link org.whispersystems.textsecure.api.messages.TextSecureAttachmentPointer} + * received in a {@link TextSecureDataMessage}. + * @param destination The download destination for this attachment. + * @param listener An optional listener (may be null) to receive callbacks on download progress. + * + * @return An InputStream that streams the plaintext attachment contents. + * @throws IOException + * @throws InvalidMessageException + */ public InputStream retrieveAttachment(TextSecureAttachmentPointer pointer, File destination, ProgressListener listener) throws IOException, InvalidMessageException { diff --git a/java/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureAttachment.java b/java/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureAttachment.java index cb1cab82aa..acbc09c827 100644 --- a/java/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureAttachment.java +++ b/java/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureAttachment.java @@ -83,7 +83,17 @@ public TextSecureAttachmentStream build() { } } + /** + * An interface to receive progress information on upload/download of + * an attachment. + */ public interface ProgressListener { + /** + * Called on a progress change event. + * + * @param total The total amount to transmit/receive in bytes. + * @param progress The amount that has been transmitted/received in bytes thus far + */ public void onAttachmentProgress(long total, long progress); } } From beef6937cbb26b99869f41482f3cad327736b73d Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Wed, 1 Jul 2015 15:15:15 -0700 Subject: [PATCH 03/19] Bump version to 1.6.1 // FREEBIE --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index dd90855165..28a474212c 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ subprojects { - ext.version_number = "1.6.0" + ext.version_number = "1.6.1" ext.group_info = "org.whispersystems" ext.axolotl_version = "1.3.1" From e7f05eb60829f2cb85b2308fc501d2e691944e23 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Thu, 30 Jul 2015 12:54:58 -0700 Subject: [PATCH 04/19] Be more careful with JSON processing of server responses. // FREEBIE --- .../internal/push/PushServiceSocket.java | 49 +++++++++++++------ .../textsecure/internal/util/JsonUtil.java | 20 +++----- 2 files changed, 41 insertions(+), 28 deletions(-) diff --git a/java/src/main/java/org/whispersystems/textsecure/internal/push/PushServiceSocket.java b/java/src/main/java/org/whispersystems/textsecure/internal/push/PushServiceSocket.java index ebe7f35de0..fc5ca9a92c 100644 --- a/java/src/main/java/org/whispersystems/textsecure/internal/push/PushServiceSocket.java +++ b/java/src/main/java/org/whispersystems/textsecure/internal/push/PushServiceSocket.java @@ -17,6 +17,7 @@ package org.whispersystems.textsecure.internal.push; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; import org.apache.http.conn.ssl.StrictHostnameVerifier; import org.whispersystems.libaxolotl.IdentityKey; @@ -259,8 +260,6 @@ public List getPreKeys(TextSecureAddress destination, int deviceId } return bundles; - } catch (JsonUtil.JsonParseException e) { - throw new IOException(e); } catch (NotFoundException nfe) { throw new UnregisteredUserException(destination.getNumber(), nfe); } @@ -301,8 +300,6 @@ public PreKeyBundle getPreKey(TextSecureAddress destination, int deviceId) throw return new PreKeyBundle(device.getRegistrationId(), device.getDeviceId(), preKeyId, preKey, signedPreKeyId, signedPreKey, signedPreKeySignature, response.getIdentityKey()); - } catch (JsonUtil.JsonParseException e) { - throw new IOException(e); } catch (NotFoundException nfe) { throw new UnregisteredUserException(destination.getNumber(), nfe); } @@ -359,11 +356,16 @@ public void retrieveAttachment(String relay, long attachmentId, File destination public List retrieveDirectory(Set contactTokens) throws NonSuccessfulResponseCodeException, PushNetworkException { - ContactTokenList contactTokenList = new ContactTokenList(new LinkedList<>(contactTokens)); - String response = makeRequest(DIRECTORY_TOKENS_PATH, "PUT", JsonUtil.toJson(contactTokenList)); - ContactTokenDetailsList activeTokens = JsonUtil.fromJson(response, ContactTokenDetailsList.class); + try { + ContactTokenList contactTokenList = new ContactTokenList(new LinkedList<>(contactTokens)); + String response = makeRequest(DIRECTORY_TOKENS_PATH, "PUT", JsonUtil.toJson(contactTokenList)); + ContactTokenDetailsList activeTokens = JsonUtil.fromJson(response, ContactTokenDetailsList.class); - return activeTokens.getContacts(); + return activeTokens.getContacts(); + } catch (IOException e) { + Log.w(TAG, e); + throw new NonSuccessfulResponseCodeException("Unable to parse entity"); + } } public ContactTokenDetails getContactTokenDetails(String contactToken) throws IOException { @@ -501,26 +503,45 @@ private HttpURLConnection makeBaseRequest(String urlFragment, String method, Str connection.disconnect(); throw new NotFoundException("Not found"); case 409: + MismatchedDevices mismatchedDevices; + try { - response = Util.readFully(connection.getErrorStream()); + response = Util.readFully(connection.getErrorStream()); + mismatchedDevices = JsonUtil.fromJson(response, MismatchedDevices.class); + } catch (JsonProcessingException e) { + Log.w(TAG, e); + throw new NonSuccessfulResponseCodeException("Bad response: " + responseCode + " " + responseMessage); } catch (IOException e) { throw new PushNetworkException(e); } - throw new MismatchedDevicesException(JsonUtil.fromJson(response, MismatchedDevices.class)); + + throw new MismatchedDevicesException(mismatchedDevices); case 410: + StaleDevices staleDevices; + try { - response = Util.readFully(connection.getErrorStream()); + response = Util.readFully(connection.getErrorStream()); + staleDevices = JsonUtil.fromJson(response, StaleDevices.class); + } catch (JsonProcessingException e) { + throw new NonSuccessfulResponseCodeException("Bad response: " + responseCode + " " + responseMessage); } catch (IOException e) { throw new PushNetworkException(e); } - throw new StaleDevicesException(JsonUtil.fromJson(response, StaleDevices.class)); + + throw new StaleDevicesException(staleDevices); case 411: + DeviceLimit deviceLimit; + try { - response = Util.readFully(connection.getErrorStream()); + response = Util.readFully(connection.getErrorStream()); + deviceLimit = JsonUtil.fromJson(response, DeviceLimit.class); + } catch (JsonProcessingException e) { + throw new NonSuccessfulResponseCodeException("Bad response: " + responseCode + " " + responseMessage); } catch (IOException e) { throw new PushNetworkException(e); } - throw new DeviceLimitExceededException(JsonUtil.fromJson(response, DeviceLimit.class)); + + throw new DeviceLimitExceededException(deviceLimit); case 417: throw new ExpectationFailedException(); } diff --git a/java/src/main/java/org/whispersystems/textsecure/internal/util/JsonUtil.java b/java/src/main/java/org/whispersystems/textsecure/internal/util/JsonUtil.java index 104a79fc3a..37f50dd0bd 100644 --- a/java/src/main/java/org/whispersystems/textsecure/internal/util/JsonUtil.java +++ b/java/src/main/java/org/whispersystems/textsecure/internal/util/JsonUtil.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; @@ -36,21 +37,12 @@ public static String toJson(Object object) { } } - public static T fromJson(String json, Class clazz) { - try { - return objectMapper.readValue(json, clazz); - } catch (IOException e) { - Log.w(TAG, e); - throw new JsonParseException(e); - } + public static T fromJson(String json, Class clazz) + throws IOException + { + return objectMapper.readValue(json, clazz); } - - public static class JsonParseException extends RuntimeException { - public JsonParseException(Exception e) { - super(e); - } - } - + public static class IdentityKeySerializer extends JsonSerializer { @Override public void serialize(IdentityKey value, JsonGenerator gen, SerializerProvider serializers) From c05108727f58886691ca7fc79b22036081cc5252 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Thu, 30 Jul 2015 12:55:14 -0700 Subject: [PATCH 05/19] Bump version to 1.6.2 // FREEBIE --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 28a474212c..a256cfe03f 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ subprojects { - ext.version_number = "1.6.1" + ext.version_number = "1.6.2" ext.group_info = "org.whispersystems" ext.axolotl_version = "1.3.1" From 4c93231c3ccfb662d42c52348b7ad1ad9f3989a4 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Thu, 20 Aug 2015 10:16:45 -0700 Subject: [PATCH 06/19] Add support for registering with token and specifying UA. // FREEBIE --- .../api/TextSecureAccountManager.java | 38 +++++++++++++++---- .../api/TextSecureMessageReceiver.java | 15 +++++--- .../api/TextSecureMessageSender.java | 3 +- .../internal/push/AccountAttributes.java | 11 +----- .../internal/push/PushServiceSocket.java | 26 ++++++++++--- .../websocket/OkHttpClientWrapper.java | 11 +++++- .../websocket/WebSocketConnection.java | 6 ++- 7 files changed, 78 insertions(+), 32 deletions(-) diff --git a/java/src/main/java/org/whispersystems/textsecure/api/TextSecureAccountManager.java b/java/src/main/java/org/whispersystems/textsecure/api/TextSecureAccountManager.java index 83364f7139..b76c0cf596 100644 --- a/java/src/main/java/org/whispersystems/textsecure/api/TextSecureAccountManager.java +++ b/java/src/main/java/org/whispersystems/textsecure/api/TextSecureAccountManager.java @@ -57,6 +57,7 @@ public class TextSecureAccountManager { private final PushServiceSocket pushServiceSocket; private final String user; + private final String userAgent; /** * Construct a TextSecureAccountManager. @@ -65,12 +66,15 @@ public class TextSecureAccountManager { * @param trustStore The {@link org.whispersystems.textsecure.api.push.TrustStore} for the TextSecure server's TLS certificate. * @param user A TextSecure phone number. * @param password A TextSecure password. + * @param userAgent A string which identifies the client software. */ public TextSecureAccountManager(String url, TrustStore trustStore, - String user, String password) + String user, String password, + String userAgent) { - this.pushServiceSocket = new PushServiceSocket(url, trustStore, new StaticCredentialsProvider(user, password, null)); + this.pushServiceSocket = new PushServiceSocket(url, trustStore, new StaticCredentialsProvider(user, password, null), userAgent); this.user = user; + this.userAgent = userAgent; } /** @@ -108,14 +112,13 @@ public void requestVoiceVerificationCode() throws IOException { } /** - * Verify a TextSecure account. + * Verify a TextSecure account with a received SMS or voice verification code. * * @param verificationCode The verification code received via SMS or Voice * (see {@link #requestSmsVerificationCode} and * {@link #requestVoiceVerificationCode}). * @param signalingKey 52 random bytes. A 32 byte AES key and a 20 byte Hmac256 key, * concatenated. - * @param supportsSms Indicate whether this client is capable of supporting encrypted SMS. * @param axolotlRegistrationId A random 14-bit number that identifies this TextSecure install. * This value should remain consistent across registrations for the * same install, but probabilistically differ across registrations @@ -123,12 +126,31 @@ public void requestVoiceVerificationCode() throws IOException { * * @throws IOException */ - public void verifyAccount(String verificationCode, String signalingKey, - boolean supportsSms, int axolotlRegistrationId) + public void verifyAccountWithCode(String verificationCode, String signalingKey, int axolotlRegistrationId) throws IOException { - this.pushServiceSocket.verifyAccount(verificationCode, signalingKey, - supportsSms, axolotlRegistrationId); + this.pushServiceSocket.verifyAccountCode(verificationCode, signalingKey, + axolotlRegistrationId); + } + + /** + * Verify a TextSecure account with a signed token from a trusted source. + * + * @param verificationToken The signed token provided by a trusted server. + + * @param signalingKey 52 random bytes. A 32 byte AES key and a 20 byte Hmac256 key, + * concatenated. + * @param axolotlRegistrationId A random 14-bit number that identifies this TextSecure install. + * This value should remain consistent across registrations for the + * same install, but probabilistically differ across registrations + * for separate installs. + * + * @throws IOException + */ + public void verifyAccountWithToken(String verificationToken, String signalingKey, int axolotlRegistrationId) + throws IOException + { + this.pushServiceSocket.verifyAccountToken(verificationToken, signalingKey, axolotlRegistrationId); } /** diff --git a/java/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageReceiver.java b/java/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageReceiver.java index 093cb1f5e8..6e653e2261 100644 --- a/java/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageReceiver.java +++ b/java/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageReceiver.java @@ -47,6 +47,7 @@ public class TextSecureMessageReceiver { private final TrustStore trustStore; private final String url; private final CredentialsProvider credentialsProvider; + private final String userAgent; /** * Construct a TextSecureMessageReceiver. @@ -59,9 +60,10 @@ public class TextSecureMessageReceiver { * @param signalingKey The 52 byte signaling key assigned to this user at registration. */ public TextSecureMessageReceiver(String url, TrustStore trustStore, - String user, String password, String signalingKey) + String user, String password, + String signalingKey, String userAgent) { - this(url, trustStore, new StaticCredentialsProvider(user, password, signalingKey)); + this(url, trustStore, new StaticCredentialsProvider(user, password, signalingKey), userAgent); } /** @@ -72,11 +74,14 @@ public TextSecureMessageReceiver(String url, TrustStore trustStore, * the server's TLS signing certificate. * @param credentials The TextSecure user's credentials. */ - public TextSecureMessageReceiver(String url, TrustStore trustStore, CredentialsProvider credentials) { + public TextSecureMessageReceiver(String url, TrustStore trustStore, + CredentialsProvider credentials, String userAgent) + { this.url = url; this.trustStore = trustStore; this.credentialsProvider = credentials; - this.socket = new PushServiceSocket(url, trustStore, credentials); + this.socket = new PushServiceSocket(url, trustStore, credentials, userAgent); + this.userAgent = userAgent; } /** @@ -124,7 +129,7 @@ public InputStream retrieveAttachment(TextSecureAttachmentPointer pointer, File * @return A TextSecureMessagePipe for receiving TextSecure messages. */ public TextSecureMessagePipe createMessagePipe() { - WebSocketConnection webSocket = new WebSocketConnection(url, trustStore, credentialsProvider); + WebSocketConnection webSocket = new WebSocketConnection(url, trustStore, credentialsProvider, userAgent); return new TextSecureMessagePipe(webSocket, credentialsProvider); } diff --git a/java/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageSender.java b/java/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageSender.java index 959d7570fc..7a789ea7b4 100644 --- a/java/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageSender.java +++ b/java/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageSender.java @@ -88,9 +88,10 @@ public class TextSecureMessageSender { public TextSecureMessageSender(String url, TrustStore trustStore, String user, String password, AxolotlStore store, + String userAgent, Optional eventListener) { - this.socket = new PushServiceSocket(url, trustStore, new StaticCredentialsProvider(user, password, null)); + this.socket = new PushServiceSocket(url, trustStore, new StaticCredentialsProvider(user, password, null), userAgent); this.store = store; this.localAddress = new TextSecureAddress(user); this.eventListener = eventListener; diff --git a/java/src/main/java/org/whispersystems/textsecure/internal/push/AccountAttributes.java b/java/src/main/java/org/whispersystems/textsecure/internal/push/AccountAttributes.java index 5d32b48378..f92f9a60a8 100644 --- a/java/src/main/java/org/whispersystems/textsecure/internal/push/AccountAttributes.java +++ b/java/src/main/java/org/whispersystems/textsecure/internal/push/AccountAttributes.java @@ -23,15 +23,11 @@ public class AccountAttributes { @JsonProperty private String signalingKey; - @JsonProperty - private boolean supportsSms; - @JsonProperty private int registrationId; - public AccountAttributes(String signalingKey, boolean supportsSms, int registrationId) { + public AccountAttributes(String signalingKey, int registrationId) { this.signalingKey = signalingKey; - this.supportsSms = supportsSms; this.registrationId = registrationId; } @@ -41,11 +37,8 @@ public String getSignalingKey() { return signalingKey; } - public boolean isSupportsSms() { - return supportsSms; - } - public int getRegistrationId() { return registrationId; } + } diff --git a/java/src/main/java/org/whispersystems/textsecure/internal/push/PushServiceSocket.java b/java/src/main/java/org/whispersystems/textsecure/internal/push/PushServiceSocket.java index fc5ca9a92c..2ea74663a4 100644 --- a/java/src/main/java/org/whispersystems/textsecure/internal/push/PushServiceSocket.java +++ b/java/src/main/java/org/whispersystems/textsecure/internal/push/PushServiceSocket.java @@ -79,7 +79,8 @@ public class PushServiceSocket { private static final String CREATE_ACCOUNT_SMS_PATH = "/v1/accounts/sms/code/%s"; private static final String CREATE_ACCOUNT_VOICE_PATH = "/v1/accounts/voice/code/%s"; - private static final String VERIFY_ACCOUNT_PATH = "/v1/accounts/code/%s"; + private static final String VERIFY_ACCOUNT_CODE_PATH = "/v1/accounts/code/%s"; + private static final String VERIFY_ACCOUNT_TOKEN_PATH = "/v1/accounts/token/%s"; private static final String REGISTER_GCM_PATH = "/v1/accounts/gcm/"; private static final String PREKEY_METADATA_PATH = "/v2/keys/"; @@ -103,12 +104,14 @@ public class PushServiceSocket { private final String serviceUrl; private final TrustManager[] trustManagers; private final CredentialsProvider credentialsProvider; + private final String userAgent; - public PushServiceSocket(String serviceUrl, TrustStore trustStore, CredentialsProvider credentialsProvider) + public PushServiceSocket(String serviceUrl, TrustStore trustStore, CredentialsProvider credentialsProvider, String userAgent) { this.serviceUrl = serviceUrl; this.credentialsProvider = credentialsProvider; this.trustManagers = BlacklistingTrustManager.createFor(trustStore); + this.userAgent = userAgent; } public void createAccount(boolean voice) throws IOException { @@ -116,12 +119,19 @@ public void createAccount(boolean voice) throws IOException { makeRequest(String.format(path, credentialsProvider.getUser()), "GET", null); } - public void verifyAccount(String verificationCode, String signalingKey, - boolean supportsSms, int registrationId) + public void verifyAccountCode(String verificationCode, String signalingKey, int registrationId) throws IOException { - AccountAttributes signalingKeyEntity = new AccountAttributes(signalingKey, supportsSms, registrationId); - makeRequest(String.format(VERIFY_ACCOUNT_PATH, verificationCode), + AccountAttributes signalingKeyEntity = new AccountAttributes(signalingKey, registrationId); + makeRequest(String.format(VERIFY_ACCOUNT_CODE_PATH, verificationCode), + "PUT", JsonUtil.toJson(signalingKeyEntity)); + } + + public void verifyAccountToken(String verificationToken, String signalingKey, int registrationId) + throws IOException + { + AccountAttributes signalingKeyEntity = new AccountAttributes(signalingKey, registrationId); + makeRequest(String.format(VERIFY_ACCOUNT_TOKEN_PATH, verificationToken), "PUT", JsonUtil.toJson(signalingKeyEntity)); } @@ -579,6 +589,10 @@ private HttpURLConnection getConnection(String urlFragment, String method, Strin connection.setRequestProperty("Authorization", getAuthorizationHeader()); } + if (userAgent != null) { + connection.setRequestProperty("X-Signal-Agent", userAgent); + } + if (body != null) { connection.setDoOutput(true); } diff --git a/java/src/main/java/org/whispersystems/textsecure/internal/websocket/OkHttpClientWrapper.java b/java/src/main/java/org/whispersystems/textsecure/internal/websocket/OkHttpClientWrapper.java index a439565364..9a60e17f5c 100644 --- a/java/src/main/java/org/whispersystems/textsecure/internal/websocket/OkHttpClientWrapper.java +++ b/java/src/main/java/org/whispersystems/textsecure/internal/websocket/OkHttpClientWrapper.java @@ -31,6 +31,7 @@ public class OkHttpClientWrapper implements WebSocketListener { private final TrustStore trustStore; private final CredentialsProvider credentialsProvider; private final WebSocketEventListener listener; + private final String userAgent; private WebSocket webSocket; private boolean closed; @@ -38,6 +39,7 @@ public class OkHttpClientWrapper implements WebSocketListener { public OkHttpClientWrapper(String uri, TrustStore trustStore, CredentialsProvider credentialsProvider, + String userAgent, WebSocketEventListener listener) { Log.w(TAG, "Connecting to: " + uri); @@ -45,6 +47,7 @@ public OkHttpClientWrapper(String uri, TrustStore trustStore, this.uri = uri; this.trustStore = trustStore; this.credentialsProvider = credentialsProvider; + this.userAgent = userAgent; this.listener = listener; } @@ -127,7 +130,13 @@ private synchronized WebSocket newSocket(int timeout, TimeUnit unit) { okHttpClient.setReadTimeout(timeout, unit); okHttpClient.setConnectTimeout(timeout, unit); - return WebSocket.newWebSocket(okHttpClient, new Request.Builder().url(filledUri).build()); + Request.Builder requestBuilder = new Request.Builder().url(filledUri); + + if (userAgent != null) { + requestBuilder.addHeader("X-Signal-Agent", userAgent); + } + + return WebSocket.newWebSocket(okHttpClient, requestBuilder.build()); } private SSLSocketFactory createTlsSocketFactory(TrustStore trustStore) { diff --git a/java/src/main/java/org/whispersystems/textsecure/internal/websocket/WebSocketConnection.java b/java/src/main/java/org/whispersystems/textsecure/internal/websocket/WebSocketConnection.java index 3b4a67f243..46281dbab4 100644 --- a/java/src/main/java/org/whispersystems/textsecure/internal/websocket/WebSocketConnection.java +++ b/java/src/main/java/org/whispersystems/textsecure/internal/websocket/WebSocketConnection.java @@ -27,13 +27,15 @@ public class WebSocketConnection implements WebSocketEventListener { private final String wsUri; private final TrustStore trustStore; private final CredentialsProvider credentialsProvider; + private final String userAgent; private OkHttpClientWrapper client; private KeepAliveSender keepAliveSender; - public WebSocketConnection(String httpUri, TrustStore trustStore, CredentialsProvider credentialsProvider) { + public WebSocketConnection(String httpUri, TrustStore trustStore, CredentialsProvider credentialsProvider, String userAgent) { this.trustStore = trustStore; this.credentialsProvider = credentialsProvider; + this.userAgent = userAgent; this.wsUri = httpUri.replace("https://", "wss://") .replace("http://", "ws://") + "/v1/websocket/?login=%s&password=%s"; } @@ -42,7 +44,7 @@ public synchronized void connect() { Log.w(TAG, "WSC connect()..."); if (client == null) { - client = new OkHttpClientWrapper(wsUri, trustStore, credentialsProvider, this); + client = new OkHttpClientWrapper(wsUri, trustStore, credentialsProvider, userAgent, this); client.connect(KEEPALIVE_TIMEOUT_SECONDS + 10, TimeUnit.SECONDS); } } From cbb2038d46b940cf3b71a457863c886cd329b3fb Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Thu, 20 Aug 2015 10:17:11 -0700 Subject: [PATCH 07/19] Bump version to 1.7.0 // FREEBIE --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index a256cfe03f..100a9e070e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ subprojects { - ext.version_number = "1.6.2" + ext.version_number = "1.7.0" ext.group_info = "org.whispersystems" ext.axolotl_version = "1.3.1" From a25ebefcbbacfaa18e7cc70e297f328ba81cfc1a Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Mon, 21 Sep 2015 14:34:06 -0700 Subject: [PATCH 08/19] Support for verification tokens // FREEBIE --- .../textsecure/api/TextSecureAccountManager.java | 4 ++++ .../internal/push/AuthorizationToken.java | 13 +++++++++++++ .../textsecure/internal/push/PushServiceSocket.java | 6 ++++++ 3 files changed, 23 insertions(+) create mode 100644 java/src/main/java/org/whispersystems/textsecure/internal/push/AuthorizationToken.java diff --git a/java/src/main/java/org/whispersystems/textsecure/api/TextSecureAccountManager.java b/java/src/main/java/org/whispersystems/textsecure/api/TextSecureAccountManager.java index b76c0cf596..e2e713402d 100644 --- a/java/src/main/java/org/whispersystems/textsecure/api/TextSecureAccountManager.java +++ b/java/src/main/java/org/whispersystems/textsecure/api/TextSecureAccountManager.java @@ -235,6 +235,10 @@ public List getContacts(Set e164numbers) return activeTokens; } + public String getAccountVerificationToken() throws IOException { + return this.pushServiceSocket.getAccountVerificationToken(); + } + public String getNewDeviceVerificationCode() throws IOException { return this.pushServiceSocket.getNewDeviceVerificationCode(); } diff --git a/java/src/main/java/org/whispersystems/textsecure/internal/push/AuthorizationToken.java b/java/src/main/java/org/whispersystems/textsecure/internal/push/AuthorizationToken.java new file mode 100644 index 0000000000..e82a137a2b --- /dev/null +++ b/java/src/main/java/org/whispersystems/textsecure/internal/push/AuthorizationToken.java @@ -0,0 +1,13 @@ +package org.whispersystems.textsecure.internal.push; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class AuthorizationToken { + + @JsonProperty + private String token; + + public String getToken() { + return token; + } +} diff --git a/java/src/main/java/org/whispersystems/textsecure/internal/push/PushServiceSocket.java b/java/src/main/java/org/whispersystems/textsecure/internal/push/PushServiceSocket.java index 2ea74663a4..a6c60efadf 100644 --- a/java/src/main/java/org/whispersystems/textsecure/internal/push/PushServiceSocket.java +++ b/java/src/main/java/org/whispersystems/textsecure/internal/push/PushServiceSocket.java @@ -82,6 +82,7 @@ public class PushServiceSocket { private static final String VERIFY_ACCOUNT_CODE_PATH = "/v1/accounts/code/%s"; private static final String VERIFY_ACCOUNT_TOKEN_PATH = "/v1/accounts/token/%s"; private static final String REGISTER_GCM_PATH = "/v1/accounts/gcm/"; + private static final String REQUEST_TOKEN_PATH = "/v1/accounts/token"; private static final String PREKEY_METADATA_PATH = "/v2/keys/"; private static final String PREKEY_PATH = "/v2/keys/%s"; @@ -135,6 +136,11 @@ public void verifyAccountToken(String verificationToken, String signalingKey, in "PUT", JsonUtil.toJson(signalingKeyEntity)); } + public String getAccountVerificationToken() throws IOException { + String responseText = makeRequest(REQUEST_TOKEN_PATH, "GET", null); + return JsonUtil.fromJson(responseText, AuthorizationToken.class).getToken(); + } + public String getNewDeviceVerificationCode() throws IOException { String responseText = makeRequest(PROVISIONING_CODE_PATH, "GET", null); return JsonUtil.fromJson(responseText, DeviceCode.class).getVerificationCode(); From e530014a5e16ccdbf9cb74037a083c7d7cff4c13 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Mon, 21 Sep 2015 15:07:01 -0700 Subject: [PATCH 09/19] Support for size and preview info in attachment pointers. // FREEBIE --- .../api/TextSecureMessageSender.java | 16 +- .../api/crypto/TextSecureCipher.java | 5 +- .../messages/TextSecureAttachmentPointer.java | 30 ++- .../messages/TextSecureAttachmentStream.java | 12 ++ .../internal/push/TextSecureProtos.java | 200 ++++++++++++++++-- protobuf/TextSecure.proto | 2 + 6 files changed, 236 insertions(+), 29 deletions(-) diff --git a/java/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageSender.java b/java/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageSender.java index 7a789ea7b4..eaee62e68f 100644 --- a/java/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageSender.java +++ b/java/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageSender.java @@ -341,11 +341,17 @@ private AttachmentPointer createAttachmentPointer(TextSecureAttachmentStream att long attachmentId = socket.sendAttachment(attachmentData); - return AttachmentPointer.newBuilder() - .setContentType(attachment.getContentType()) - .setId(attachmentId) - .setKey(ByteString.copyFrom(attachmentKey)) - .build(); + AttachmentPointer.Builder builder = AttachmentPointer.newBuilder() + .setContentType(attachment.getContentType()) + .setId(attachmentId) + .setKey(ByteString.copyFrom(attachmentKey)) + .setSize((int)attachment.getLength()); + + if (attachment.getPreview().isPresent()) { + builder.setThumbnail(ByteString.copyFrom(attachment.getPreview().get())); + } + + return builder.build(); } diff --git a/java/src/main/java/org/whispersystems/textsecure/api/crypto/TextSecureCipher.java b/java/src/main/java/org/whispersystems/textsecure/api/crypto/TextSecureCipher.java index 5d54059885..8fafb0a604 100644 --- a/java/src/main/java/org/whispersystems/textsecure/api/crypto/TextSecureCipher.java +++ b/java/src/main/java/org/whispersystems/textsecure/api/crypto/TextSecureCipher.java @@ -32,6 +32,7 @@ import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage; import org.whispersystems.libaxolotl.protocol.WhisperMessage; import org.whispersystems.libaxolotl.state.AxolotlStore; +import org.whispersystems.libaxolotl.util.guava.Optional; import org.whispersystems.textsecure.api.messages.TextSecureAttachment; import org.whispersystems.textsecure.api.messages.TextSecureAttachmentPointer; import org.whispersystems.textsecure.api.messages.TextSecureContent; @@ -164,7 +165,9 @@ private TextSecureDataMessage createTextSecureMessage(TextSecureEnvelope envelop attachments.add(new TextSecureAttachmentPointer(pointer.getId(), pointer.getContentType(), pointer.getKey().toByteArray(), - envelope.getRelay())); + envelope.getRelay(), + pointer.hasSize() ? Optional.of(pointer.getSize()) : Optional.absent(), + pointer.hasThumbnail() ? Optional.of(pointer.getThumbnail().toByteArray()): Optional.absent())); } return new TextSecureDataMessage(envelope.getTimestamp(), groupInfo, attachments, diff --git a/java/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureAttachmentPointer.java b/java/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureAttachmentPointer.java index a86e6199d2..aad4805197 100644 --- a/java/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureAttachmentPointer.java +++ b/java/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureAttachmentPointer.java @@ -27,15 +27,25 @@ */ public class TextSecureAttachmentPointer extends TextSecureAttachment { - private final long id; - private final byte[] key; - private final Optional relay; + private final long id; + private final byte[] key; + private final Optional relay; + private final Optional size; + private final Optional preview; public TextSecureAttachmentPointer(long id, String contentType, byte[] key, String relay) { + this(id, contentType, key, relay, Optional.absent(), Optional.absent()); + } + + public TextSecureAttachmentPointer(long id, String contentType, byte[] key, String relay, + Optional size, Optional preview) + { super(contentType); - this.id = id; - this.key = key; - this.relay = Optional.fromNullable(relay); + this.id = id; + this.key = key; + this.relay = Optional.fromNullable(relay); + this.size = size; + this.preview = preview; } public long getId() { @@ -59,4 +69,12 @@ public boolean isPointer() { public Optional getRelay() { return relay; } + + public Optional getSize() { + return size; + } + + public Optional getPreview() { + return preview; + } } diff --git a/java/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureAttachmentStream.java b/java/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureAttachmentStream.java index 81fae9ed54..a3955cfba6 100644 --- a/java/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureAttachmentStream.java +++ b/java/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureAttachmentStream.java @@ -16,6 +16,8 @@ */ package org.whispersystems.textsecure.api.messages; +import org.whispersystems.libaxolotl.util.guava.Optional; + import java.io.InputStream; /** @@ -26,12 +28,18 @@ public class TextSecureAttachmentStream extends TextSecureAttachment { private final InputStream inputStream; private final long length; private final ProgressListener listener; + private final Optional preview; public TextSecureAttachmentStream(InputStream inputStream, String contentType, long length, ProgressListener listener) { + this(inputStream, contentType, length, Optional.absent(), listener); + } + + public TextSecureAttachmentStream(InputStream inputStream, String contentType, long length, Optional preview, ProgressListener listener) { super(contentType); this.inputStream = inputStream; this.length = length; this.listener = listener; + this.preview = preview; } @Override @@ -55,4 +63,8 @@ public long getLength() { public ProgressListener getListener() { return listener; } + + public Optional getPreview() { + return preview; + } } diff --git a/java/src/main/java/org/whispersystems/textsecure/internal/push/TextSecureProtos.java b/java/src/main/java/org/whispersystems/textsecure/internal/push/TextSecureProtos.java index 652baefabb..ee3d6b2861 100644 --- a/java/src/main/java/org/whispersystems/textsecure/internal/push/TextSecureProtos.java +++ b/java/src/main/java/org/whispersystems/textsecure/internal/push/TextSecureProtos.java @@ -6480,6 +6480,26 @@ public interface AttachmentPointerOrBuilder * optional bytes key = 3; */ com.google.protobuf.ByteString getKey(); + + // optional uint32 size = 4; + /** + * optional uint32 size = 4; + */ + boolean hasSize(); + /** + * optional uint32 size = 4; + */ + int getSize(); + + // optional bytes thumbnail = 5; + /** + * optional bytes thumbnail = 5; + */ + boolean hasThumbnail(); + /** + * optional bytes thumbnail = 5; + */ + com.google.protobuf.ByteString getThumbnail(); } /** * Protobuf type {@code textsecure.AttachmentPointer} @@ -6547,6 +6567,16 @@ private AttachmentPointer( key_ = input.readBytes(); break; } + case 32: { + bitField0_ |= 0x00000008; + size_ = input.readUInt32(); + break; + } + case 42: { + bitField0_ |= 0x00000010; + thumbnail_ = input.readBytes(); + break; + } } } } catch (com.google.protobuf.InvalidProtocolBufferException e) { @@ -6662,10 +6692,44 @@ public com.google.protobuf.ByteString getKey() { return key_; } + // optional uint32 size = 4; + public static final int SIZE_FIELD_NUMBER = 4; + private int size_; + /** + * optional uint32 size = 4; + */ + public boolean hasSize() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + /** + * optional uint32 size = 4; + */ + public int getSize() { + return size_; + } + + // optional bytes thumbnail = 5; + public static final int THUMBNAIL_FIELD_NUMBER = 5; + private com.google.protobuf.ByteString thumbnail_; + /** + * optional bytes thumbnail = 5; + */ + public boolean hasThumbnail() { + return ((bitField0_ & 0x00000010) == 0x00000010); + } + /** + * optional bytes thumbnail = 5; + */ + public com.google.protobuf.ByteString getThumbnail() { + return thumbnail_; + } + private void initFields() { id_ = 0L; contentType_ = ""; key_ = com.google.protobuf.ByteString.EMPTY; + size_ = 0; + thumbnail_ = com.google.protobuf.ByteString.EMPTY; } private byte memoizedIsInitialized = -1; public final boolean isInitialized() { @@ -6688,6 +6752,12 @@ public void writeTo(com.google.protobuf.CodedOutputStream output) if (((bitField0_ & 0x00000004) == 0x00000004)) { output.writeBytes(3, key_); } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + output.writeUInt32(4, size_); + } + if (((bitField0_ & 0x00000010) == 0x00000010)) { + output.writeBytes(5, thumbnail_); + } getUnknownFields().writeTo(output); } @@ -6709,6 +6779,14 @@ public int getSerializedSize() { size += com.google.protobuf.CodedOutputStream .computeBytesSize(3, key_); } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt32Size(4, size_); + } + if (((bitField0_ & 0x00000010) == 0x00000010)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(5, thumbnail_); + } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; return size; @@ -6831,6 +6909,10 @@ public Builder clear() { bitField0_ = (bitField0_ & ~0x00000002); key_ = com.google.protobuf.ByteString.EMPTY; bitField0_ = (bitField0_ & ~0x00000004); + size_ = 0; + bitField0_ = (bitField0_ & ~0x00000008); + thumbnail_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000010); return this; } @@ -6871,6 +6953,14 @@ public org.whispersystems.textsecure.internal.push.TextSecureProtos.AttachmentPo to_bitField0_ |= 0x00000004; } result.key_ = key_; + if (((from_bitField0_ & 0x00000008) == 0x00000008)) { + to_bitField0_ |= 0x00000008; + } + result.size_ = size_; + if (((from_bitField0_ & 0x00000010) == 0x00000010)) { + to_bitField0_ |= 0x00000010; + } + result.thumbnail_ = thumbnail_; result.bitField0_ = to_bitField0_; onBuilt(); return result; @@ -6898,6 +6988,12 @@ public Builder mergeFrom(org.whispersystems.textsecure.internal.push.TextSecureP if (other.hasKey()) { setKey(other.getKey()); } + if (other.hasSize()) { + setSize(other.getSize()); + } + if (other.hasThumbnail()) { + setThumbnail(other.getThumbnail()); + } this.mergeUnknownFields(other.getUnknownFields()); return this; } @@ -7068,6 +7164,75 @@ public Builder clearKey() { return this; } + // optional uint32 size = 4; + private int size_ ; + /** + * optional uint32 size = 4; + */ + public boolean hasSize() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + /** + * optional uint32 size = 4; + */ + public int getSize() { + return size_; + } + /** + * optional uint32 size = 4; + */ + public Builder setSize(int value) { + bitField0_ |= 0x00000008; + size_ = value; + onChanged(); + return this; + } + /** + * optional uint32 size = 4; + */ + public Builder clearSize() { + bitField0_ = (bitField0_ & ~0x00000008); + size_ = 0; + onChanged(); + return this; + } + + // optional bytes thumbnail = 5; + private com.google.protobuf.ByteString thumbnail_ = com.google.protobuf.ByteString.EMPTY; + /** + * optional bytes thumbnail = 5; + */ + public boolean hasThumbnail() { + return ((bitField0_ & 0x00000010) == 0x00000010); + } + /** + * optional bytes thumbnail = 5; + */ + public com.google.protobuf.ByteString getThumbnail() { + return thumbnail_; + } + /** + * optional bytes thumbnail = 5; + */ + public Builder setThumbnail(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000010; + thumbnail_ = value; + onChanged(); + return this; + } + /** + * optional bytes thumbnail = 5; + */ + public Builder clearThumbnail() { + bitField0_ = (bitField0_ & ~0x00000010); + thumbnail_ = getDefaultInstance().getThumbnail(); + onChanged(); + return this; + } + // @@protoc_insertion_point(builder_scope:textsecure.AttachmentPointer) } @@ -11161,23 +11326,24 @@ public org.whispersystems.textsecure.internal.push.TextSecureProtos.GroupDetails "\004blob\030\001 \001(\0132\035.textsecure.AttachmentPoint" + "er\032l\n\007Request\0222\n\004type\030\001 \001(\0162$.textsecure" + ".SyncMessage.Request.Type\"-\n\004Type\022\013\n\007UNK" + - "NOWN\020\000\022\014\n\010CONTACTS\020\001\022\n\n\006GROUPS\020\002\"A\n\021Atta" + + "NOWN\020\000\022\014\n\010CONTACTS\020\001\022\n\n\006GROUPS\020\002\"b\n\021Atta" + "chmentPointer\022\n\n\002id\030\001 \001(\006\022\023\n\013contentType" + - "\030\002 \001(\t\022\013\n\003key\030\003 \001(\014\"\315\001\n\014GroupContext\022\n\n\002" + - "id\030\001 \001(\014\022+\n\004type\030\002 \001(\0162\035.textsecure.Grou", - "pContext.Type\022\014\n\004name\030\003 \001(\t\022\017\n\007members\030\004" + - " \003(\t\022-\n\006avatar\030\005 \001(\0132\035.textsecure.Attach" + - "mentPointer\"6\n\004Type\022\013\n\007UNKNOWN\020\000\022\n\n\006UPDA" + - "TE\020\001\022\013\n\007DELIVER\020\002\022\010\n\004QUIT\020\003\"\220\001\n\016ContactD" + - "etails\022\016\n\006number\030\001 \001(\t\022\014\n\004name\030\002 \001(\t\0221\n\006" + - "avatar\030\003 \001(\0132!.textsecure.ContactDetails" + - ".Avatar\032-\n\006Avatar\022\023\n\013contentType\030\001 \001(\t\022\016" + - "\n\006length\030\002 \001(\r\"\231\001\n\014GroupDetails\022\n\n\002id\030\001 " + - "\001(\014\022\014\n\004name\030\002 \001(\t\022\017\n\007members\030\003 \003(\t\022/\n\006av" + - "atar\030\004 \001(\0132\037.textsecure.GroupDetails.Ava", - "tar\032-\n\006Avatar\022\023\n\013contentType\030\001 \001(\t\022\016\n\006le" + - "ngth\030\002 \001(\rB?\n+org.whispersystems.textsec" + - "ure.internal.pushB\020TextSecureProtos" + "\030\002 \001(\t\022\013\n\003key\030\003 \001(\014\022\014\n\004size\030\004 \001(\r\022\021\n\tthu" + + "mbnail\030\005 \001(\014\"\315\001\n\014GroupContext\022\n\n\002id\030\001 \001(", + "\014\022+\n\004type\030\002 \001(\0162\035.textsecure.GroupContex" + + "t.Type\022\014\n\004name\030\003 \001(\t\022\017\n\007members\030\004 \003(\t\022-\n" + + "\006avatar\030\005 \001(\0132\035.textsecure.AttachmentPoi" + + "nter\"6\n\004Type\022\013\n\007UNKNOWN\020\000\022\n\n\006UPDATE\020\001\022\013\n" + + "\007DELIVER\020\002\022\010\n\004QUIT\020\003\"\220\001\n\016ContactDetails\022" + + "\016\n\006number\030\001 \001(\t\022\014\n\004name\030\002 \001(\t\0221\n\006avatar\030" + + "\003 \001(\0132!.textsecure.ContactDetails.Avatar" + + "\032-\n\006Avatar\022\023\n\013contentType\030\001 \001(\t\022\016\n\006lengt" + + "h\030\002 \001(\r\"\231\001\n\014GroupDetails\022\n\n\002id\030\001 \001(\014\022\014\n\004" + + "name\030\002 \001(\t\022\017\n\007members\030\003 \003(\t\022/\n\006avatar\030\004 ", + "\001(\0132\037.textsecure.GroupDetails.Avatar\032-\n\006" + + "Avatar\022\023\n\013contentType\030\001 \001(\t\022\016\n\006length\030\002 " + + "\001(\rB?\n+org.whispersystems.textsecure.int" + + "ernal.pushB\020TextSecureProtos" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { @@ -11237,7 +11403,7 @@ public com.google.protobuf.ExtensionRegistry assignDescriptors( internal_static_textsecure_AttachmentPointer_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_textsecure_AttachmentPointer_descriptor, - new java.lang.String[] { "Id", "ContentType", "Key", }); + new java.lang.String[] { "Id", "ContentType", "Key", "Size", "Thumbnail", }); internal_static_textsecure_GroupContext_descriptor = getDescriptor().getMessageTypes().get(5); internal_static_textsecure_GroupContext_fieldAccessorTable = new diff --git a/protobuf/TextSecure.proto b/protobuf/TextSecure.proto index d561175280..91514c4bc9 100644 --- a/protobuf/TextSecure.proto +++ b/protobuf/TextSecure.proto @@ -72,6 +72,8 @@ message AttachmentPointer { optional fixed64 id = 1; optional string contentType = 2; optional bytes key = 3; + optional uint32 size = 4; + optional bytes thumbnail = 5; } message GroupContext { From 68132de1eb902282763a892e869679bc584c3edc Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Mon, 21 Sep 2015 15:07:29 -0700 Subject: [PATCH 10/19] Bump version to 1.7.2 // FREEBIE --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 100a9e070e..7f09eb9a2b 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ subprojects { - ext.version_number = "1.7.0" + ext.version_number = "1.7.2" ext.group_info = "org.whispersystems" ext.axolotl_version = "1.3.1" From 13086cfba468c98d66c285a20dc8c13d92b39a85 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Mon, 21 Sep 2015 17:31:38 -0700 Subject: [PATCH 11/19] Support for syncing voice support indicators // FREEBIE --- .../api/TextSecureAccountManager.java | 28 ++++++++++++++++--- .../api/push/ContactTokenDetails.java | 17 ++++++++--- .../internal/push/AccountAttributes.java | 9 +++++- .../push/ContactTokenDetailsList.java | 3 ++ .../internal/push/PushServiceSocket.java | 14 +++++++--- 5 files changed, 58 insertions(+), 13 deletions(-) diff --git a/java/src/main/java/org/whispersystems/textsecure/api/TextSecureAccountManager.java b/java/src/main/java/org/whispersystems/textsecure/api/TextSecureAccountManager.java index e2e713402d..bce04ef6c4 100644 --- a/java/src/main/java/org/whispersystems/textsecure/api/TextSecureAccountManager.java +++ b/java/src/main/java/org/whispersystems/textsecure/api/TextSecureAccountManager.java @@ -123,14 +123,15 @@ public void requestVoiceVerificationCode() throws IOException { * This value should remain consistent across registrations for the * same install, but probabilistically differ across registrations * for separate installs. + * @param voice A boolean that indicates whether the client supports secure voice (RedPhone) calls. * * @throws IOException */ - public void verifyAccountWithCode(String verificationCode, String signalingKey, int axolotlRegistrationId) + public void verifyAccountWithCode(String verificationCode, String signalingKey, int axolotlRegistrationId, boolean voice) throws IOException { this.pushServiceSocket.verifyAccountCode(verificationCode, signalingKey, - axolotlRegistrationId); + axolotlRegistrationId, voice); } /** @@ -144,13 +145,32 @@ public void verifyAccountWithCode(String verificationCode, String signalingKey, * This value should remain consistent across registrations for the * same install, but probabilistically differ across registrations * for separate installs. + * @param voice A boolean that indicates whether the client supports secure voice (RedPhone) calls. * * @throws IOException */ - public void verifyAccountWithToken(String verificationToken, String signalingKey, int axolotlRegistrationId) + public void verifyAccountWithToken(String verificationToken, String signalingKey, int axolotlRegistrationId, boolean voice) throws IOException { - this.pushServiceSocket.verifyAccountToken(verificationToken, signalingKey, axolotlRegistrationId); + this.pushServiceSocket.verifyAccountToken(verificationToken, signalingKey, axolotlRegistrationId, voice); + } + + /** + * Refresh account attributes with server. + * + * @param signalingKey 52 random bytes. A 32 byte AES key and a 20 byte Hmac256 key, concatenated. + * @param axolotlRegistrationId A random 14-bit number that identifies this TextSecure install. + * This value should remain consistent across registrations for the same + * install, but probabilistically differ across registrations for + * separate installs. + * @param voice A boolean that indicates whether the client supports secure voice (RedPhone) + * + * @throws IOException + */ + public void setAccountAttributes(String signalingKey, int axolotlRegistrationId, boolean voice) + throws IOException + { + this.pushServiceSocket.setAccountAttributes(signalingKey, axolotlRegistrationId, voice); } /** diff --git a/java/src/main/java/org/whispersystems/textsecure/api/push/ContactTokenDetails.java b/java/src/main/java/org/whispersystems/textsecure/api/push/ContactTokenDetails.java index 3012492446..3140e7d7a8 100644 --- a/java/src/main/java/org/whispersystems/textsecure/api/push/ContactTokenDetails.java +++ b/java/src/main/java/org/whispersystems/textsecure/api/push/ContactTokenDetails.java @@ -16,15 +16,24 @@ */ package org.whispersystems.textsecure.api.push; +import com.fasterxml.jackson.annotation.JsonProperty; + /** * A class that represents a contact's registration state. */ public class ContactTokenDetails { + @JsonProperty private String token; + + @JsonProperty private String relay; + + @JsonProperty private String number; - private boolean supportsSms; + + @JsonProperty + private boolean voice; public ContactTokenDetails() {} @@ -43,10 +52,10 @@ public String getRelay() { } /** - * @return Whether this contact supports receiving encrypted SMS. + * @return Whether this contact supports secure voice calls. */ - public boolean isSupportsSms() { - return supportsSms; + public boolean isVoice() { + return voice; } public void setNumber(String number) { diff --git a/java/src/main/java/org/whispersystems/textsecure/internal/push/AccountAttributes.java b/java/src/main/java/org/whispersystems/textsecure/internal/push/AccountAttributes.java index f92f9a60a8..ada7aad212 100644 --- a/java/src/main/java/org/whispersystems/textsecure/internal/push/AccountAttributes.java +++ b/java/src/main/java/org/whispersystems/textsecure/internal/push/AccountAttributes.java @@ -26,9 +26,13 @@ public class AccountAttributes { @JsonProperty private int registrationId; - public AccountAttributes(String signalingKey, int registrationId) { + @JsonProperty + private boolean voice; + + public AccountAttributes(String signalingKey, int registrationId, boolean voice) { this.signalingKey = signalingKey; this.registrationId = registrationId; + this.voice = voice; } public AccountAttributes() {} @@ -41,4 +45,7 @@ public int getRegistrationId() { return registrationId; } + public boolean isVoice() { + return voice; + } } diff --git a/java/src/main/java/org/whispersystems/textsecure/internal/push/ContactTokenDetailsList.java b/java/src/main/java/org/whispersystems/textsecure/internal/push/ContactTokenDetailsList.java index 79fd91a4ed..730b23f530 100644 --- a/java/src/main/java/org/whispersystems/textsecure/internal/push/ContactTokenDetailsList.java +++ b/java/src/main/java/org/whispersystems/textsecure/internal/push/ContactTokenDetailsList.java @@ -16,12 +16,15 @@ */ package org.whispersystems.textsecure.internal.push; +import com.fasterxml.jackson.annotation.JsonProperty; + import org.whispersystems.textsecure.api.push.ContactTokenDetails; import java.util.List; public class ContactTokenDetailsList { + @JsonProperty private List contacts; public ContactTokenDetailsList() {} diff --git a/java/src/main/java/org/whispersystems/textsecure/internal/push/PushServiceSocket.java b/java/src/main/java/org/whispersystems/textsecure/internal/push/PushServiceSocket.java index a6c60efadf..34160d892f 100644 --- a/java/src/main/java/org/whispersystems/textsecure/internal/push/PushServiceSocket.java +++ b/java/src/main/java/org/whispersystems/textsecure/internal/push/PushServiceSocket.java @@ -83,6 +83,7 @@ public class PushServiceSocket { private static final String VERIFY_ACCOUNT_TOKEN_PATH = "/v1/accounts/token/%s"; private static final String REGISTER_GCM_PATH = "/v1/accounts/gcm/"; private static final String REQUEST_TOKEN_PATH = "/v1/accounts/token"; + private static final String SET_ACCOUNT_ATTRIBUTES = "/v1/acccounts/attributes"; private static final String PREKEY_METADATA_PATH = "/v2/keys/"; private static final String PREKEY_PATH = "/v2/keys/%s"; @@ -120,22 +121,27 @@ public void createAccount(boolean voice) throws IOException { makeRequest(String.format(path, credentialsProvider.getUser()), "GET", null); } - public void verifyAccountCode(String verificationCode, String signalingKey, int registrationId) + public void verifyAccountCode(String verificationCode, String signalingKey, int registrationId, boolean voice) throws IOException { - AccountAttributes signalingKeyEntity = new AccountAttributes(signalingKey, registrationId); + AccountAttributes signalingKeyEntity = new AccountAttributes(signalingKey, registrationId, voice); makeRequest(String.format(VERIFY_ACCOUNT_CODE_PATH, verificationCode), "PUT", JsonUtil.toJson(signalingKeyEntity)); } - public void verifyAccountToken(String verificationToken, String signalingKey, int registrationId) + public void verifyAccountToken(String verificationToken, String signalingKey, int registrationId, boolean voice) throws IOException { - AccountAttributes signalingKeyEntity = new AccountAttributes(signalingKey, registrationId); + AccountAttributes signalingKeyEntity = new AccountAttributes(signalingKey, registrationId, voice); makeRequest(String.format(VERIFY_ACCOUNT_TOKEN_PATH, verificationToken), "PUT", JsonUtil.toJson(signalingKeyEntity)); } + public void setAccountAttributes(String signalingKey, int registrationId, boolean voice) throws IOException { + AccountAttributes accountAttributes = new AccountAttributes(signalingKey, registrationId, voice); + makeRequest(SET_ACCOUNT_ATTRIBUTES, "PUT", JsonUtil.toJson(accountAttributes)); + } + public String getAccountVerificationToken() throws IOException { String responseText = makeRequest(REQUEST_TOKEN_PATH, "GET", null); return JsonUtil.fromJson(responseText, AuthorizationToken.class).getToken(); From 4016eb0a3796c743cdec55a6505d555342eba959 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Mon, 21 Sep 2015 17:32:11 -0700 Subject: [PATCH 12/19] Bump version to 1.8.0 // FREEBIE --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7f09eb9a2b..0056133c9b 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ subprojects { - ext.version_number = "1.7.2" + ext.version_number = "1.8.0" ext.group_info = "org.whispersystems" ext.axolotl_version = "1.3.1" From 7ab60d62c3d56f947b9a951556182c1c345d4131 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Wed, 30 Sep 2015 18:07:50 -0700 Subject: [PATCH 13/19] Fix typo in account attributes path // FREEBIE --- .../textsecure/internal/push/PushServiceSocket.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/src/main/java/org/whispersystems/textsecure/internal/push/PushServiceSocket.java b/java/src/main/java/org/whispersystems/textsecure/internal/push/PushServiceSocket.java index 34160d892f..e1e53ef7d6 100644 --- a/java/src/main/java/org/whispersystems/textsecure/internal/push/PushServiceSocket.java +++ b/java/src/main/java/org/whispersystems/textsecure/internal/push/PushServiceSocket.java @@ -83,7 +83,7 @@ public class PushServiceSocket { private static final String VERIFY_ACCOUNT_TOKEN_PATH = "/v1/accounts/token/%s"; private static final String REGISTER_GCM_PATH = "/v1/accounts/gcm/"; private static final String REQUEST_TOKEN_PATH = "/v1/accounts/token"; - private static final String SET_ACCOUNT_ATTRIBUTES = "/v1/acccounts/attributes"; + private static final String SET_ACCOUNT_ATTRIBUTES = "/v1/accounts/attributes/"; private static final String PREKEY_METADATA_PATH = "/v2/keys/"; private static final String PREKEY_PATH = "/v2/keys/%s"; From 2a103d2fe2f695f24ea9dcdfb4f89c09db7ab9e4 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Wed, 30 Sep 2015 18:08:01 -0700 Subject: [PATCH 14/19] Bump version to 1.8.1 // FREEBIE --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 0056133c9b..3f30fa3c0f 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ subprojects { - ext.version_number = "1.8.0" + ext.version_number = "1.8.1" ext.group_info = "org.whispersystems" ext.axolotl_version = "1.3.1" From a8d6f5974f4a900c7bfc5ab99a0d48928b2a8878 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Mon, 9 Nov 2015 17:38:46 -0800 Subject: [PATCH 15/19] Make phone number validity test work for Faroe islands and elsewhere. // FREEBIE --- android/build.gradle | 5 +++++ java/build.gradle | 7 ++++++- .../textsecure/api/util/PhoneNumberFormatter.java | 5 ++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 7656360c9e..1145f75f22 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -61,6 +61,11 @@ signing { sign configurations.archives } + +def sonatypeRepo = sonatypeRepo != null ? sonatypeRepo : "" +def whisperSonatypeUsername = whisperSonatypeUsername != null ? whisperSonatypeUsername : "" +def whisperSonatypePassword = whisperSonatypePassword != null ? whisperSonatypePassword : "" + uploadArchives { configuration = configurations.archives repositories.mavenDeployer { diff --git a/java/build.gradle b/java/build.gradle index 440fd91fc7..142736eac3 100644 --- a/java/build.gradle +++ b/java/build.gradle @@ -14,7 +14,7 @@ repositories { dependencies { compile 'com.google.protobuf:protobuf-java:2.5.0' - compile 'com.googlecode.libphonenumber:libphonenumber:6.1' + compile 'com.googlecode.libphonenumber:libphonenumber:7.1.0' compile 'com.fasterxml.jackson.core:jackson-databind:2.5.0' compile "org.whispersystems:axolotl-java:${axolotl_version}" @@ -35,6 +35,11 @@ signing { sign configurations.archives } + +def sonatypeRepo = sonatypeRepo != null ? sonatypeRepo : "" +def whisperSonatypeUsername = whisperSonatypeUsername != null ? whisperSonatypeUsername : "" +def whisperSonatypePassword = whisperSonatypePassword != null ? whisperSonatypePassword : "" + uploadArchives { configuration = configurations.archives repositories.mavenDeployer { diff --git a/java/src/main/java/org/whispersystems/textsecure/api/util/PhoneNumberFormatter.java b/java/src/main/java/org/whispersystems/textsecure/api/util/PhoneNumberFormatter.java index 05ecdf145a..75076f44dd 100644 --- a/java/src/main/java/org/whispersystems/textsecure/api/util/PhoneNumberFormatter.java +++ b/java/src/main/java/org/whispersystems/textsecure/api/util/PhoneNumberFormatter.java @@ -36,7 +36,10 @@ public class PhoneNumberFormatter { private static final String TAG = PhoneNumberFormatter.class.getSimpleName(); public static boolean isValidNumber(String number) { - return number.matches("^\\+[0-9]{10,}"); + return number.matches("^\\+[0-9]{10,}") || + number.matches("^\\+298[0-9]{6}") || + number.matches("^\\+240[0-9]{6}") || + number.matches("^\\+689[0-9]{6}"); } private static String impreciseFormatNumber(String number, String localNumber) From 864c1532b8688c5be1f67296605ae3381f4ffed9 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Mon, 9 Nov 2015 17:39:28 -0800 Subject: [PATCH 16/19] Bump version to 1.8.2 // FREEBIE --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 3f30fa3c0f..f567df5f42 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ subprojects { - ext.version_number = "1.8.1" + ext.version_number = "1.8.2" ext.group_info = "org.whispersystems" ext.axolotl_version = "1.3.1" From a63f33686d8d679389f0e23ece5afe54bc9fa7ab Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Mon, 16 Nov 2015 11:10:05 -0800 Subject: [PATCH 17/19] Switch to OkHttp for all server interactions // FREEBIE --- .../internal/push/PushServiceSocket.java | 91 +++++++------------ 1 file changed, 32 insertions(+), 59 deletions(-) diff --git a/java/src/main/java/org/whispersystems/textsecure/internal/push/PushServiceSocket.java b/java/src/main/java/org/whispersystems/textsecure/internal/push/PushServiceSocket.java index e1e53ef7d6..7a76284410 100644 --- a/java/src/main/java/org/whispersystems/textsecure/internal/push/PushServiceSocket.java +++ b/java/src/main/java/org/whispersystems/textsecure/internal/push/PushServiceSocket.java @@ -18,6 +18,11 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; +import com.squareup.okhttp.MediaType; +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.RequestBody; +import com.squareup.okhttp.Response; import org.apache.http.conn.ssl.StrictHostnameVerifier; import org.whispersystems.libaxolotl.IdentityKey; @@ -101,8 +106,6 @@ public class PushServiceSocket { private static final String RECEIPT_PATH = "/v1/receipt/%s/%d"; private static final String ATTACHMENT_PATH = "/v1/attachments/%s"; - private static final boolean ENFORCE_SSL = true; - private final String serviceUrl; private final TrustManager[] trustManagers; private final CredentialsProvider credentialsProvider; @@ -486,50 +489,33 @@ private void uploadAttachment(String method, String url, InputStream data, private String makeRequest(String urlFragment, String method, String body) throws NonSuccessfulResponseCodeException, PushNetworkException { - HttpURLConnection connection = makeBaseRequest(urlFragment, method, body); - - try { - String response = Util.readFully(connection.getInputStream()); - connection.disconnect(); - - return response; - } catch (IOException ioe) { - throw new PushNetworkException(ioe); - } - } + Response response = getConnection(urlFragment, method, body); - private HttpURLConnection makeBaseRequest(String urlFragment, String method, String body) - throws NonSuccessfulResponseCodeException, PushNetworkException - { - HttpURLConnection connection = getConnection(urlFragment, method, body); - int responseCode; - String responseMessage; - String response; + int responseCode; + String responseMessage; + String responseBody; try { - responseCode = connection.getResponseCode(); - responseMessage = connection.getResponseMessage(); + responseCode = response.code(); + responseMessage = response.message(); + responseBody = response.body().string(); } catch (IOException ioe) { throw new PushNetworkException(ioe); } switch (responseCode) { case 413: - connection.disconnect(); throw new RateLimitException("Rate limit exceeded: " + responseCode); case 401: case 403: - connection.disconnect(); throw new AuthorizationFailedException("Authorization failed!"); case 404: - connection.disconnect(); throw new NotFoundException("Not found"); case 409: MismatchedDevices mismatchedDevices; try { - response = Util.readFully(connection.getErrorStream()); - mismatchedDevices = JsonUtil.fromJson(response, MismatchedDevices.class); + mismatchedDevices = JsonUtil.fromJson(responseBody, MismatchedDevices.class); } catch (JsonProcessingException e) { Log.w(TAG, e); throw new NonSuccessfulResponseCodeException("Bad response: " + responseCode + " " + responseMessage); @@ -542,8 +528,7 @@ private HttpURLConnection makeBaseRequest(String urlFragment, String method, Str StaleDevices staleDevices; try { - response = Util.readFully(connection.getErrorStream()); - staleDevices = JsonUtil.fromJson(response, StaleDevices.class); + staleDevices = JsonUtil.fromJson(responseBody, StaleDevices.class); } catch (JsonProcessingException e) { throw new NonSuccessfulResponseCodeException("Bad response: " + responseCode + " " + responseMessage); } catch (IOException e) { @@ -555,8 +540,7 @@ private HttpURLConnection makeBaseRequest(String urlFragment, String method, Str DeviceLimit deviceLimit; try { - response = Util.readFully(connection.getErrorStream()); - deviceLimit = JsonUtil.fromJson(response, DeviceLimit.class); + deviceLimit = JsonUtil.fromJson(responseBody, DeviceLimit.class); } catch (JsonProcessingException e) { throw new NonSuccessfulResponseCodeException("Bad response: " + responseCode + " " + responseMessage); } catch (IOException e) { @@ -573,52 +557,41 @@ private HttpURLConnection makeBaseRequest(String urlFragment, String method, Str responseMessage); } - return connection; + return responseBody; } - private HttpURLConnection getConnection(String urlFragment, String method, String body) + private Response getConnection(String urlFragment, String method, String body) throws PushNetworkException { try { + Log.w(TAG, "Push service URL: " + serviceUrl); + Log.w(TAG, "Opening URL: " + String.format("%s%s", serviceUrl, urlFragment)); + SSLContext context = SSLContext.getInstance("TLS"); context.init(null, trustManagers, null); - URL url = new URL(String.format("%s%s", serviceUrl, urlFragment)); - Log.w(TAG, "Push service URL: " + serviceUrl); - Log.w(TAG, "Opening URL: " + url); + OkHttpClient okHttpClient = new OkHttpClient(); + okHttpClient.setSslSocketFactory(context.getSocketFactory()); + okHttpClient.setHostnameVerifier(new StrictHostnameVerifier()); - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + Request.Builder request = new Request.Builder(); + request.url(String.format("%s%s", serviceUrl, urlFragment)); - if (ENFORCE_SSL) { - ((HttpsURLConnection) connection).setSSLSocketFactory(context.getSocketFactory()); - ((HttpsURLConnection) connection).setHostnameVerifier(new StrictHostnameVerifier()); + if (body != null) { + request.method(method, RequestBody.create(MediaType.parse("application/json"), body)); + } else { + request.method(method, null); } - connection.setRequestMethod(method); - connection.setRequestProperty("Content-Type", "application/json"); - if (credentialsProvider.getPassword() != null) { - connection.setRequestProperty("Authorization", getAuthorizationHeader()); + request.addHeader("Authorization", getAuthorizationHeader()); } if (userAgent != null) { - connection.setRequestProperty("X-Signal-Agent", userAgent); - } - - if (body != null) { - connection.setDoOutput(true); - } - - connection.connect(); - - if (body != null) { - Log.w(TAG, method + " -- " + body); - OutputStream out = connection.getOutputStream(); - out.write(body.getBytes()); - out.close(); + request.addHeader("X-Signal-Agent", userAgent); } - return connection; + return okHttpClient.newCall(request.build()).execute(); } catch (IOException e) { throw new PushNetworkException(e); } catch (NoSuchAlgorithmException | KeyManagementException e) { From ac21bcfb390fb13eb320f1800bca62cd60fac062 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Mon, 16 Nov 2015 11:11:40 -0800 Subject: [PATCH 18/19] Support numbers from new caledonia // FREEBIE --- .../whispersystems/textsecure/api/util/PhoneNumberFormatter.java | 1 + 1 file changed, 1 insertion(+) diff --git a/java/src/main/java/org/whispersystems/textsecure/api/util/PhoneNumberFormatter.java b/java/src/main/java/org/whispersystems/textsecure/api/util/PhoneNumberFormatter.java index 75076f44dd..ce9fd9d6b6 100644 --- a/java/src/main/java/org/whispersystems/textsecure/api/util/PhoneNumberFormatter.java +++ b/java/src/main/java/org/whispersystems/textsecure/api/util/PhoneNumberFormatter.java @@ -39,6 +39,7 @@ public static boolean isValidNumber(String number) { return number.matches("^\\+[0-9]{10,}") || number.matches("^\\+298[0-9]{6}") || number.matches("^\\+240[0-9]{6}") || + number.matches("^\\+687[0-9]{6}") || number.matches("^\\+689[0-9]{6}"); } From 432ffbaa3a2eeb100ca1fa6470d2243a64e6c913 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Mon, 16 Nov 2015 11:11:58 -0800 Subject: [PATCH 19/19] Bump version to 1.8.3 // FREEBIE --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index f567df5f42..56f5ce6a24 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ subprojects { - ext.version_number = "1.8.2" + ext.version_number = "1.8.3" ext.group_info = "org.whispersystems" ext.axolotl_version = "1.3.1"