summaryrefslogtreecommitdiff
path: root/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity')
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/DnsResolver.java39
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/OkHttpClientGenerator.java182
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/TLSCompatSocketFactory.java158
3 files changed, 379 insertions, 0 deletions
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/DnsResolver.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/DnsResolver.java
new file mode 100644
index 00000000..44de1e6d
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/DnsResolver.java
@@ -0,0 +1,39 @@
+package se.leap.bitmaskclient.providersetup.connectivity;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.List;
+
+import okhttp3.Dns;
+import se.leap.bitmaskclient.base.models.Provider;
+import se.leap.bitmaskclient.base.models.ProviderObservable;
+import se.leap.bitmaskclient.base.utils.IPAddress;
+
+class DnsResolver implements Dns {
+
+ @Override
+ public List<InetAddress> lookup(@NotNull String hostname) throws UnknownHostException {
+ try {
+ return Dns.SYSTEM.lookup(hostname);
+ } catch (UnknownHostException e) {
+ ProviderObservable observable = ProviderObservable.getInstance();
+ Provider currentProvider;
+ if (observable.getProviderForDns() != null) {
+ currentProvider = observable.getProviderForDns();
+ } else {
+ currentProvider = observable.getCurrentProvider();
+ }
+ String ip = currentProvider.getIpForHostname(hostname);
+ if (!ip.isEmpty()) {
+ ArrayList<InetAddress> addresses = new ArrayList<>();
+ addresses.add(InetAddress.getByAddress(hostname, IPAddress.asBytes(ip)));
+ return addresses;
+ } else {
+ throw new UnknownHostException("Hostname " + hostname + " not found");
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/OkHttpClientGenerator.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/OkHttpClientGenerator.java
new file mode 100644
index 00000000..2077a8b9
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/OkHttpClientGenerator.java
@@ -0,0 +1,182 @@
+/**
+ * Copyright (c) 2018 LEAP Encryption Access Project and contributers
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+package se.leap.bitmaskclient.providersetup.connectivity;
+
+import android.content.res.Resources;
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.net.UnknownHostException;
+import java.security.KeyManagementException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.cert.CertificateException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
+import okhttp3.CipherSuite;
+import okhttp3.ConnectionSpec;
+import okhttp3.Cookie;
+import okhttp3.CookieJar;
+import okhttp3.HttpUrl;
+import okhttp3.OkHttpClient;
+import okhttp3.TlsVersion;
+
+import static android.text.TextUtils.isEmpty;
+import static se.leap.bitmaskclient.R.string.certificate_error;
+import static se.leap.bitmaskclient.R.string.error_io_exception_user_message;
+import static se.leap.bitmaskclient.R.string.error_no_such_algorithm_exception_user_message;
+import static se.leap.bitmaskclient.R.string.keyChainAccessError;
+import static se.leap.bitmaskclient.R.string.server_unreachable_message;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORS;
+import static se.leap.bitmaskclient.base.utils.ConfigHelper.getProviderFormattedString;
+
+/**
+ * Created by cyberta on 08.01.18.
+ */
+
+public class OkHttpClientGenerator {
+
+ Resources resources;
+
+ public OkHttpClientGenerator(/*SharedPreferences preferences,*/ Resources resources) {
+ this.resources = resources;
+ }
+
+ public OkHttpClient initCommercialCAHttpClient(JSONObject initError) {
+ return initHttpClient(initError, null);
+ }
+
+ public OkHttpClient initSelfSignedCAHttpClient(String caCert, JSONObject initError) {
+ return initHttpClient(initError, caCert);
+ }
+
+ public OkHttpClient init() {
+ try {
+ return createClient(null);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ private OkHttpClient initHttpClient(JSONObject initError, String certificate) {
+ if (resources == null) {
+ return null;
+ }
+ try {
+ return createClient(certificate);
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ // TODO ca cert is invalid - show better error ?!
+ addErrorMessageToJson(initError, getProviderFormattedString(resources, certificate_error));
+ } catch (IllegalStateException | KeyManagementException | KeyStoreException e) {
+ e.printStackTrace();
+ addErrorMessageToJson(initError, String.format(resources.getString(keyChainAccessError), e.getLocalizedMessage()));
+ } catch (NoSuchAlgorithmException | NoSuchProviderException e) {
+ e.printStackTrace();
+ addErrorMessageToJson(initError, resources.getString(error_no_such_algorithm_exception_user_message));
+ } catch (CertificateException e) {
+ e.printStackTrace();
+ // TODO ca cert is invalid - show better error ?!
+ addErrorMessageToJson(initError, getProviderFormattedString(resources, certificate_error));
+ } catch (UnknownHostException e) {
+ e.printStackTrace();
+ addErrorMessageToJson(initError, resources.getString(server_unreachable_message));
+ } catch (IOException e) {
+ e.printStackTrace();
+ addErrorMessageToJson(initError, resources.getString(error_io_exception_user_message));
+ } catch (Exception e) {
+ e.printStackTrace();
+ // unexpected exception, should never happen
+ // only to shorten the method signature createClient(String certificate)
+ }
+ return null;
+ }
+
+ private OkHttpClient createClient(String certificate) throws Exception {
+ TLSCompatSocketFactory sslCompatFactory;
+ ConnectionSpec spec = getConnectionSpec();
+ OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
+
+ if (!isEmpty(certificate)) {
+ sslCompatFactory = new TLSCompatSocketFactory(certificate);
+ } else {
+ sslCompatFactory = new TLSCompatSocketFactory();
+ }
+ sslCompatFactory.initSSLSocketFactory(clientBuilder);
+ clientBuilder.cookieJar(getCookieJar())
+ .connectionSpecs(Collections.singletonList(spec));
+ clientBuilder.dns(new DnsResolver());
+ return clientBuilder.build();
+ }
+
+
+
+ @NonNull
+ private ConnectionSpec getConnectionSpec() {
+ ConnectionSpec.Builder connectionSpecbuilder = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
+ .tlsVersions(TlsVersion.TLS_1_2, TlsVersion.TLS_1_3);
+ //FIXME: restrict connection further to the following recommended cipher suites for ALL supported API levels
+ //figure out how to use bcjsse for that purpose
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1)
+ connectionSpecbuilder.cipherSuites(
+ CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+ CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+ CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
+ CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+ CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
+ CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
+ );
+ return connectionSpecbuilder.build();
+ }
+
+ @NonNull
+ private CookieJar getCookieJar() {
+ return new CookieJar() {
+ private final HashMap<String, List<Cookie>> cookieStore = new HashMap<>();
+
+ @Override
+ public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
+ cookieStore.put(url.host(), cookies);
+ }
+
+ @Override
+ public List<Cookie> loadForRequest(HttpUrl url) {
+ List<Cookie> cookies = cookieStore.get(url.host());
+ return cookies != null ? cookies : new ArrayList<Cookie>();
+ }
+ };
+ }
+
+ private void addErrorMessageToJson(JSONObject jsonObject, String errorMessage) {
+ try {
+ jsonObject.put(ERRORS, errorMessage);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/TLSCompatSocketFactory.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/TLSCompatSocketFactory.java
new file mode 100644
index 00000000..5357fd74
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/TLSCompatSocketFactory.java
@@ -0,0 +1,158 @@
+package se.leap.bitmaskclient.providersetup.connectivity;
+
+import android.text.TextUtils;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.cert.CertificateException;
+import java.util.Arrays;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+
+import okhttp3.OkHttpClient;
+import se.leap.bitmaskclient.base.utils.ConfigHelper;
+
+/**
+ * Created by cyberta on 24.10.17.
+ * This class ensures that modern TLS algorithms will also be used on old devices (Android 4.1 - Android 4.4.4) in order to avoid
+ * attacks like POODLE.
+ */
+
+public class TLSCompatSocketFactory extends SSLSocketFactory {
+
+ private static final String TAG = TLSCompatSocketFactory.class.getName();
+ private SSLSocketFactory internalSSLSocketFactory;
+ private TrustManager trustManager;
+
+ public TLSCompatSocketFactory(String trustedCaCert) throws KeyManagementException, NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException, NoSuchProviderException {
+ initForSelfSignedCAs(trustedCaCert);
+ }
+
+ public TLSCompatSocketFactory() throws CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException, NoSuchProviderException, IOException {
+ initForCommercialCAs();
+ }
+
+ public void initSSLSocketFactory(OkHttpClient.Builder builder) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, KeyManagementException, IllegalStateException {
+ builder.sslSocketFactory(this, (X509TrustManager) trustManager);
+ }
+
+
+ private void initForSelfSignedCAs(String trustedSelfSignedCaCert) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException, IllegalStateException, KeyManagementException, NoSuchProviderException {
+ // Create a KeyStore containing our trusted CAs
+ String defaultType = KeyStore.getDefaultType();
+ KeyStore keyStore = KeyStore.getInstance(defaultType);
+ keyStore.load(null, null);
+ if (!TextUtils.isEmpty(trustedSelfSignedCaCert)) {
+ java.security.cert.Certificate provider_certificate = ConfigHelper.parseX509CertificateFromString(trustedSelfSignedCaCert);
+ keyStore.setCertificateEntry("provider_ca_certificate", provider_certificate);
+ }
+
+ // Create a TrustManager that trusts the CAs in our KeyStore
+ String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
+ tmf.init(keyStore);
+
+ // Check if there's only 1 X509Trustmanager -> from okttp3 source code example
+ TrustManager[] trustManagers = tmf.getTrustManagers();
+ if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
+ throw new IllegalStateException("Unexpected default trust managers:"
+ + Arrays.toString(trustManagers));
+ }
+
+ trustManager = trustManagers[0];
+
+ // Create a SSLContext that uses our TrustManager
+ SSLContext sslContext = SSLContext.getInstance("TLS");
+ sslContext.init(null, tmf.getTrustManagers(), null);
+ internalSSLSocketFactory = sslContext.getSocketFactory();
+
+ }
+
+
+ private void initForCommercialCAs() throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException {
+
+ // Create a TrustManager that trusts the CAs in our KeyStore
+ String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
+ tmf.init((KeyStore) null);
+
+ // Check if there's only 1 X509Trustmanager -> from okttp3 source code example
+ TrustManager[] trustManagers = tmf.getTrustManagers();
+ if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
+ throw new IllegalStateException("Unexpected default trust managers:"
+ + Arrays.toString(trustManagers));
+ }
+
+ trustManager = trustManagers[0];
+
+ SSLContext context = SSLContext.getInstance("TLS");
+ context.init(null, null, null);
+ internalSSLSocketFactory = context.getSocketFactory();
+ }
+
+
+ @Override
+ public String[] getDefaultCipherSuites() {
+ return internalSSLSocketFactory.getDefaultCipherSuites();
+ }
+
+ @Override
+ public String[] getSupportedCipherSuites() {
+ return internalSSLSocketFactory.getSupportedCipherSuites();
+ }
+
+ @Override
+ public Socket createSocket() throws IOException, IllegalArgumentException {
+ return enableTLSOnSocket(internalSSLSocketFactory.createSocket());
+ }
+
+ @Override
+ public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException, IllegalArgumentException {
+ return enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose));
+ }
+
+ @Override
+ public Socket createSocket(String host, int port) throws IOException, UnknownHostException, IllegalArgumentException {
+ return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
+ }
+
+ @Override
+ public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException, IllegalArgumentException {
+ return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort));
+ }
+
+ @Override
+ public Socket createSocket(InetAddress host, int port) throws IOException, IllegalArgumentException {
+ return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
+ }
+
+ @Override
+ public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException, IllegalArgumentException {
+ return enableTLSOnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort));
+ }
+
+ private Socket enableTLSOnSocket(Socket socket) throws IllegalArgumentException {
+ if(socket != null && (socket instanceof SSLSocket)) {
+ ((SSLSocket)socket).setEnabledProtocols(new String[] {"TLSv1.2"});
+ //TODO: add a android version check as soon as a new Android API or bcjsse supports TLSv1.3
+ }
+ return socket;
+
+
+ }
+
+
+
+}