From 4438b4ae69166b1c7a5c45e8ca3cef3b2266b4c9 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Wed, 9 Apr 2014 20:49:52 -0700 Subject: [PATCH] Add a TrustManager that blacklists via serial numbers. --- .../textsecure/push/PushServiceSocket.java | 50 ++++++++--- .../util/BlacklistingTrustManager.java | 87 +++++++++++++++++++ 2 files changed, 124 insertions(+), 13 deletions(-) create mode 100644 library/src/org/whispersystems/textsecure/util/BlacklistingTrustManager.java diff --git a/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java b/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java index abae23f56..f9c88e763 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 000000000..e6c6c4b06 --- /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(); + } +}