From 4c93231c3ccfb662d42c52348b7ad1ad9f3989a4 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Thu, 20 Aug 2015 10:16:45 -0700 Subject: [PATCH] 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 class TextSecureAccountManager { } /** - * 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 class TextSecureAccountManager { * * @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 class TextSecureMessageReceiver { * 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 class TextSecureMessageReceiver { * @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 class AccountAttributes { 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 class PushServiceSocket { 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 @@ public class PushServiceSocket { 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 class OkHttpClientWrapper implements WebSocketListener { this.uri = uri; this.trustStore = trustStore; this.credentialsProvider = credentialsProvider; + this.userAgent = userAgent; this.listener = listener; } @@ -127,7 +130,13 @@ public class OkHttpClientWrapper implements WebSocketListener { 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 class WebSocketConnection implements WebSocketEventListener { 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); } }