From 43fc72d2f1245a6ec44bdf88d421ea5dac07436d Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Thu, 29 Dec 2016 20:28:11 -0800 Subject: [PATCH] Support for multiple service URLs // FREEBIE --- .../api/SignalServiceAccountManager.java | 7 +- .../api/SignalServiceMessageReceiver.java | 26 +++---- .../api/SignalServiceMessageSender.java | 7 +- .../internal/push/PushServiceSocket.java | 78 ++++++++++++++----- .../internal/push/SignalServiceUrl.java | 15 +++- .../util/BlacklistingTrustManager.java | 11 ++- 6 files changed, 93 insertions(+), 51 deletions(-) diff --git a/java/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java b/java/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java index 3126f16d0..d5a64ff36 100644 --- a/java/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java +++ b/java/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java @@ -53,17 +53,16 @@ public class SignalServiceAccountManager { /** * Construct a SignalServiceAccountManager. * - * @param url The URL for the Signal Service. - * @param trustStore The {@link org.whispersystems.signalservice.api.push.TrustStore} for the SignalService server's TLS certificate. + * @param urls The URL for the Signal Service. * @param user A Signal Service phone number. * @param password A Signal Service password. * @param userAgent A string which identifies the client software. */ - public SignalServiceAccountManager(SignalServiceUrl url, TrustStore trustStore, + public SignalServiceAccountManager(SignalServiceUrl[] urls, String user, String password, String userAgent) { - this.pushServiceSocket = new PushServiceSocket(url, trustStore, new StaticCredentialsProvider(user, password, null), userAgent); + this.pushServiceSocket = new PushServiceSocket(urls, new StaticCredentialsProvider(user, password, null), userAgent); this.user = user; this.userAgent = userAgent; } diff --git a/java/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageReceiver.java b/java/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageReceiver.java index 4c71ec8cd..df0a311a0 100644 --- a/java/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageReceiver.java +++ b/java/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageReceiver.java @@ -12,7 +12,6 @@ import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; -import org.whispersystems.signalservice.api.push.TrustStore; import org.whispersystems.signalservice.api.util.CredentialsProvider; import org.whispersystems.signalservice.internal.push.PushServiceSocket; import org.whispersystems.signalservice.internal.push.SignalServiceEnvelopeEntity; @@ -34,43 +33,36 @@ public class SignalServiceMessageReceiver { private final PushServiceSocket socket; - private final TrustStore trustStore; - private final SignalServiceUrl url; + private final SignalServiceUrl[] urls; private final CredentialsProvider credentialsProvider; private final String userAgent; /** * Construct a SignalServiceMessageReceiver. * - * @param url The URL of the Signal Service. - * @param trustStore The {@link org.whispersystems.signalservice.api.push.TrustStore} containing - * the server's TLS signing certificate. + * @param urls The URL of the Signal Service. * @param user The Signal Service username (eg. phone number). * @param password The Signal Service user password. * @param signalingKey The 52 byte signaling key assigned to this user at registration. */ - public SignalServiceMessageReceiver(SignalServiceUrl url, TrustStore trustStore, + public SignalServiceMessageReceiver(SignalServiceUrl[] urls, String user, String password, String signalingKey, String userAgent) { - this(url, trustStore, new StaticCredentialsProvider(user, password, signalingKey), userAgent); + this(urls, new StaticCredentialsProvider(user, password, signalingKey), userAgent); } /** * Construct a SignalServiceMessageReceiver. * - * @param url The URL of the Signal Service. - * @param trustStore The {@link org.whispersystems.signalservice.api.push.TrustStore} containing - * the server's TLS signing certificate. + * @param urls The URL of the Signal Service. * @param credentials The Signal Service user's credentials. */ - public SignalServiceMessageReceiver(SignalServiceUrl url, TrustStore trustStore, - CredentialsProvider credentials, String userAgent) + public SignalServiceMessageReceiver(SignalServiceUrl[] urls, CredentialsProvider credentials, String userAgent) { - this.url = url; - this.trustStore = trustStore; + this.urls = urls; this.credentialsProvider = credentials; - this.socket = new PushServiceSocket(url, trustStore, credentials, userAgent); + this.socket = new PushServiceSocket(urls, credentials, userAgent); this.userAgent = userAgent; } @@ -119,7 +111,7 @@ public InputStream retrieveAttachment(SignalServiceAttachmentPointer pointer, Fi * @return A SignalServiceMessagePipe for receiving Signal Service messages. */ public SignalServiceMessagePipe createMessagePipe() { - WebSocketConnection webSocket = new WebSocketConnection(url.getUrl(), trustStore, credentialsProvider, userAgent); + WebSocketConnection webSocket = new WebSocketConnection(urls[0].getUrl(), urls[0].getTrustStore(), credentialsProvider, userAgent); return new SignalServiceMessagePipe(webSocket, credentialsProvider); } diff --git a/java/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java b/java/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java index 3cde45d6f..75a76749d 100644 --- a/java/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java +++ b/java/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java @@ -69,21 +69,20 @@ public class SignalServiceMessageSender { /** * Construct a SignalServiceMessageSender. * - * @param url The URL of the Signal Service. - * @param trustStore The trust store containing the Signal Service's signing TLS certificate. + * @param urls The URL of the Signal Service. * @param user The Signal Service username (eg phone number). * @param password The Signal Service user password. * @param store The SignalProtocolStore. * @param eventListener An optional event listener, which fires whenever sessions are * setup or torn down for a recipient. */ - public SignalServiceMessageSender(SignalServiceUrl url, TrustStore trustStore, + public SignalServiceMessageSender(SignalServiceUrl[] urls, String user, String password, SignalProtocolStore store, String userAgent, Optional eventListener) { - this.socket = new PushServiceSocket(url, trustStore, new StaticCredentialsProvider(user, password, null), userAgent); + this.socket = new PushServiceSocket(urls, new StaticCredentialsProvider(user, password, null), userAgent); this.store = store; this.localAddress = new SignalServiceAddress(user); this.eventListener = eventListener; diff --git a/java/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java b/java/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java index ebbc88176..77874c12d 100644 --- a/java/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java +++ b/java/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java @@ -27,9 +27,8 @@ import org.whispersystems.signalservice.api.messages.SignalServiceAttachment.ProgressListener; import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo; import org.whispersystems.signalservice.api.push.ContactTokenDetails; -import org.whispersystems.signalservice.api.push.SignedPreKeyEntity; import org.whispersystems.signalservice.api.push.SignalServiceAddress; -import org.whispersystems.signalservice.api.push.TrustStore; +import org.whispersystems.signalservice.api.push.SignedPreKeyEntity; import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException; import org.whispersystems.signalservice.api.push.exceptions.ExpectationFailedException; import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; @@ -55,6 +54,7 @@ import java.net.URL; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; import java.util.LinkedList; import java.util.List; import java.util.Set; @@ -94,17 +94,24 @@ public class PushServiceSocket { private static final String RECEIPT_PATH = "/v1/receipt/%s/%d"; private static final String ATTACHMENT_PATH = "/v1/attachments/%s"; - private final SignalServiceUrl serviceUrl; - private final TrustManager[] trustManagers; - private final CredentialsProvider credentialsProvider; - private final String userAgent; + private final SignalConnectionInformation[] signalConnectionInformation; + private final CredentialsProvider credentialsProvider; + private final String userAgent; + private final SecureRandom random; - public PushServiceSocket(SignalServiceUrl serviceUrl, TrustStore trustStore, CredentialsProvider credentialsProvider, String userAgent) - { - this.serviceUrl = serviceUrl; - this.credentialsProvider = credentialsProvider; - this.trustManagers = BlacklistingTrustManager.createFor(trustStore); - this.userAgent = userAgent; + public PushServiceSocket(SignalServiceUrl[] serviceUrls, CredentialsProvider credentialsProvider, String userAgent) { + try { + this.credentialsProvider = credentialsProvider; + this.userAgent = userAgent; + this.signalConnectionInformation = new SignalConnectionInformation[serviceUrls.length]; + this.random = SecureRandom.getInstance("SHA1PRNG"); + + for (int i = 0; i < serviceUrls.length; i++) { + signalConnectionInformation[i] = new SignalConnectionInformation(serviceUrls[i]); + } + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(e); + } } public void createAccount(boolean voice) throws IOException { @@ -552,8 +559,13 @@ private Response getConnection(String urlFragment, String method, String body) throws PushNetworkException { try { - Log.w(TAG, "Push service URL: " + serviceUrl.getUrl()); - Log.w(TAG, "Opening URL: " + String.format("%s%s", serviceUrl.getUrl(), urlFragment)); + SignalConnectionInformation connectionInformation = getRandom(signalConnectionInformation, random); + String url = connectionInformation.getUrl(); + Optional hostHeader = connectionInformation.getHostHeader(); + TrustManager[] trustManagers = connectionInformation.getTrustManagers(); + + Log.w(TAG, "Push service URL: " + url); + Log.w(TAG, "Opening URL: " + String.format("%s%s", url, urlFragment)); SSLContext context = SSLContext.getInstance("TLS"); context.init(null, trustManagers, null); @@ -563,7 +575,7 @@ private Response getConnection(String urlFragment, String method, String body) okHttpClient.setHostnameVerifier(new StrictHostnameVerifier()); Request.Builder request = new Request.Builder(); - request.url(String.format("%s%s", serviceUrl.getUrl(), urlFragment)); + request.url(String.format("%s%s", url, urlFragment)); if (body != null) { request.method(method, RequestBody.create(MediaType.parse("application/json"), body)); @@ -579,11 +591,10 @@ private Response getConnection(String urlFragment, String method, String body) request.addHeader("X-Signal-Agent", userAgent); } - if (serviceUrl.getHostHeader().isPresent()) { - okHttpClient.networkInterceptors().add(new HostInterceptor(serviceUrl.getHostHeader().get())); + if (hostHeader.isPresent()) { + okHttpClient.networkInterceptors().add(new HostInterceptor(hostHeader.get())); } - return okHttpClient.newCall(request.build()).execute(); } catch (IOException e) { throw new PushNetworkException(e); @@ -600,6 +611,12 @@ private String getAuthorizationHeader() { } } + private SignalConnectionInformation getRandom(SignalConnectionInformation[] connections, + SecureRandom random) + { + return connections[random.nextInt(connections.length)]; + } + private static class GcmRegistrationId { @JsonProperty @@ -646,4 +663,29 @@ public Response intercept(Chain chain) throws IOException { return chain.proceed(request.newBuilder().header("Host", host).build()); } } + + private static class SignalConnectionInformation { + + private final String url; + private final Optional hostHeader; + private final TrustManager[] trustManagers; + + private SignalConnectionInformation(SignalServiceUrl signalServiceUrl) { + this.url = signalServiceUrl.getUrl(); + this.hostHeader = signalServiceUrl.getHostHeader(); + this.trustManagers = BlacklistingTrustManager.createFor(signalServiceUrl.getTrustStore()); + } + + String getUrl() { + return url; + } + + Optional getHostHeader() { + return hostHeader; + } + + TrustManager[] getTrustManagers() { + return trustManagers; + } + } } diff --git a/java/src/main/java/org/whispersystems/signalservice/internal/push/SignalServiceUrl.java b/java/src/main/java/org/whispersystems/signalservice/internal/push/SignalServiceUrl.java index 3a8e59910..0b1414155 100644 --- a/java/src/main/java/org/whispersystems/signalservice/internal/push/SignalServiceUrl.java +++ b/java/src/main/java/org/whispersystems/signalservice/internal/push/SignalServiceUrl.java @@ -2,19 +2,22 @@ import org.whispersystems.libsignal.util.guava.Optional; +import org.whispersystems.signalservice.api.push.TrustStore; public class SignalServiceUrl { - private final Optional hostHeader; private final String url; + private final Optional hostHeader; + private TrustStore trustStore; - public SignalServiceUrl(String url) { - this(url, null); + public SignalServiceUrl(String url, TrustStore trustStore) { + this(url, null, trustStore); } - public SignalServiceUrl(String url, String hostHeader) { + public SignalServiceUrl(String url, String hostHeader, TrustStore trustStore) { this.url = url; this.hostHeader = Optional.fromNullable(hostHeader); + this.trustStore = trustStore; } @@ -25,4 +28,8 @@ public Optional getHostHeader() { public String getUrl() { return url; } + + public TrustStore getTrustStore() { + return trustStore; + } } diff --git a/java/src/main/java/org/whispersystems/signalservice/internal/util/BlacklistingTrustManager.java b/java/src/main/java/org/whispersystems/signalservice/internal/util/BlacklistingTrustManager.java index e64c357d0..98343cf85 100644 --- a/java/src/main/java/org/whispersystems/signalservice/internal/util/BlacklistingTrustManager.java +++ b/java/src/main/java/org/whispersystems/signalservice/internal/util/BlacklistingTrustManager.java @@ -6,6 +6,7 @@ package org.whispersystems.signalservice.internal.util; +import org.whispersystems.libsignal.util.Pair; import org.whispersystems.signalservice.api.push.TrustStore; import java.io.IOException; @@ -32,8 +33,8 @@ */ public class BlacklistingTrustManager implements X509TrustManager { - private static final List BLACKLIST = new LinkedList() {{ - add(new BigInteger("4098")); + private static final List> BLACKLIST = new LinkedList>() {{ + add(new Pair<>("Open Whisper Systems", new BigInteger("4098"))); }}; public static TrustManager[] createFor(TrustManager[] trustManagers) { @@ -85,8 +86,10 @@ public void checkServerTrusted(X509Certificate[] chain, String authType) trustManager.checkServerTrusted(chain, authType); for (X509Certificate certificate : chain) { - for (BigInteger blacklistedSerial : BLACKLIST) { - if (certificate.getSerialNumber().equals(blacklistedSerial)) { + for (Pair blacklistedSerial : BLACKLIST) { + if (certificate.getIssuerDN().getName().equals(blacklistedSerial.first()) && + certificate.getSerialNumber().equals(blacklistedSerial.second())) + { throw new CertificateException("Blacklisted Serial: " + certificate.getSerialNumber()); } }