diff --git a/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java b/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java
index abae23f565..f9c88e7630 100644
--- a/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java
+++ b/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java
@@ -1,3 +1,19 @@
+/**
+ * Copyright (C) 2014 Open Whisper Systems
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
package org.whispersystems.textsecure.push;
import android.content.Context;
@@ -10,6 +26,7 @@ import org.apache.http.conn.ssl.StrictHostnameVerifier;
import org.whispersystems.textsecure.crypto.IdentityKey;
import org.whispersystems.textsecure.storage.PreKeyRecord;
import org.whispersystems.textsecure.util.Base64;
+import org.whispersystems.textsecure.util.BlacklistingTrustManager;
import org.whispersystems.textsecure.util.Util;
import java.io.File;
@@ -32,8 +49,15 @@ import java.util.Set;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
+/**
+ *
+ * Network interface to the TextSecure server API.
+ *
+ * @author Moxie Marlinspike
+ */
public class PushServiceSocket {
private static final String CREATE_ACCOUNT_SMS_PATH = "/v1/accounts/sms/code/%s";
@@ -51,20 +75,20 @@ public class PushServiceSocket {
private static final boolean ENFORCE_SSL = true;
- private final Context context;
- private final String serviceUrl;
- private final String localNumber;
- private final String password;
- private final TrustManagerFactory trustManagerFactory;
+ private final Context context;
+ private final String serviceUrl;
+ private final String localNumber;
+ private final String password;
+ private final TrustManager[] trustManagers;
public PushServiceSocket(Context context, String serviceUrl, TrustStore trustStore,
String localNumber, String password)
{
- this.context = context.getApplicationContext();
- this.serviceUrl = serviceUrl;
- this.localNumber = localNumber;
- this.password = password;
- this.trustManagerFactory = initializeTrustManagerFactory(trustStore);
+ this.context = context.getApplicationContext();
+ this.serviceUrl = serviceUrl;
+ this.localNumber = localNumber;
+ this.password = password;
+ this.trustManagers = initializeTrustManager(trustStore);
}
public void createAccount(boolean voice) throws IOException {
@@ -356,7 +380,7 @@ public class PushServiceSocket {
private HttpURLConnection getConnection(String urlFragment, String method) throws IOException {
try {
SSLContext context = SSLContext.getInstance("TLS");
- context.init(null, trustManagerFactory.getTrustManagers(), null);
+ context.init(null, trustManagers, null);
URL url = new URL(String.format("%s%s", serviceUrl, urlFragment));
Log.w("PushServiceSocket", "Push service URL: " + serviceUrl);
@@ -394,7 +418,7 @@ public class PushServiceSocket {
}
}
- private TrustManagerFactory initializeTrustManagerFactory(TrustStore trustStore) {
+ private TrustManager[] initializeTrustManager(TrustStore trustStore) {
try {
InputStream keyStoreInputStream = trustStore.getKeyStoreInputStream();
KeyStore keyStore = KeyStore.getInstance("BKS");
@@ -404,7 +428,7 @@ public class PushServiceSocket {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509");
trustManagerFactory.init(keyStore);
- return trustManagerFactory;
+ return BlacklistingTrustManager.createFor(trustManagerFactory.getTrustManagers());
} catch (KeyStoreException kse) {
throw new AssertionError(kse);
} catch (CertificateException e) {
diff --git a/library/src/org/whispersystems/textsecure/util/BlacklistingTrustManager.java b/library/src/org/whispersystems/textsecure/util/BlacklistingTrustManager.java
new file mode 100644
index 0000000000..e6c6c4b069
--- /dev/null
+++ b/library/src/org/whispersystems/textsecure/util/BlacklistingTrustManager.java
@@ -0,0 +1,87 @@
+/**
+ * Copyright (C) 2014 Open Whisper Systems
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.whispersystems.textsecure.util;
+
+import java.math.BigInteger;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+/**
+ * Trust manager that defers to a system X509 trust manager, and
+ * additionally rejects certificates if they have a blacklisted
+ * serial.
+ *
+ * @author Moxie Marlinspike
+ */
+public class BlacklistingTrustManager implements X509TrustManager {
+
+ private static final List BLACKLIST = new LinkedList() {{
+ add(new BigInteger("4098"));
+ }};
+
+ public static TrustManager[] createFor(TrustManager[] trustManagers) {
+ for (TrustManager trustManager : trustManagers) {
+ if (trustManager instanceof X509TrustManager) {
+ TrustManager[] results = new BlacklistingTrustManager[1];
+ results[0] = new BlacklistingTrustManager((X509TrustManager)trustManager);
+
+ return results;
+ }
+ }
+
+ throw new AssertionError("No X509 Trust Managers!");
+ }
+
+ private final X509TrustManager trustManager;
+
+ public BlacklistingTrustManager(X509TrustManager trustManager) {
+ this.trustManager = trustManager;
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType)
+ throws CertificateException
+ {
+ trustManager.checkClientTrusted(chain, authType);
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType)
+ throws CertificateException
+ {
+ trustManager.checkServerTrusted(chain, authType);
+
+ for (X509Certificate certificate : chain) {
+ for (BigInteger blacklistedSerial : BLACKLIST) {
+ if (certificate.getSerialNumber().equals(blacklistedSerial)) {
+ throw new CertificateException("Blacklisted Serial: " + certificate.getSerialNumber());
+ }
+ }
+ }
+
+ }
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ return trustManager.getAcceptedIssuers();
+ }
+}