summaryrefslogtreecommitdiff
path: root/app/src/main/java/se/leap/bitmaskclient/providersetup
diff options
context:
space:
mode:
authorcyBerta <cyberta@riseup.net>2021-06-23 03:27:17 +0200
committercyBerta <cyberta@riseup.net>2021-07-21 22:02:24 +0200
commitfe6a0e47121d17d08c7d913f1db086687a569446 (patch)
tree0b37235a33c490647f6222d5f1cd6072abc34506 /app/src/main/java/se/leap/bitmaskclient/providersetup
parent571c0479f7400e56cfdb27408160d8a816cc8610 (diff)
initial tor-integration to circumvent blocking attempts of the provider api
Diffstat (limited to 'app/src/main/java/se/leap/bitmaskclient/providersetup')
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderAPI.java134
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderAPICommand.java18
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java72
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/OkHttpClientGenerator.java25
4 files changed, 221 insertions, 28 deletions
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderAPI.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderAPI.java
index 23c750a3..dcbe9636 100644
--- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderAPI.java
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderAPI.java
@@ -17,17 +17,36 @@
package se.leap.bitmaskclient.providersetup;
import android.annotation.SuppressLint;
+import android.app.Notification;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.ServiceConnection;
import android.content.SharedPreferences;
+import android.os.Build;
+import android.os.IBinder;
+import android.util.Log;
import androidx.annotation.NonNull;
import androidx.core.app.JobIntentService;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import org.torproject.jni.TorService;
+
+import java.io.Closeable;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import se.leap.bitmaskclient.base.utils.PreferenceHelper;
import se.leap.bitmaskclient.providersetup.connectivity.OkHttpClientGenerator;
+import se.leap.bitmaskclient.tor.TorNotificationManager;
+import se.leap.bitmaskclient.tor.TorStatusObservable;
import static se.leap.bitmaskclient.base.models.Constants.SHARED_PREFERENCES;
+import static se.leap.bitmaskclient.base.utils.ConfigHelper.ensureNotOnMainThread;
+import static se.leap.bitmaskclient.tor.TorNotificationManager.TOR_SERVICE_NOTIFICATION_ID;
+import static se.leap.bitmaskclient.tor.TorStatusObservable.TorStatus.OFF;
+import static se.leap.bitmaskclient.tor.TorStatusObservable.TorStatus.STOPPING;
/**
* Implements HTTP api methods (encapsulated in {{@link ProviderApiManager}})
@@ -49,6 +68,7 @@ public class ProviderAPI extends JobIntentService implements ProviderApiManagerB
final public static String
TAG = ProviderAPI.class.getSimpleName(),
+ STOP_PROXY = "stopProxy",
SET_UP_PROVIDER = "setUpProvider",
UPDATE_PROVIDER_DETAILS = "updateProviderDetails",
DOWNLOAD_GEOIP_JSON = "downloadGeoIpJson",
@@ -85,6 +105,7 @@ public class ProviderAPI extends JobIntentService implements ProviderApiManagerB
INCORRECTLY_DOWNLOADED_GEOIP_JSON = 18;
ProviderApiManager providerApiManager;
+ private volatile TorServiceConnection torServiceConnection;
//TODO: refactor me, please!
//used in insecure flavor only
@@ -116,13 +137,126 @@ public class ProviderAPI extends JobIntentService implements ProviderApiManagerB
}
@Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (torServiceConnection != null) {
+ torServiceConnection.close();
+ torServiceConnection = null;
+ }
+ }
+
+ @Override
public void broadcastEvent(Intent intent) {
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
+
+
+ @Override
+ public int initTorConnection() {
+ initTorServiceConnection(this);
+ if (torServiceConnection != null) {
+ Intent torServiceIntent = new Intent(this, TorService.class);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ Notification notification = TorNotificationManager.buildTorForegroundNotification(getApplicationContext());
+ //noinspection NewApi
+ getApplicationContext().startForegroundService(torServiceIntent);
+ torServiceConnection.torService.startForeground(TOR_SERVICE_NOTIFICATION_ID, notification);
+ } else {
+ getApplicationContext().startService(torServiceIntent);
+ }
+
+ return torServiceConnection.torService.getHttpTunnelPort();
+ }
+
+ return -1;
+ }
+
+ @Override
+ public void stopTorConnection() {
+ if (TorStatusObservable.getStatus() != OFF) {
+ TorStatusObservable.updateState(this, STOPPING.toString());
+ initTorServiceConnection(this);
+ if (torServiceConnection != null) {
+ torServiceConnection.torService.stopSelf();
+ }
+ }
+ }
+
private ProviderApiManager initApiManager() {
SharedPreferences preferences = getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE);
OkHttpClientGenerator clientGenerator = new OkHttpClientGenerator(getResources());
return new ProviderApiManager(preferences, getResources(), clientGenerator, this);
}
+
+ /**
+ * Assigns a new TorServiceConnection to ProviderAPI's member variable torServiceConnection.
+ * Only one thread at a time can create the service connection, that will be shared between threads
+ *
+ * @throws InterruptedException thrown if thread gets interrupted
+ * @throws IllegalStateException thrown if this method was not called from a background thread
+ */
+ private void initTorServiceConnection(Context context) {
+ if (PreferenceHelper.useTor(context)) {
+ try {
+ if (torServiceConnection == null) {
+ Log.d(TAG, "serviceConnection is still null");
+ torServiceConnection = new TorServiceConnection(context);
+ }
+ } catch (InterruptedException | IllegalStateException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public static class TorServiceConnection implements Closeable {
+ private final Context context;
+ private ServiceConnection serviceConnection;
+ private TorService torService;
+
+ TorServiceConnection(Context context) throws InterruptedException, IllegalStateException {
+ this.context = context;
+ ensureNotOnMainThread(context);
+ Log.d(TAG, "initSynchronizedServiceConnection!");
+ initSynchronizedServiceConnection(context);
+ }
+
+ @Override
+ public void close() {
+ context.unbindService(serviceConnection);
+ }
+
+ private void initSynchronizedServiceConnection(final Context context) throws InterruptedException {
+ final BlockingQueue<TorService> blockingQueue = new LinkedBlockingQueue<>(1);
+ this.serviceConnection = new ServiceConnection() {
+ volatile boolean mConnectedAtLeastOnce = false;
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ if (!mConnectedAtLeastOnce) {
+ mConnectedAtLeastOnce = true;
+ try {
+ TorService.LocalBinder binder = (TorService.LocalBinder) service;
+ blockingQueue.put(binder.getService());
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ }
+ };
+ Intent intent = new Intent(context, TorService.class);
+ context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
+ torService = blockingQueue.take();
+ }
+
+ public TorService getService() {
+ return torService;
+ }
+ }
+
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderAPICommand.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderAPICommand.java
index 1408dce8..3cdfcab0 100644
--- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderAPICommand.java
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderAPICommand.java
@@ -13,12 +13,12 @@ import se.leap.bitmaskclient.base.models.Provider;
public class ProviderAPICommand {
private static final String TAG = ProviderAPICommand.class.getSimpleName();
- private Context context;
+ private final Context context;
- private String action;
- private Bundle parameters;
- private ResultReceiver resultReceiver;
- private Provider provider;
+ private final String action;
+ private final Bundle parameters;
+ private final ResultReceiver resultReceiver;
+ private final Provider provider;
private ProviderAPICommand(@NonNull Context context, @NonNull String action, @NonNull Provider provider, ResultReceiver resultReceiver) {
this(context.getApplicationContext(), action, Bundle.EMPTY, provider, resultReceiver);
@@ -64,22 +64,22 @@ public class ProviderAPICommand {
return command;
}
- public static void execute(Context context, String action, @NonNull Provider provider) {
+ public static void execute(Context context, String action, Provider provider) {
ProviderAPICommand command = new ProviderAPICommand(context, action, provider);
command.execute();
}
- public static void execute(Context context, String action, Bundle parameters, @NonNull Provider provider) {
+ public static void execute(Context context, String action, Bundle parameters, Provider provider) {
ProviderAPICommand command = new ProviderAPICommand(context, action, parameters, provider);
command.execute();
}
- public static void execute(Context context, String action, Bundle parameters, @NonNull Provider provider, ResultReceiver resultReceiver) {
+ public static void execute(Context context, String action, Bundle parameters, Provider provider, ResultReceiver resultReceiver) {
ProviderAPICommand command = new ProviderAPICommand(context, action, parameters, provider, resultReceiver);
command.execute();
}
- public static void execute(Context context, String action, @NonNull Provider provider, ResultReceiver resultReceiver) {
+ public static void execute(Context context, String action, Provider provider, ResultReceiver resultReceiver) {
ProviderAPICommand command = new ProviderAPICommand(context, action, provider, resultReceiver);
command.execute();
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java
index c5dc6572..8118c872 100644
--- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java
@@ -48,6 +48,9 @@ import java.security.interfaces.RSAPrivateKey;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
+import java.util.Observer;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLPeerUnverifiedException;
@@ -59,10 +62,13 @@ import se.leap.bitmaskclient.base.models.Constants.CREDENTIAL_ERRORS;
import se.leap.bitmaskclient.base.models.Provider;
import se.leap.bitmaskclient.base.models.ProviderObservable;
import se.leap.bitmaskclient.base.utils.ConfigHelper;
+import se.leap.bitmaskclient.base.utils.PreferenceHelper;
+import se.leap.bitmaskclient.eip.EipStatus;
import se.leap.bitmaskclient.providersetup.connectivity.OkHttpClientGenerator;
import se.leap.bitmaskclient.providersetup.models.LeapSRPSession;
import se.leap.bitmaskclient.providersetup.models.SrpCredentials;
import se.leap.bitmaskclient.providersetup.models.SrpRegistrationData;
+import se.leap.bitmaskclient.tor.TorStatusObservable;
import static se.leap.bitmaskclient.R.string.certificate_error;
import static se.leap.bitmaskclient.R.string.error_io_exception_user_message;
@@ -119,6 +125,7 @@ import static se.leap.bitmaskclient.providersetup.ProviderAPI.PROVIDER_OK;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.RECEIVER_KEY;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.SET_UP_PROVIDER;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.SIGN_UP;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.STOP_PROXY;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.SUCCESSFUL_LOGIN;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.SUCCESSFUL_LOGOUT;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.SUCCESSFUL_SIGNUP;
@@ -128,6 +135,8 @@ import static se.leap.bitmaskclient.providersetup.ProviderAPI.USER_MESSAGE;
import static se.leap.bitmaskclient.providersetup.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.ERROR_CERTIFICATE_PINNING;
import static se.leap.bitmaskclient.providersetup.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.ERROR_CORRUPTED_PROVIDER_JSON;
import static se.leap.bitmaskclient.providersetup.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.ERROR_INVALID_CERTIFICATE;
+import static se.leap.bitmaskclient.tor.TorStatusObservable.TorStatus.ON;
+import static se.leap.bitmaskclient.tor.TorStatusObservable.getProxyPort;
/**
* Implements the logic of the http api calls. The methods of this class needs to be called from
@@ -140,9 +149,11 @@ public abstract class ProviderApiManagerBase {
public interface ProviderApiServiceCallback {
void broadcastEvent(Intent intent);
+ int initTorConnection();
+ void stopTorConnection();
}
- private ProviderApiServiceCallback serviceCallback;
+ private final ProviderApiServiceCallback serviceCallback;
protected SharedPreferences preferences;
protected Resources resources;
@@ -164,17 +175,22 @@ public abstract class ProviderApiManagerBase {
String action = command.getAction();
Bundle parameters = command.getBundleExtra(PARAMETERS);
- Provider provider = command.getParcelableExtra(PROVIDER_KEY);
+ if (action == null) {
+ Log.e(TAG, "Intent without action sent!");
+ return;
+ }
- if (provider == null) {
+ Provider provider = null;
+ if (command.getParcelableExtra(PROVIDER_KEY) != null) {
+ provider = command.getParcelableExtra(PROVIDER_KEY);
+ } else if (!STOP_PROXY.equals(action)) {
//TODO: consider returning error back e.g. NO_PROVIDER
Log.e(TAG, action +" called without provider!");
return;
}
- if (action == null) {
- Log.e(TAG, "Intent without action sent!");
- return;
- }
+
+ // uncomment for testing --v
+ TorStatusObservable.setProxyPort(startTorProxy());
Bundle result = new Bundle();
switch (action) {
@@ -269,9 +285,43 @@ public abstract class ProviderApiManagerBase {
}
ProviderObservable.getInstance().setProviderForDns(null);
}
+ break;
+ case STOP_PROXY:
+ serviceCallback.stopTorConnection();
+ break;
}
}
+ protected int startTorProxy() {
+ int port = -1;
+ if (PreferenceHelper.useTor(preferences) && EipStatus.getInstance().isDisconnected() ) {
+ port = serviceCallback.initTorConnection();
+ if (port != -1) {
+ try {
+ waitForTorCircuits();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ port = -1;
+ }
+ }
+ }
+ return port;
+ }
+
+ private void waitForTorCircuits() throws InterruptedException {
+ if (TorStatusObservable.getStatus() == ON) {
+ return;
+ }
+ CountDownLatch countDownLatch = new CountDownLatch(1);
+ Observer observer = (o, arg) -> {
+ if (TorStatusObservable.getStatus() == ON) {
+ countDownLatch.countDown();
+ }
+ };
+ TorStatusObservable.getInstance().addObserver(observer);
+ countDownLatch.await(90, TimeUnit.SECONDS);
+ }
+
void resetProviderDetails(Provider provider) {
provider.reset();
deleteProviderDetailsFromPreferences(preferences, provider.getDomain());
@@ -342,7 +392,7 @@ public abstract class ProviderApiManagerBase {
private Bundle register(Provider provider, String username, String password) {
JSONObject stepResult = null;
- OkHttpClient okHttpClient = clientGenerator.initSelfSignedCAHttpClient(provider.getCaCert(), stepResult);
+ OkHttpClient okHttpClient = clientGenerator.initSelfSignedCAHttpClient(provider.getCaCert(), getProxyPort(), stepResult);
if (okHttpClient == null) {
return backendErrorNotification(stepResult, username);
}
@@ -401,7 +451,7 @@ public abstract class ProviderApiManagerBase {
String providerApiUrl = provider.getApiUrlWithVersion();
- OkHttpClient okHttpClient = clientGenerator.initSelfSignedCAHttpClient(provider.getCaCert(), stepResult);
+ OkHttpClient okHttpClient = clientGenerator.initSelfSignedCAHttpClient(provider.getCaCert(), getProxyPort(), stepResult);
if (okHttpClient == null) {
return backendErrorNotification(stepResult, username);
}
@@ -681,7 +731,7 @@ public abstract class ProviderApiManagerBase {
JSONObject errorJson = new JSONObject();
String providerUrl = provider.getApiUrlString() + "/provider.json";
- OkHttpClient okHttpClient = clientGenerator.initSelfSignedCAHttpClient(provider.getCaCert(), errorJson);
+ OkHttpClient okHttpClient = clientGenerator.initSelfSignedCAHttpClient(provider.getCaCert(), getProxyPort(), errorJson);
if (okHttpClient == null) {
result.putString(ERRORS, errorJson.toString());
return false;
@@ -950,7 +1000,7 @@ public abstract class ProviderApiManagerBase {
}
private boolean logOut(Provider provider) {
- OkHttpClient okHttpClient = clientGenerator.initSelfSignedCAHttpClient(provider.getCaCert(), new JSONObject());
+ OkHttpClient okHttpClient = clientGenerator.initSelfSignedCAHttpClient(provider.getCaCert(), getProxyPort(), new JSONObject());
if (okHttpClient == null) {
return false;
}
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
index 2077a8b9..ea619263 100644
--- a/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/OkHttpClientGenerator.java
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/OkHttpClientGenerator.java
@@ -18,6 +18,7 @@
package se.leap.bitmaskclient.providersetup.connectivity;
import android.content.res.Resources;
+import android.net.LocalSocketAddress;
import android.os.Build;
import androidx.annotation.NonNull;
@@ -26,6 +27,9 @@ import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
@@ -50,6 +54,7 @@ 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.proxy;
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;
@@ -61,34 +66,35 @@ import static se.leap.bitmaskclient.base.utils.ConfigHelper.getProviderFormatted
public class OkHttpClientGenerator {
Resources resources;
+ private final static String PROXY_HOST = "127.0.0.1";
public OkHttpClientGenerator(/*SharedPreferences preferences,*/ Resources resources) {
this.resources = resources;
}
- public OkHttpClient initCommercialCAHttpClient(JSONObject initError) {
- return initHttpClient(initError, null);
+ public OkHttpClient initCommercialCAHttpClient(JSONObject initError, int proxyPort) {
+ return initHttpClient(initError, null, proxyPort);
}
- public OkHttpClient initSelfSignedCAHttpClient(String caCert, JSONObject initError) {
- return initHttpClient(initError, caCert);
+ public OkHttpClient initSelfSignedCAHttpClient(String caCert, int proxyPort, JSONObject initError) {
+ return initHttpClient(initError, caCert, proxyPort);
}
public OkHttpClient init() {
try {
- return createClient(null);
+ return createClient(null, -1);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
- private OkHttpClient initHttpClient(JSONObject initError, String certificate) {
+ private OkHttpClient initHttpClient(JSONObject initError, String certificate, int proxyPort) {
if (resources == null) {
return null;
}
try {
- return createClient(certificate);
+ return createClient(certificate, proxyPort);
} catch (IllegalArgumentException e) {
e.printStackTrace();
// TODO ca cert is invalid - show better error ?!
@@ -117,7 +123,7 @@ public class OkHttpClientGenerator {
return null;
}
- private OkHttpClient createClient(String certificate) throws Exception {
+ private OkHttpClient createClient(String certificate, int proxyPort) throws Exception {
TLSCompatSocketFactory sslCompatFactory;
ConnectionSpec spec = getConnectionSpec();
OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
@@ -131,6 +137,9 @@ public class OkHttpClientGenerator {
clientBuilder.cookieJar(getCookieJar())
.connectionSpecs(Collections.singletonList(spec));
clientBuilder.dns(new DnsResolver());
+ if (proxyPort != -1) {
+ clientBuilder.proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(PROXY_HOST, proxyPort)));
+ }
return clientBuilder.build();
}