diff options
author | cyberta <cyberta@riseup.net> | 2021-02-28 21:29:39 +0000 |
---|---|---|
committer | cyberta <cyberta@riseup.net> | 2021-02-28 21:29:39 +0000 |
commit | 665b965194cf24d01b02789adf286950da2e91b2 (patch) | |
tree | b31c4d03d8c2511371184d2fe059a94fb1fd2cae | |
parent | 8bb73089566e78a195f21f4746c9db416abf95a4 (diff) | |
parent | 401e28431fcd3a093aa34d429f5bc08abca5d422 (diff) |
Merge branch 'beta-release' into 'master'
Beta release
See merge request leap/bitmask_android!131
33 files changed, 1369 insertions, 441 deletions
diff --git a/app/build.gradle b/app/build.gradle index 5b16f8e9..87726734 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -216,6 +216,7 @@ android { //runProguard true if(signingConfigs.contains(release)) signingConfig signingConfigs.release.isSigningReady() ? signingConfigs.release : signingConfigs.debug + //ndk.debugSymbolLevel = "full" //minifyEnabled = true //shrinkResources true buildConfigField "Boolean", "DEBUG_MODE", "false" @@ -426,7 +427,7 @@ android.applicationVariants.all { variant -> } // remove unrelated abi specific assets - variant.mergeAssets.doLast { + variant.mergeAssetsProvider.get().doLast { // if not a fat build if (abiDimension.abiVersionCode > 0) { def filesToDelete = fileTree(dir: variant.mergeAssets.outputDir, excludes: ["*pie_openvpn.${abiDimension.abiFilter}", diff --git a/app/src/insecure/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java b/app/src/insecure/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java index 833d1e48..dfb5346b 100644 --- a/app/src/insecure/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java +++ b/app/src/insecure/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java @@ -204,7 +204,7 @@ public class ProviderApiManager extends ProviderApiManagerBase { result.putBoolean(BROADCAST_RESULT_KEY, true); } } catch (NullPointerException | JSONException e) { - setErrorResult(result, eipServiceJsonString); + setErrorResult(result, R.string.error_json_exception_user_message, null); } //TODO: check why the following line is not in production result.putParcelable(PROVIDER_KEY, provider); diff --git a/app/src/main/aidl/de/blinkt/openvpn/core/IOpenVPNServiceInternal.aidl b/app/src/main/aidl/de/blinkt/openvpn/core/IOpenVPNServiceInternal.aidl index 293c2b6d..98cc3e42 100644 --- a/app/src/main/aidl/de/blinkt/openvpn/core/IOpenVPNServiceInternal.aidl +++ b/app/src/main/aidl/de/blinkt/openvpn/core/IOpenVPNServiceInternal.aidl @@ -23,4 +23,6 @@ interface IOpenVPNServiceInternal { boolean isVpnRunning(); + void startWithForegroundNotification(); + } diff --git a/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java b/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java index b148d04d..82eb3ca9 100644 --- a/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java +++ b/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java @@ -5,29 +5,25 @@ package de.blinkt.openvpn; -import android.annotation.TargetApi; import android.app.Activity; -import android.app.AlertDialog; import android.content.ActivityNotFoundException; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; import android.content.Intent; -import android.content.SharedPreferences; import android.net.VpnService; import android.os.Build; import android.os.Bundle; -import java.io.IOException; +import androidx.annotation.StringRes; import de.blinkt.openvpn.core.ConnectionStatus; -import de.blinkt.openvpn.core.Preferences; -import de.blinkt.openvpn.core.VPNLaunchHelper; import de.blinkt.openvpn.core.VpnStatus; -import se.leap.bitmaskclient.base.MainActivity; import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.eip.EipCommand; +import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_PREPARE_VPN; +import static se.leap.bitmaskclient.base.models.Constants.EIP_N_CLOSEST_GATEWAY; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PROFILE; +import static se.leap.bitmaskclient.eip.EIP.ERRORS; import static se.leap.bitmaskclient.eip.EipResultBroadcast.tellToReceiverOrBroadcast; /** @@ -57,54 +53,52 @@ import static se.leap.bitmaskclient.eip.EipResultBroadcast.tellToReceiverOrBroad */ public class LaunchVPN extends Activity { - public static final String EXTRA_KEY = "de.blinkt.openvpn.shortcutProfileUUID"; - public static final String EXTRA_NAME = "de.blinkt.openvpn.shortcutProfileName"; - public static final String EXTRA_HIDELOG = "de.blinkt.openvpn.showNoLogWindow"; - public static final String CLEARLOG = "clearlogconnect"; - - private static final int START_VPN_PROFILE = 70; private static final String TAG = LaunchVPN.class.getName(); + private VpnProfile selectedProfile; + private int selectedGateway; - private VpnProfile mSelectedProfile; - private boolean mhideLog = false; - - private boolean mCmfixed = false; - @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); - startVpnFromIntent(); - } - - protected void startVpnFromIntent() { - // Resolve the intent - final Intent intent = getIntent(); final String action = intent.getAction(); + if (!Intent.ACTION_MAIN.equals(action)) { + finish(); + } - // If the intent is a request to create a shortcut, we'll do that and exit - + VpnProfile profileToConnect = (VpnProfile) intent.getExtras().getSerializable(PROVIDER_PROFILE); + selectedGateway = intent.getExtras().getInt(EIP_N_CLOSEST_GATEWAY, 0); + if (profileToConnect == null) { + showAlertInMainActivity(R.string.shortcut_profile_notfound); + finish(); + } else { + selectedProfile = profileToConnect; + } - if (Intent.ACTION_MAIN.equals(action)) { - // Check if we need to clear the log - if (Preferences.getDefaultSharedPreferences(this).getBoolean(CLEARLOG, true)) - VpnStatus.clearLog(); + Intent vpnIntent; + try { + vpnIntent = VpnService.prepare(this.getApplicationContext()); + } catch (NullPointerException npe) { + showAlertInMainActivity(R.string.vpn_error_establish); + finish(); + return; + } - // we got called to be the starting point, most likely a shortcut - mhideLog = intent.getBooleanExtra(EXTRA_HIDELOG, false); - VpnProfile profileToConnect = (VpnProfile) intent.getExtras().getSerializable(PROVIDER_PROFILE); + if (vpnIntent != null) { + // we don't have the permission yet to start the VPN - if (profileToConnect == null) { - VpnStatus.logError(R.string.shortcut_profile_notfound); - // show Log window to display error - showLogWindow(); - finish(); - } else { - mSelectedProfile = profileToConnect; - launchVPN(); + VpnStatus.updateStateString("USER_VPN_PERMISSION", "", R.string.state_user_vpn_permission, + ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT); + // Start the query + try { + startActivityForResult(vpnIntent, START_VPN_PROFILE); + } catch (ActivityNotFoundException ane) { + // Shame on you Sony! At least one user reported that + // an official Sony Xperia Arc S image triggers this exception + showAlertInMainActivity(R.string.no_vpn_support_image); } } } @@ -113,13 +107,8 @@ public class LaunchVPN extends Activity { protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); - if(requestCode==START_VPN_PROFILE) { - SharedPreferences prefs = Preferences.getDefaultSharedPreferences(this); - boolean showLogWindow = prefs.getBoolean("showlogwindow", true); - - if(!mhideLog && showLogWindow) - showLogWindow(); - VPNLaunchHelper.startOpenVpn(mSelectedProfile, getBaseContext()); + if(requestCode==START_VPN_PROFILE && resultCode == Activity.RESULT_OK) { + EipCommand.launchVPNProfile(getApplicationContext(), selectedProfile, selectedGateway); finish(); } else if (resultCode == Activity.RESULT_CANCELED) { @@ -127,110 +116,29 @@ public class LaunchVPN extends Activity { VpnStatus.updateStateString("USER_VPN_PERMISSION_CANCELLED", "", R.string.state_user_vpn_permission_cancelled, ConnectionStatus.LEVEL_NOTCONNECTED); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - VpnStatus.logError(R.string.nought_alwayson_warning); - - finish(); - } - } - - void showLogWindow() { - - Intent startLW = new Intent(getBaseContext(), MainActivity.class); - startLW.putExtra(MainActivity.ACTION_SHOW_LOG_FRAGMENT, true); - startLW.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); - startActivity(startLW); - - } - - void showConfigErrorDialog(int vpnok) { - AlertDialog.Builder d = new AlertDialog.Builder(this); - d.setTitle(R.string.config_error_found); - d.setMessage(vpnok); - d.setPositiveButton(android.R.string.ok, new OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - finish(); - - } - }); - d.setOnCancelListener(new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - finish(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + showAlertInMainActivity(R.string.nought_alwayson_warning); } - }); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) - setOnDismissListener(d); - d.show(); - } - - @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) - private void setOnDismissListener(AlertDialog.Builder d) { - d.setOnDismissListener(new DialogInterface.OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialog) { - finish(); - } - }); - } - void launchVPN() { - int vpnok = mSelectedProfile.checkProfile(this); - if (vpnok != R.string.no_error_found) { - showConfigErrorDialog(vpnok); - return; - } - - Intent intent = null; - try { - intent = VpnService.prepare(this.getApplicationContext()); - } catch (NullPointerException npe) { - tellToReceiverOrBroadcast(this.getApplicationContext(), EIP_ACTION_PREPARE_VPN, RESULT_CANCELED); finish(); - return; - } - - // Check if we want to fix /dev/tun - SharedPreferences prefs = Preferences.getDefaultSharedPreferences(this); - boolean usecm9fix = prefs.getBoolean("useCM9Fix", false); - boolean loadTunModule = prefs.getBoolean("loadTunModule", false); - - if (loadTunModule) - execeuteSUcmd("insmod /system/lib/modules/tun.ko"); - - if (usecm9fix && !mCmfixed) { - execeuteSUcmd("chown system /dev/tun"); - } - - if (intent != null) { - VpnStatus.updateStateString("USER_VPN_PERMISSION", "", R.string.state_user_vpn_permission, - ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT); - // Start the query - try { - startActivityForResult(intent, START_VPN_PROFILE); - } catch (ActivityNotFoundException ane) { - // Shame on you Sony! At least one user reported that - // an official Sony Xperia Arc S image triggers this exception - VpnStatus.logError(R.string.no_vpn_support_image); - showLogWindow(); - } - } else { - onActivityResult(START_VPN_PROFILE, Activity.RESULT_OK, null); } + } + void showAlertInMainActivity(@StringRes int errorString) { + Bundle result = new Bundle(); + setErrorResult(result, errorString); + tellToReceiverOrBroadcast(this.getApplicationContext(), EIP_ACTION_PREPARE_VPN, RESULT_CANCELED, result); } - private void execeuteSUcmd(String command) { - try { - ProcessBuilder pb = new ProcessBuilder("su", "-c", command); - Process p = pb.start(); - int ret = p.waitFor(); - if (ret == 0) - mCmfixed = true; - } catch (InterruptedException | IOException e) { - VpnStatus.logException("SU command", e); - } + /** + * helper function to add error to result bundle + * + * @param result - result of an action + * @param errorMessageId - id of string resource describing the error + */ + void setErrorResult(Bundle result, @StringRes int errorMessageId) { + VpnStatus.logError(errorMessageId); + result.putString(ERRORS, getResources().getString(errorMessageId)); + result.putBoolean(BROADCAST_RESULT_KEY, false); } }
\ No newline at end of file diff --git a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java index 2c1a65dc..9ed2054e 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java @@ -8,12 +8,10 @@ package de.blinkt.openvpn.core; import android.Manifest.permission; import android.annotation.TargetApi; import android.app.Notification; -import android.app.UiModeManager; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.ShortcutManager; -import android.content.res.Configuration; import android.content.res.Resources; import android.net.ConnectivityManager; import android.net.VpnService; @@ -24,12 +22,13 @@ import android.os.IBinder; import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.RemoteException; -import androidx.annotation.RequiresApi; import android.system.OsConstants; import android.text.TextUtils; import android.util.Log; import android.widget.Toast; +import androidx.annotation.RequiresApi; + import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.net.Inet6Address; @@ -45,9 +44,10 @@ import de.blinkt.openvpn.core.VpnStatus.StateListener; import de.blinkt.openvpn.core.connection.Connection; import de.blinkt.openvpn.core.connection.Obfs4Connection; import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.eip.EipStatus; import se.leap.bitmaskclient.eip.VpnNotificationManager; -import se.leap.bitmaskclient.pluggableTransports.Shapeshifter; import se.leap.bitmaskclient.firewall.FirewallManager; +import se.leap.bitmaskclient.pluggableTransports.Shapeshifter; import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_CONNECTED; import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT; @@ -61,7 +61,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac public static final String START_SERVICE_STICKY = "de.blinkt.openvpn.START_SERVICE_STICKY"; public static final String ALWAYS_SHOW_NOTIFICATION = "de.blinkt.openvpn.NOTIFICATION_ALWAYS_VISIBLE"; public static final String DISCONNECT_VPN = "de.blinkt.openvpn.DISCONNECT_VPN"; - public static final String NOTIFICATION_CHANNEL_BG_ID = "openvpn_bg"; public static final String NOTIFICATION_CHANNEL_NEWSTATUS_ID = "openvpn_newstat"; public static final String VPNSERVICE_TUN = "vpnservice-tun"; public final static String ORBOT_PACKAGE_NAME = "org.torproject.android"; @@ -92,11 +91,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac private Shapeshifter shapeshifter; private FirewallManager firewallManager; - private static final int PRIORITY_MIN = -2; - private static final int PRIORITY_DEFAULT = 0; - private static final int PRIORITY_MAX = 2; - - private final IBinder mBinder = new IOpenVPNServiceInternal.Stub() { @Override @@ -118,6 +112,11 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac public boolean isVpnRunning() throws RemoteException { return OpenVPNService.this.isVpnRunning(); } + + @Override + public void startWithForegroundNotification() throws RemoteException { + OpenVPNService.this.startWithForegroundNotification(); + } }; // From: http://stackoverflow.com/questions/3758606/how-to-convert-byte-size-into-human-readable-format-in-java @@ -197,13 +196,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac firewallManager.stop(); } - private boolean runningOnAndroidTV() { - UiModeManager uiModeManager = (UiModeManager) getSystemService(UI_MODE_SERVICE); - if (uiModeManager == null) - return false; - return uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION; - } - synchronized void registerDeviceStateReceiver(OpenVPNManagement magnagement) { // Registers BroadcastReceiver to track network connection changes. IntentFilter filter = new IntentFilter(); @@ -218,8 +210,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac registerReceiver(mDeviceStateReceiver, filter); VpnStatus.addByteCountListener(mDeviceStateReceiver); - /*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) - addLollipopCMListener(); */ } synchronized void unregisterDeviceStateReceiver() { @@ -235,9 +225,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } mDeviceStateReceiver = null; - /*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) - removeLollipopCMListener();*/ - } @Override @@ -281,6 +268,13 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } @Override + public void startWithForegroundNotification() { + // Always show notification here to avoid problem with startForeground timeout + notificationManager.createOpenVpnNotificationChannel(); + notificationManager.buildForegroundServiceNotification(EipStatus.getInstance().getLevel(), this::onNotificationBuild); + } + + @Override public int onStartCommand(Intent intent, int flags, int startId) { if (intent != null && intent.getBooleanExtra(ALWAYS_SHOW_NOTIFICATION, false)) @@ -334,6 +328,8 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } else { /* The intent is null when we are set as always-on or the service has been restarted. */ Log.d(TAG, "Starting VPN due to isAlwaysOn system settings or app crash."); + startWithForegroundNotification(); + mProfile = VpnStatus.getLastConnectedVpnProfile(this); VpnStatus.logInfo(R.string.service_restarted); @@ -375,9 +371,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } private void startOpenVPN() { - /** - * see change above (l. 292 ff) - */ //TODO: investigate how connections[n] with n>0 get called during vpn setup (on connection refused?) // Do we need to check if there's any obfs4 connection in mProfile.mConnections and start // the dispatcher here? Can we start the dispatcher at a later point of execution, e.g. when @@ -524,8 +517,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac @Override public void onCreate() { super.onCreate(); - notificationManager = new VpnNotificationManager(this, this); - notificationManager.createOpenVpnNotificationChannel(); + notificationManager = new VpnNotificationManager(this); firewallManager = new FirewallManager(this, true); } @@ -543,9 +535,8 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac // Just in case unregister for state VpnStatus.removeStateListener(this); VpnStatus.flushLog(); - notificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_BG_ID); - notificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_NEWSTATUS_ID); firewallManager.onDestroy(); + notificationManager.cancelAll(); } private String getTunConfigString() { @@ -1013,14 +1004,10 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac if (mProcessThread == null && !mNotificationAlwaysVisible) return; - String channel = NOTIFICATION_CHANNEL_NEWSTATUS_ID; // Display byte count only after being connected - if (level == LEVEL_CONNECTED) { mDisplayBytecount = true; mConnecttime = System.currentTimeMillis(); - if (!runningOnAndroidTV()) - channel = NOTIFICATION_CHANNEL_BG_ID; firewallManager.start(); } else { mDisplayBytecount = false; @@ -1033,7 +1020,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac VpnStatus.getLastCleanLogMessage(this), level, 0, - channel); + NOTIFICATION_CHANNEL_NEWSTATUS_ID); } @@ -1064,7 +1051,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac null, LEVEL_CONNECTED, mConnecttime, - NOTIFICATION_CHANNEL_BG_ID); + NOTIFICATION_CHANNEL_NEWSTATUS_ID); } } @@ -1108,7 +1095,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac getString(resid), LEVEL_WAITING_FOR_USER_INPUT, 0, - NOTIFICATION_CHANNEL_BG_ID); + NOTIFICATION_CHANNEL_NEWSTATUS_ID); } @@ -1117,11 +1104,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac startForeground(notificationId, notification); } - @Override - public void onNotificationStop() { - stopForeground(true); - } - public void trigger_url_open(String info) { /* String channel = NOTIFICATION_CHANNEL_USERREQ_ID; diff --git a/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java b/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java index 7c742746..540ca043 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java +++ b/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java @@ -7,7 +7,6 @@ package de.blinkt.openvpn.core; import android.annotation.TargetApi; import android.content.Context; -import android.content.Intent; import android.os.Build; import java.io.File; @@ -18,7 +17,6 @@ import java.util.Arrays; import java.util.Vector; import se.leap.bitmaskclient.R; -import de.blinkt.openvpn.VpnProfile; public class VPNLaunchHelper { private static final String MININONPIEVPN = "nopie_openvpn"; @@ -120,7 +118,6 @@ public class VPNLaunchHelper { return false; } - return true; } catch (IOException e) { VpnStatus.logException(e); @@ -129,20 +126,6 @@ public class VPNLaunchHelper { } - - public static void startOpenVpn(VpnProfile startprofile, Context context) { - Intent startVPN = startprofile.prepareStartService(context); - if (startVPN != null) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) - //noinspection NewApi - context.startForegroundService(startVPN); - else - context.startService(startVPN); - - } - } - - public static String getConfigFilePath(Context context) { return context.getCacheDir().getAbsolutePath() + "/" + OVPNCONFIGFILE; } diff --git a/app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java b/app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java index 676e6c82..126c4a98 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java @@ -20,13 +20,14 @@ package se.leap.bitmaskclient.base; import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; +import android.util.Log; + import androidx.annotation.StringRes; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.DialogFragment; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentTransaction; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AppCompatActivity; -import android.util.Log; import org.json.JSONException; import org.json.JSONObject; @@ -35,24 +36,27 @@ import java.util.Observable; import java.util.Observer; import se.leap.bitmaskclient.R; -import se.leap.bitmaskclient.base.fragments.NavigationDrawerFragment; -import se.leap.bitmaskclient.eip.EIP; -import se.leap.bitmaskclient.eip.EipCommand; -import se.leap.bitmaskclient.eip.EipSetupListener; -import se.leap.bitmaskclient.eip.EipSetupObserver; import se.leap.bitmaskclient.base.fragments.EipFragment; import se.leap.bitmaskclient.base.fragments.ExcludeAppsFragment; import se.leap.bitmaskclient.base.fragments.LogFragment; +import se.leap.bitmaskclient.base.fragments.MainActivityErrorDialog; +import se.leap.bitmaskclient.base.fragments.NavigationDrawerFragment; import se.leap.bitmaskclient.base.models.Provider; import se.leap.bitmaskclient.base.models.ProviderObservable; -import se.leap.bitmaskclient.providersetup.models.LeapSRPSession; -import se.leap.bitmaskclient.providersetup.activities.LoginActivity; import se.leap.bitmaskclient.base.utils.PreferenceHelper; -import se.leap.bitmaskclient.base.fragments.MainActivityErrorDialog; +import se.leap.bitmaskclient.eip.EIP; +import se.leap.bitmaskclient.eip.EipCommand; +import se.leap.bitmaskclient.eip.EipSetupListener; +import se.leap.bitmaskclient.eip.EipSetupObserver; +import se.leap.bitmaskclient.providersetup.activities.LoginActivity; +import se.leap.bitmaskclient.providersetup.models.LeapSRPSession; +import static se.leap.bitmaskclient.R.string.downloading_vpn_certificate_failed; +import static se.leap.bitmaskclient.R.string.vpn_certificate_user_message; import static se.leap.bitmaskclient.base.models.Constants.ASK_TO_CANCEL_VPN; import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_CODE; import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY; +import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_LAUNCH_VPN; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_PREPARE_VPN; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START; import static se.leap.bitmaskclient.base.models.Constants.EIP_REQUEST; @@ -61,16 +65,14 @@ import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_CONFIGURE import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_LOG_IN; import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_SWITCH_PROVIDER; import static se.leap.bitmaskclient.base.models.Constants.SHARED_PREFERENCES; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.storeProviderInPreferences; +import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_INVALID_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_VPN_PREPARE; import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORID; import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORS; import static se.leap.bitmaskclient.providersetup.ProviderAPI.INCORRECTLY_DOWNLOADED_EIP_SERVICE; import static se.leap.bitmaskclient.providersetup.ProviderAPI.INCORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE; import static se.leap.bitmaskclient.providersetup.ProviderAPI.USER_MESSAGE; -import static se.leap.bitmaskclient.R.string.downloading_vpn_certificate_failed; -import static se.leap.bitmaskclient.R.string.vpn_certificate_user_message; -import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_INVALID_VPN_CERTIFICATE; -import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_VPN_PREPARE; -import static se.leap.bitmaskclient.base.utils.PreferenceHelper.storeProviderInPreferences; public class MainActivity extends AppCompatActivity implements EipSetupListener, Observer, ExcludeAppsFragment.ExcludedAppsCallback { @@ -256,7 +258,14 @@ public class MainActivity extends AppCompatActivity implements EipSetupListener, break; case EIP_ACTION_PREPARE_VPN: if (resultCode == RESULT_CANCELED) { - showMainActivityErrorDialog(getString(R.string.vpn_error_establish), ERROR_VPN_PREPARE); + String error = resultData.getString(ERRORS); + showMainActivityErrorDialog(error, ERROR_VPN_PREPARE); + } + break; + case EIP_ACTION_LAUNCH_VPN: + if (resultCode == RESULT_CANCELED) { + String error = resultData.getString(ERRORS); + showMainActivityErrorDialog(error); } break; } diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java index 4ff80ea6..615221ae 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java @@ -52,6 +52,7 @@ import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; import butterknife.Unbinder; +import de.blinkt.openvpn.core.ConnectionStatus; import de.blinkt.openvpn.core.IOpenVPNServiceInternal; import de.blinkt.openvpn.core.OpenVPNService; import de.blinkt.openvpn.core.VpnStatus; @@ -175,12 +176,18 @@ public class EipFragment extends Fragment implements Observer { View view = inflater.inflate(R.layout.f_eip, container, false); unbinder = ButterKnife.bind(this, view); - Bundle arguments = getArguments(); - if (arguments != null && arguments.containsKey(ASK_TO_CANCEL_VPN) && arguments.getBoolean(ASK_TO_CANCEL_VPN)) { - arguments.remove(ASK_TO_CANCEL_VPN); - setArguments(arguments); - askToStopEIP(); + try { + Bundle arguments = getArguments(); + if (arguments != null && arguments.containsKey(ASK_TO_CANCEL_VPN) && arguments.getBoolean(ASK_TO_CANCEL_VPN)) { + arguments.remove(ASK_TO_CANCEL_VPN); + setArguments(arguments); + askToStopEIP(); + } + } catch (IllegalStateException e) { + // probably setArguments failed because the fragments state is already saved + e.printStackTrace(); } + restoreFromSavedInstance(savedInstanceState); return view; } @@ -319,10 +326,7 @@ public class EipFragment extends Fragment implements Observer { } else { EipCommand.startVPN(context.getApplicationContext(), false); } - vpnStateImage.showProgress(); - routedText.setVisibility(GONE); - vpnRoute.setVisibility(GONE); - colorBackgroundALittle(); + EipStatus.getInstance().updateState("UI_CONNECTING", "", 0, ConnectionStatus.LEVEL_START); } protected void stopEipIfPossible() { diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/MainActivityErrorDialog.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/MainActivityErrorDialog.java index 4b307f23..f036b411 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/MainActivityErrorDialog.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/MainActivityErrorDialog.java @@ -111,15 +111,15 @@ public class MainActivityErrorDialog extends DialogFragment { public Dialog onCreateDialog(Bundle savedInstanceState) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); Context applicationContext = getContext().getApplicationContext(); - builder.setMessage(reasonToFail) - .setNegativeButton(R.string.cancel, (dialog, id) -> { - }); + builder.setMessage(reasonToFail); switch (downloadError) { case ERROR_INVALID_VPN_CERTIFICATE: builder.setPositiveButton(R.string.update_certificate, (dialog, which) -> ProviderAPICommand.execute(getContext(), UPDATE_INVALID_VPN_CERTIFICATE, provider)); + builder.setNegativeButton(R.string.cancel, (dialog, id) -> {}); break; case NO_MORE_GATEWAYS: + builder.setNegativeButton(R.string.cancel, (dialog, id) -> {}); if (provider.supportsPluggableTransports()) { if (getUsePluggableTransports(applicationContext)) { builder.setPositiveButton(warning_option_try_ovpn, ((dialog, which) -> { @@ -139,9 +139,7 @@ public class MainActivityErrorDialog extends DialogFragment { } break; case ERROR_VPN_PREPARE: - builder.setPositiveButton(R.string.retry, (dialog, which) -> { - EipCommand.startVPN(applicationContext, false); - }); + builder.setPositiveButton(android.R.string.ok, (dialog, which) -> { }); break; default: break; diff --git a/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java b/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java index e60019fc..a0d295bd 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java @@ -80,7 +80,7 @@ public interface Constants { String EIP_ACTION_START_BLOCKING_VPN = "se.leap.bitmaskclient.EIP_ACTION_START_BLOCKING_VPN"; String EIP_ACTION_STOP_BLOCKING_VPN = "se.leap.bitmaskclient.EIP_ACTION_STOP_BLOCKING_VPN"; String EIP_ACTION_PREPARE_VPN = "se.leap.bitmaskclient.EIP_ACTION_PREPARE_VPN"; - String EIP_ACTION_CONFIGURE_TETHERING = "se.leap.bitmaskclient.EIP_ACTION_CONFIGURE_TETHERING"; + String EIP_ACTION_LAUNCH_VPN = "se.leap.bitmaskclient.EIP_ACTION_LAUNCH_VPN"; String EIP_RECEIVER = "EIP.RECEIVER"; String EIP_REQUEST = "EIP.REQUEST"; @@ -157,6 +157,7 @@ public interface Constants { // JSON KEYS ///////////////////////////////////////////// String IP_ADDRESS = "ip_address"; + String IP_ADDRESS6 = "ip_address6"; String REMOTE = "remote"; String PORTS = "ports"; String PROTOCOLS = "protocols"; diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java index 66b7c6cf..74226250 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java @@ -22,16 +22,19 @@ import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.SharedPreferences; +import android.net.VpnService; +import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.os.ResultReceiver; +import android.util.Log; + import androidx.annotation.NonNull; import androidx.annotation.StringRes; import androidx.annotation.WorkerThread; import androidx.core.app.JobIntentService; import androidx.localbroadcastmanager.content.LocalBroadcastManager; -import android.util.Log; import org.json.JSONException; import org.json.JSONObject; @@ -43,26 +46,31 @@ import java.util.Observer; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; +import de.blinkt.openvpn.LaunchVPN; import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.core.ConnectionStatus; import de.blinkt.openvpn.core.IOpenVPNServiceInternal; import de.blinkt.openvpn.core.OpenVPNService; +import de.blinkt.openvpn.core.Preferences; import de.blinkt.openvpn.core.VpnStatus; import de.blinkt.openvpn.core.connection.Connection; +import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.base.OnBootReceiver; import se.leap.bitmaskclient.base.models.ProviderObservable; -import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.base.utils.PreferenceHelper; import static android.app.Activity.RESULT_CANCELED; import static android.app.Activity.RESULT_OK; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OPENVPN; +import static se.leap.bitmaskclient.R.string.vpn_certificate_is_invalid; +import static se.leap.bitmaskclient.R.string.warning_client_parsing_error_gateways; import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_GATEWAY_SETUP_OBSERVER_EVENT; import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY; +import static se.leap.bitmaskclient.base.models.Constants.CLEARLOG; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_CHECK_CERT_VALIDITY; -import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_CONFIGURE_TETHERING; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_IS_RUNNING; +import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_LAUNCH_VPN; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START_ALWAYS_ON_VPN; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START_BLOCKING_VPN; @@ -75,13 +83,13 @@ import static se.leap.bitmaskclient.base.models.Constants.EIP_RESTART_ON_BOOT; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PROFILE; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_VPN_CERTIFICATE; import static se.leap.bitmaskclient.base.models.Constants.SHARED_PREFERENCES; -import static se.leap.bitmaskclient.R.string.vpn_certificate_is_invalid; -import static se.leap.bitmaskclient.R.string.warning_client_parsing_error_gateways; +import static se.leap.bitmaskclient.base.utils.ConfigHelper.ensureNotOnMainThread; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getUsePluggableTransports; +import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_INVALID_PROFILE; import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_INVALID_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_VPN_PREPARE; import static se.leap.bitmaskclient.eip.EIP.EIPErrors.NO_MORE_GATEWAYS; import static se.leap.bitmaskclient.eip.EipResultBroadcast.tellToReceiverOrBroadcast; -import static se.leap.bitmaskclient.base.utils.ConfigHelper.ensureNotOnMainThread; -import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getUsePluggableTransports; /** * EIP is the abstract base class for interacting with and managing the Encrypted @@ -106,6 +114,7 @@ public final class EIP extends JobIntentService implements Observer { // Service connection to OpenVpnService, shared between threads private volatile OpenVpnServiceConnection openVpnServiceConnection; private WeakReference<ResultReceiver> mResultRef = new WeakReference<>(null); + private volatile VoidVpnServiceConnection voidVpnServiceConnection; /** * Unique job ID for this service. @@ -116,7 +125,8 @@ public final class EIP extends JobIntentService implements Observer { UNKNOWN, ERROR_INVALID_VPN_CERTIFICATE, NO_MORE_GATEWAYS, - ERROR_VPN_PREPARE + ERROR_VPN_PREPARE, + ERROR_INVALID_PROFILE } /** @@ -146,6 +156,10 @@ public final class EIP extends JobIntentService implements Observer { openVpnServiceConnection.close(); openVpnServiceConnection = null; } + if (voidVpnServiceConnection != null) { + voidVpnServiceConnection.close(); + voidVpnServiceConnection = null; + } } /** @@ -175,7 +189,7 @@ public final class EIP extends JobIntentService implements Observer { int nClosestGateway; switch (action) { case EIP_ACTION_START: - boolean earlyRoutes = intent.getBooleanExtra(EIP_EARLY_ROUTES, true); + boolean earlyRoutes = intent.getBooleanExtra(EIP_EARLY_ROUTES, false); nClosestGateway = intent.getIntExtra(EIP_N_CLOSEST_GATEWAY, 0); startEIP(earlyRoutes, nClosestGateway); break; @@ -195,8 +209,9 @@ public final class EIP extends JobIntentService implements Observer { disconnect(); earlyRoutes(); break; - case EIP_ACTION_CONFIGURE_TETHERING: - Log.d(TAG, "TODO: implement tethering configuration"); + case EIP_ACTION_LAUNCH_VPN: + VpnProfile profile = (VpnProfile) intent.getSerializableExtra(PROVIDER_PROFILE); + launchProfile(profile); break; } } @@ -211,11 +226,11 @@ public final class EIP extends JobIntentService implements Observer { @SuppressLint("ApplySharedPref") private void startEIP(boolean earlyRoutes, int nClosestGateway) { Log.d(TAG, "start EIP with early routes: " + earlyRoutes + " and nClosest Gateway: " + nClosestGateway); + Bundle result = new Bundle(); if (!eipStatus.isBlockingVpnEstablished() && earlyRoutes) { - earlyRoutes(); + earlyRoutes(result); } - Bundle result = new Bundle(); if (!preferences.getBoolean(EIP_RESTART_ON_BOOT, false)) { preferences.edit().putBoolean(EIP_RESTART_ON_BOOT, true).commit(); } @@ -234,12 +249,11 @@ public final class EIP extends JobIntentService implements Observer { } Gateway gateway = gatewaysManager.select(nClosestGateway); - - if (launchActiveGateway(gateway, nClosestGateway)) { - tellToReceiverOrBroadcast(this, EIP_ACTION_START, RESULT_OK); - } else { - setErrorResult(result, NO_MORE_GATEWAYS.toString(), getStringResourceForNoMoreGateways(), getString(R.string.app_name)); + launchActiveGateway(gateway, nClosestGateway, result); + if (result.containsKey(BROADCAST_RESULT_KEY) && !result.getBoolean(BROADCAST_RESULT_KEY)) { tellToReceiverOrBroadcast(this, EIP_ACTION_START, RESULT_CANCELED, result); + } else { + tellToReceiverOrBroadcast(this, EIP_ACTION_START, RESULT_OK); } } @@ -250,9 +264,19 @@ public final class EIP extends JobIntentService implements Observer { private void startEIPAlwaysOnVpn() { GatewaysManager gatewaysManager = new GatewaysManager(getApplicationContext()); Gateway gateway = gatewaysManager.select(0); + Bundle result = new Bundle(); - if (!launchActiveGateway(gateway, 0)) { - Log.d(TAG, "startEIPAlwaysOnVpn no active profile available!"); + launchActiveGateway(gateway, 0, result); + if (result.containsKey(BROADCAST_RESULT_KEY) && !result.getBoolean(BROADCAST_RESULT_KEY)){ + VpnStatus.logWarning("ALWAYS-ON VPN: " + getString(R.string.no_vpn_profiles_defined)); + } + } + + private void earlyRoutes() { + Bundle result = new Bundle(); + earlyRoutes(result); + if (result.containsKey(BROADCAST_RESULT_KEY) && !result.getBoolean(BROADCAST_RESULT_KEY)){ + tellToReceiverOrBroadcast(this, EIP_ACTION_START_BLOCKING_VPN, RESULT_CANCELED, result); } } @@ -260,10 +284,27 @@ public final class EIP extends JobIntentService implements Observer { * Early routes are routes that block traffic until a new * VpnService is started properly. */ - private void earlyRoutes() { - Intent voidVpnLauncher = new Intent(getApplicationContext(), VoidVpnLauncher.class); - voidVpnLauncher.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(voidVpnLauncher); + private void earlyRoutes(Bundle result) { + Intent blockingIntent = VpnService.prepare(getApplicationContext()); // stops the VPN connection created by another application. + if (blockingIntent == null) { + try { + initVoidVpnServiceConnection(); + Intent voidVpnService = new Intent(getApplicationContext(), VoidVpnService.class); + voidVpnService.setAction(EIP_ACTION_START_BLOCKING_VPN); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + getApplicationContext().startForegroundService(voidVpnService); + voidVpnServiceConnection.getService().startWithForegroundNotification(); + } else { + getApplicationContext().startService(voidVpnService); + } + } catch (InterruptedException | IllegalStateException e) { + setErrorResult(result, R.string.vpn_error_establish, null); + } + } else { + Intent voidVpnLauncher = new Intent(getApplicationContext(), VoidVpnLauncher.class); + voidVpnLauncher.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(voidVpnLauncher); + } } /** @@ -271,21 +312,59 @@ public final class EIP extends JobIntentService implements Observer { * * @param gateway to connect to */ - private boolean launchActiveGateway(Gateway gateway, int nClosestGateway) { + private void launchActiveGateway(Gateway gateway, int nClosestGateway, Bundle result) { VpnProfile profile; Connection.TransportType transportType = getUsePluggableTransports(this) ? OBFS4 : OPENVPN; if (gateway == null || (profile = gateway.getProfile(transportType)) == null) { - return false; + setErrorResult(result, NO_MORE_GATEWAYS.toString(), getStringResourceForNoMoreGateways(), getString(R.string.app_name)); + return; } - Intent intent = new Intent(BROADCAST_GATEWAY_SETUP_OBSERVER_EVENT); - intent.putExtra(PROVIDER_PROFILE, profile); - intent.putExtra(Gateway.KEY_N_CLOSEST_GATEWAY, nClosestGateway); - LocalBroadcastManager.getInstance(this).sendBroadcast(intent); - return true; + Intent intent; + try { + intent = VpnService.prepare(getApplicationContext()); + } catch (NullPointerException npe) { + setErrorResult(result, ERROR_VPN_PREPARE.toString(), R.string.vpn_error_establish); + return; + } + if (intent == null) { + // vpn has been successfully prepared + + //inform EipSetupObserver about vpn connecting attempt + Intent setupObserverIntent = new Intent(BROADCAST_GATEWAY_SETUP_OBSERVER_EVENT); + setupObserverIntent.putExtra(PROVIDER_PROFILE, profile); + setupObserverIntent.putExtra(EIP_N_CLOSEST_GATEWAY, nClosestGateway); + LocalBroadcastManager.getInstance(this).sendBroadcast(setupObserverIntent); + + // Check if we need to clear the log + if (Preferences.getDefaultSharedPreferences(this).getBoolean(CLEARLOG, true)) + VpnStatus.clearLog(); + + // check profile configuration + int vpnok = profile.checkProfile(this); + if (vpnok != R.string.no_error_found) { + VpnStatus.logError(R.string.config_error_found); + VpnStatus.logError(vpnok); + setErrorResult(result, ERROR_INVALID_PROFILE.toString(), 0); + return; + } + + //launch profile + launchProfile(profile, result); + + } else { + // vpn permission is missing + Intent permissionIntent = new Intent(getApplicationContext(), LaunchVPN.class); + permissionIntent.setAction(Intent.ACTION_MAIN); + permissionIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + permissionIntent.putExtra(PROVIDER_PROFILE, profile); + permissionIntent.putExtra(EIP_N_CLOSEST_GATEWAY, nClosestGateway); + startActivity(permissionIntent); + } } + /** * Stop VPN * First checks if the OpenVpnConnection is open then @@ -352,14 +431,16 @@ public final class EIP extends JobIntentService implements Observer { void setErrorResult(Bundle result, String errorId, @StringRes int errorMessageId, Object... args) { JSONObject errorJson = new JSONObject(); try { - String errorMessage; - if (args != null) { - errorMessage = getResources().getString(errorMessageId, args); - } else { - errorMessage = getResources().getString(errorMessageId); + if (errorMessageId != 0) { + String errorMessage; + if (args != null) { + errorMessage = getResources().getString(errorMessageId, args); + } else { + errorMessage = getResources().getString(errorMessageId); + } + VpnStatus.logWarning("[EIP] error: " + errorMessage); + errorJson.put(ERRORS, errorMessage); } - VpnStatus.logWarning("[EIP] error: " + errorMessage); - errorJson.put(ERRORS, errorMessage); errorJson.put(ERRORID, errorId); } catch (JSONException e) { e.printStackTrace(); @@ -409,6 +490,41 @@ public final class EIP extends JobIntentService implements Observer { return false; } + /** + * binds OpenVPNService to this service, starts it as a foreground service with a profile + * @param vpnProfile OpenVPN profile used to create a VPN connection + * @param result Bundle containing information about possible errors + */ + private void launchProfile(VpnProfile vpnProfile, Bundle result) { + Intent startVPN = vpnProfile.prepareStartService(getApplicationContext()); + if (startVPN != null) { + try { + initOpenVpnServiceConnection(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + //noinspection NewApi + getApplicationContext().startForegroundService(startVPN); + openVpnServiceConnection.getService().startWithForegroundNotification(); + } else { + getApplicationContext().startService(startVPN); + } + } catch (InterruptedException | IllegalStateException | RemoteException e) { + setErrorResult(result, R.string.vpn_error_establish, null); + } + } else { + setErrorResult(result, R.string.vpn_error_establish, null); + } + } + + private void launchProfile(VpnProfile vpnProfile) { + Bundle bundle = new Bundle(); + launchProfile(vpnProfile, bundle); + if (bundle.containsKey(BROADCAST_RESULT_KEY) && !bundle.getBoolean(BROADCAST_RESULT_KEY)) { + tellToReceiverOrBroadcast(this.getApplicationContext(), EIP_ACTION_LAUNCH_VPN, RESULT_CANCELED, bundle); + } else { + tellToReceiverOrBroadcast(this.getApplicationContext(), EIP_ACTION_LAUNCH_VPN, RESULT_OK); + } + } + private @StringRes int getStringResourceForNoMoreGateways() { if (ProviderObservable.getInstance().getCurrentProvider().supportsPluggableTransports()) { @@ -437,6 +553,69 @@ public final class EIP extends JobIntentService implements Observer { } /** + * Assigns a new VoidVpnServiceConnection to EIP's member variable voidVpnServiceConnection. + * 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 initVoidVpnServiceConnection() throws InterruptedException, IllegalStateException { + if (voidVpnServiceConnection == null) { + Log.d(TAG, "serviceConnection is still null"); + voidVpnServiceConnection = new VoidVpnServiceConnection(this); + } + } + + public static class VoidVpnServiceConnection implements Closeable { + private final Context context; + private ServiceConnection serviceConnection; + private VoidVpnService voidVpnService; + + VoidVpnServiceConnection(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<VoidVpnService> 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 { + VoidVpnService.VoidVpnServiceBinder binder = (VoidVpnService.VoidVpnServiceBinder) service; + blockingQueue.put(binder.getService()); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + } + }; + Intent intent = new Intent(context, VoidVpnService.class); + context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE); + voidVpnService = blockingQueue.take(); + } + + public VoidVpnService getService() { + return voidVpnService; + } + } + + /** * Creates a service connection to OpenVpnService. * The constructor blocks until the service is bound to the given Context. * Pattern stolen from android.security.KeyChain.java diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/EipCommand.java b/app/src/main/java/se/leap/bitmaskclient/eip/EipCommand.java index 0650e8cd..46704419 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EipCommand.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EipCommand.java @@ -9,14 +9,17 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import de.blinkt.openvpn.VpnProfile; + import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_CHECK_CERT_VALIDITY; -import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_CONFIGURE_TETHERING; +import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_LAUNCH_VPN; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START_BLOCKING_VPN; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_STOP; import static se.leap.bitmaskclient.base.models.Constants.EIP_EARLY_ROUTES; import static se.leap.bitmaskclient.base.models.Constants.EIP_N_CLOSEST_GATEWAY; import static se.leap.bitmaskclient.base.models.Constants.EIP_RECEIVER; +import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PROFILE; /** * Use this class to send commands to EIP @@ -73,6 +76,15 @@ public class EipCommand { execute(context, EIP_ACTION_STOP); } + public static void launchVPNProfile(@NonNull Context context, VpnProfile vpnProfile, Integer closestGateway) { + Intent baseIntent = new Intent(); + baseIntent.putExtra(PROVIDER_PROFILE, vpnProfile); + baseIntent.putExtra(EIP_N_CLOSEST_GATEWAY, closestGateway); + execute(context, EIP_ACTION_LAUNCH_VPN, null, baseIntent); + } + + public static void launchVoidVPN(@NonNull Context context) { execute(context, EIP_ACTION_START_BLOCKING_VPN);} + @VisibleForTesting public static void stopVPN(@NonNull Context context, ResultReceiver resultReceiver) { execute(context, EIP_ACTION_STOP, resultReceiver, null); @@ -87,13 +99,4 @@ public class EipCommand { execute(context, EIP_ACTION_CHECK_CERT_VALIDITY, resultReceiver, null); } - public static void configureTethering(@NonNull Context context) { - execute(context, EIP_ACTION_CONFIGURE_TETHERING); - } - - @VisibleForTesting - public static void configureTethering(@NonNull Context context, ResultReceiver resultReceiver) { - execute(context, EIP_ACTION_CONFIGURE_TETHERING); - } - } diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java b/app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java index f35e5e30..1ad5f7d2 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java @@ -54,10 +54,12 @@ import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_GATEWAY_SETU import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_PROVIDER_API_EVENT; import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_CODE; import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY; +import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_LAUNCH_VPN; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_PREPARE_VPN; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START_ALWAYS_ON_VPN; import static se.leap.bitmaskclient.base.models.Constants.EIP_EARLY_ROUTES; +import static se.leap.bitmaskclient.base.models.Constants.EIP_N_CLOSEST_GATEWAY; import static se.leap.bitmaskclient.base.models.Constants.EIP_REQUEST; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PROFILE; @@ -158,14 +160,14 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta ProviderObservable.getInstance().updateProvider(provider); PreferenceHelper.storeProviderInPreferences(preferences, provider); if (EipStatus.getInstance().isDisconnected()) { - EipCommand.startVPN(context.getApplicationContext(), true); + EipCommand.startVPN(context.getApplicationContext(), false); } break; case CORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE: provider = resultData.getParcelable(PROVIDER_KEY); ProviderObservable.getInstance().updateProvider(provider); PreferenceHelper.storeProviderInPreferences(preferences, provider); - EipCommand.startVPN(context.getApplicationContext(), true); + EipCommand.startVPN(context.getApplicationContext(), false); break; case CORRECTLY_DOWNLOADED_GEOIP_JSON: provider = resultData.getParcelable(PROVIDER_KEY); @@ -212,14 +214,18 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta case EIP_ACTION_START_ALWAYS_ON_VPN: if (resultCode == RESULT_CANCELED) { //setup failed - if (error == EIP.EIPErrors.NO_MORE_GATEWAYS) { - finishGatewaySetup(false); - EipCommand.startBlockingVPN(context.getApplicationContext()); - } else { - //FIXME: - finishGatewaySetup(false); - EipCommand.stopVPN(context); - EipStatus.refresh(); + switch (error) { + case NO_MORE_GATEWAYS: + finishGatewaySetup(false); + EipCommand.startBlockingVPN(context.getApplicationContext()); + break; + case ERROR_INVALID_PROFILE: + selectNextGateway(); + break; + default: + finishGatewaySetup(false); + EipCommand.stopVPN(context); + EipStatus.refresh(); } } break; @@ -230,6 +236,13 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta EipStatus.refresh(); } break; + case EIP_ACTION_LAUNCH_VPN: + if (resultCode == RESULT_CANCELED) { + VpnStatus.logError("Error starting VpnService."); + finishGatewaySetup(false); + EipStatus.refresh(); + } + break; default: break; } @@ -252,21 +265,9 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta return; } setupVpnProfile = vpnProfile; - setupNClosestGateway.set(event.getIntExtra(Gateway.KEY_N_CLOSEST_GATEWAY, 0)); + setupNClosestGateway.set(event.getIntExtra(EIP_N_CLOSEST_GATEWAY, 0)); Log.d(TAG, "bitmaskapp add state listener"); VpnStatus.addStateListener(this); - - launchVPN(setupVpnProfile); - } - - private void launchVPN(VpnProfile vpnProfile) { - Intent intent = new Intent(context.getApplicationContext(), LaunchVPN.class); - intent.setAction(Intent.ACTION_MAIN); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra(LaunchVPN.EXTRA_HIDELOG, true); - intent.putExtra(PROVIDER_PROFILE, vpnProfile); - intent.putExtra(Gateway.KEY_N_CLOSEST_GATEWAY, setupNClosestGateway.get()); - context.startActivity(intent); } @Override diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java b/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java index ad84ec5a..bc123683 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java @@ -77,7 +77,7 @@ public class EipStatus extends Observable implements VpnStatus.StateListener { currentStatus.setLocalizedResId(localizedResId); currentStatus.setLevel(level); currentStatus.setEipLevel(level); - if (tmp != currentStatus.getLevel() || "RECONNECTING".equals(state)) { + if (tmp != currentStatus.getLevel() || "RECONNECTING".equals(state) || "UI_CONNECTING".equals(state)) { refresh(); } } diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java index 1df54e6e..6b44856e 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java @@ -55,7 +55,6 @@ import static se.leap.bitmaskclient.base.models.Constants.VERSION; public class Gateway { public final static String TAG = Gateway.class.getSimpleName(); - public final static String KEY_N_CLOSEST_GATEWAY = "N_CLOSEST_GATEWAY"; private JSONObject generalConfiguration; private JSONObject secrets; diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnLauncher.java b/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnLauncher.java index e6905448..e2cd86b9 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnLauncher.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnLauncher.java @@ -3,10 +3,10 @@ package se.leap.bitmaskclient.eip; import android.app.Activity; import android.content.Intent; import android.net.VpnService; -import android.os.Build; import android.os.Bundle; -import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START_BLOCKING_VPN; +import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_PREPARE_VPN; +import static se.leap.bitmaskclient.eip.EipResultBroadcast.tellToReceiverOrBroadcast; public class VoidVpnLauncher extends Activity { @@ -19,24 +19,25 @@ public class VoidVpnLauncher extends Activity { } public void setUp() { - Intent blocking_intent = VpnService.prepare(getApplicationContext()); // stops the VPN connection created by another application. - if (blocking_intent != null) - startActivityForResult(blocking_intent, VPN_USER_PERMISSION); + Intent blockingIntent = null; + try { + blockingIntent = VpnService.prepare(getApplicationContext()); // stops the VPN connection created by another application. + } catch (NullPointerException npe) { + tellToReceiverOrBroadcast(this.getApplicationContext(), EIP_ACTION_PREPARE_VPN, RESULT_CANCELED); + finish(); + } + if (blockingIntent != null) { + startActivityForResult(blockingIntent, VPN_USER_PERMISSION); + } else { - onActivityResult(VPN_USER_PERMISSION, RESULT_OK, null); + EipCommand.startBlockingVPN(getApplicationContext()); } } protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == VPN_USER_PERMISSION) { if (resultCode == RESULT_OK) { - Intent void_vpn_service = new Intent(getApplicationContext(), VoidVpnService.class); - void_vpn_service.setAction(EIP_ACTION_START_BLOCKING_VPN); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - startForegroundService(void_vpn_service); - } else { - startService(void_vpn_service); - } + EipCommand.launchVoidVPN(getApplicationContext()); } } finish(); diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java b/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java index 77038492..35d2b376 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java @@ -21,7 +21,9 @@ import android.app.Notification; import android.content.Intent; import android.content.SharedPreferences; import android.net.VpnService; +import android.os.Binder; import android.os.Build; +import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.system.OsConstants; import android.util.Log; @@ -53,13 +55,27 @@ public class VoidVpnService extends VpnService implements Observer, VpnNotificat private EipStatus eipStatus; private VpnNotificationManager notificationManager; + private final IBinder binder = new VoidVpnServiceBinder(); + public class VoidVpnServiceBinder extends Binder { + VoidVpnService getService() { + // Return this instance of LocalService so clients can call public methods + return VoidVpnService.this; + } + } + + @Override + public IBinder onBind(Intent intent) { + return binder; + } + + + @Override public void onCreate() { super.onCreate(); eipStatus = EipStatus.getInstance(); eipStatus.addObserver(this); - notificationManager = new VpnNotificationManager(this, this); - notificationManager.createVoidVpnNotificationChannel(); + notificationManager = new VpnNotificationManager(this); } @Override @@ -77,6 +93,7 @@ public class VoidVpnService extends VpnService implements Observer, VpnNotificat thread.run(); } else if (action.equals("android.net.VpnService") && Build.VERSION.SDK_INT >= ALWAYS_ON_MIN_API_LEVEL) { //only always-on feature triggers this + startWithForegroundNotification(); thread = new Thread(new Runnable() { public void run() { establishBlockingVpn(); @@ -99,14 +116,19 @@ public class VoidVpnService extends VpnService implements Observer, VpnNotificat closeFd(); } + @Override + public void onDestroy() { + super.onDestroy(); + notificationManager.cancelAll(); + } + private void stop() { - notificationManager.stopNotifications(NOTIFICATION_CHANNEL_NEWSTATUS_ID); - notificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_NEWSTATUS_ID); if (thread != null) { thread.interrupt(); } closeFd(); VpnStatus.updateStateString("NOPROCESS", "BLOCKING VPN STOPPED", R.string.state_noprocess, ConnectionStatus.LEVEL_NOTCONNECTED); + stopForeground(true); } public static boolean isRunning() throws NullPointerException { @@ -185,9 +207,11 @@ public class VoidVpnService extends VpnService implements Observer, VpnNotificat notificationManager.buildVoidVpnNotification( blockingMessage, blockingMessage, - eipStatus.getLevel()); + eipStatus.getLevel(), + this + ); } else { - notificationManager.stopNotifications(NOTIFICATION_CHANNEL_NEWSTATUS_ID); + stopForeground(true); } } @@ -196,9 +220,15 @@ public class VoidVpnService extends VpnService implements Observer, VpnNotificat startForeground(notificationId, notification); } - @Override - public void onNotificationStop() { - stopForeground(true); + public void startWithForegroundNotification() { + notificationManager.createOpenVpnNotificationChannel(); + String message = getString(R.string.state_disconnected); + notificationManager.buildVoidVpnNotification( + message, + message, + eipStatus.getLevel(), + this::onNotificationBuild + ); } } diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java b/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java index 51069d6d..6fffb403 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java @@ -37,6 +37,7 @@ import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OPENVPN; import static se.leap.bitmaskclient.base.models.Constants.CAPABILITIES; import static se.leap.bitmaskclient.base.models.Constants.IP_ADDRESS; +import static se.leap.bitmaskclient.base.models.Constants.IP_ADDRESS6; import static se.leap.bitmaskclient.base.models.Constants.OPTIONS; import static se.leap.bitmaskclient.base.models.Constants.PORTS; import static se.leap.bitmaskclient.base.models.Constants.PROTOCOLS; @@ -70,7 +71,7 @@ public class VpnConfigGenerator { public void checkCapabilities() throws ConfigParser.ConfigParseError { try { - if (apiVersion == 3) { + if (apiVersion >= 3) { JSONArray supportedTransports = gateway.getJSONObject(CAPABILITIES).getJSONArray(TRANSPORT); for (int i = 0; i < supportedTransports.length(); i++) { JSONObject transport = supportedTransports.getJSONObject(i); @@ -170,8 +171,13 @@ public class VpnConfigGenerator { gatewayConfigApiv1(stringBuilder, ipAddress, capabilities); break; case 3: + case 4: + String ipAddress6 = gateway.optString(IP_ADDRESS6); + String[] ipAddresses = ipAddress6.isEmpty() ? + new String[]{ipAddress} : + new String[]{ipAddress6, ipAddress}; JSONArray transports = capabilities.getJSONArray(TRANSPORT); - gatewayConfigApiv3(transportType, stringBuilder, ipAddress, transports); + gatewayConfigMinApiv3(transportType, stringBuilder, ipAddresses, transports); break; } } catch (JSONException e) { @@ -186,11 +192,11 @@ public class VpnConfigGenerator { return remotes; } - private void gatewayConfigApiv3(Connection.TransportType transportType, StringBuilder stringBuilder, String ipAddress, JSONArray transports) throws JSONException { + private void gatewayConfigMinApiv3(Connection.TransportType transportType, StringBuilder stringBuilder, String[] ipAddresses, JSONArray transports) throws JSONException { if (transportType == OBFS4) { - obfs4GatewayConfigApiv3(stringBuilder, ipAddress, transports); + obfs4GatewayConfigMinApiv3(stringBuilder, ipAddresses, transports); } else { - ovpnGatewayConfigApi3(stringBuilder, ipAddress, transports); + ovpnGatewayConfigMinApi3(stringBuilder, ipAddresses, transports); } } @@ -209,7 +215,7 @@ public class VpnConfigGenerator { } } - private void ovpnGatewayConfigApi3(StringBuilder stringBuilder, String ipAddress, JSONArray transports) throws JSONException { + private void ovpnGatewayConfigMinApi3(StringBuilder stringBuilder, String[] ipAddresses, JSONArray transports) throws JSONException { String port; String protocol; JSONObject openvpnTransport = getTransport(transports, OPENVPN); @@ -219,8 +225,10 @@ public class VpnConfigGenerator { JSONArray protocols = openvpnTransport.getJSONArray(PROTOCOLS); for (int k = 0; k < protocols.length(); k++) { protocol = protocols.optString(k); - String newRemote = REMOTE + " " + ipAddress + " " + port + " " + protocol + newLine; - stringBuilder.append(newRemote); + for (String ipAddress : ipAddresses) { + String newRemote = REMOTE + " " + ipAddress + " " + port + " " + protocol + newLine; + stringBuilder.append(newRemote); + } } } } @@ -237,8 +245,20 @@ public class VpnConfigGenerator { return selectedTransport; } - private void obfs4GatewayConfigApiv3(StringBuilder stringBuilder, String ipAddress, JSONArray transports) throws JSONException { + private void obfs4GatewayConfigMinApiv3(StringBuilder stringBuilder, String[] ipAddresses, JSONArray transports) throws JSONException { JSONObject obfs4Transport = getTransport(transports, OBFS4); + //for now only use ipv4 gateway the syntax route remote_host 255.255.255.255 net_gateway is not yet working + // https://community.openvpn.net/openvpn/ticket/1161 + /*for (String ipAddress : ipAddresses) { + String route = "route " + ipAddress + " 255.255.255.255 net_gateway" + newLine; + stringBuilder.append(route); + }*/ + + if (ipAddresses.length == 0) { + return; + } + + String ipAddress = ipAddresses[ipAddresses.length - 1]; String route = "route " + ipAddress + " 255.255.255.255 net_gateway" + newLine; stringBuilder.append(route); String remote = REMOTE + " " + DISPATCHER_IP + " " + DISPATCHER_PORT + " " + obfs4Transport.getJSONArray(PROTOCOLS).getString(0) + newLine; diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/VpnNotificationManager.java b/app/src/main/java/se/leap/bitmaskclient/eip/VpnNotificationManager.java index b3ed5394..6fac0f72 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VpnNotificationManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VpnNotificationManager.java @@ -27,32 +27,34 @@ import android.graphics.Color; import android.graphics.Typeface; import android.os.Build; import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.core.app.NotificationCompat; -import androidx.core.app.NotificationManagerCompat; import android.text.Spannable; import android.text.SpannableString; import android.text.TextUtils; import android.text.style.StyleSpan; import android.widget.RemoteViews; +import androidx.annotation.NonNull; +import androidx.core.app.NotificationCompat; +import androidx.core.app.NotificationManagerCompat; + import de.blinkt.openvpn.LaunchVPN; import de.blinkt.openvpn.core.ConnectionStatus; import de.blinkt.openvpn.core.OpenVPNService; -import se.leap.bitmaskclient.base.MainActivity; +import de.blinkt.openvpn.core.VpnStatus; import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.base.MainActivity; import se.leap.bitmaskclient.base.StartActivity; import static android.os.Build.VERSION_CODES.O; -import static androidx.core.app.NotificationCompat.PRIORITY_HIGH; -import static androidx.core.app.NotificationCompat.PRIORITY_MAX; -import static androidx.core.app.NotificationCompat.PRIORITY_MIN; import static android.text.TextUtils.isEmpty; +import static androidx.core.app.NotificationCompat.PRIORITY_DEFAULT; +import static androidx.core.app.NotificationCompat.PRIORITY_MAX; import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_NONETWORK; import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT; +import static se.leap.bitmaskclient.base.MainActivity.ACTION_SHOW_VPN_FRAGMENT; import static se.leap.bitmaskclient.base.models.Constants.ASK_TO_CANCEL_VPN; +import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_STOP_BLOCKING_VPN; -import static se.leap.bitmaskclient.base.MainActivity.ACTION_SHOW_VPN_FRAGMENT; /** * Created by cyberta on 14.01.18. @@ -60,29 +62,20 @@ import static se.leap.bitmaskclient.base.MainActivity.ACTION_SHOW_VPN_FRAGMENT; public class VpnNotificationManager { + private static final String TAG = VpnNotificationManager.class.getSimpleName(); Context context; - private VpnServiceCallback vpnServiceCallback; - private NotificationManager notificationManager; - private NotificationManagerCompat compatNotificationManager; - private String[] notificationChannels = { - OpenVPNService.NOTIFICATION_CHANNEL_NEWSTATUS_ID, - OpenVPNService.NOTIFICATION_CHANNEL_BG_ID, - VoidVpnService.NOTIFICATION_CHANNEL_NEWSTATUS_ID}; - private String lastNotificationChannel = ""; + private final NotificationManagerCompat compatNotificationManager; public interface VpnServiceCallback { void onNotificationBuild(int notificationId, Notification notification); - void onNotificationStop(); } - public VpnNotificationManager(@NonNull Context context, @NonNull VpnServiceCallback vpnServiceCallback) { + public VpnNotificationManager(@NonNull Context context) { this.context = context; - notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); compatNotificationManager = NotificationManagerCompat.from(context); - this.vpnServiceCallback = vpnServiceCallback; } - public void buildVoidVpnNotification(final String msg, String tickerText, ConnectionStatus status) { + public void buildVoidVpnNotification(final String msg, String tickerText, ConnectionStatus status, VpnServiceCallback callback) { //TODO: implement extra Dashboard.ACTION_ASK_TO_CANCEL_BLOCKING_VPN NotificationCompat.Action.Builder actionBuilder = new NotificationCompat.Action.Builder(R.drawable.ic_menu_close_clear_cancel, context.getString(R.string.vpn_button_turn_off_blocking), getStopVoidVpnIntent()); @@ -97,28 +90,45 @@ public class VpnNotificationManager { PRIORITY_MAX, 0, getMainActivityIntent(), - actionBuilder.build()); + actionBuilder.build(), + callback + ); } - public void stopNotifications(String notificationChannelNewstatusId) { - vpnServiceCallback.onNotificationStop(); - compatNotificationManager.cancel(notificationChannelNewstatusId.hashCode()); - } - public void deleteNotificationChannel(String notificationChannel) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && - notificationManager.getNotificationChannel(notificationChannel) != null) { - notificationManager.deleteNotificationChannel(notificationChannel); + public void buildForegroundServiceNotification(ConnectionStatus connectionStatus, + VpnServiceCallback callback) { + String message = ""; + // if the app was killed by the system getLastCleanLogMessage returns an empty string + // because the state doesn't get persisted. We can use LEVEL_NOTCONNECTED as an indicator for + // that case because the openvpn service won't be connected then + if (connectionStatus == ConnectionStatus.LEVEL_NOTCONNECTED) { + message = context.getString(R.string.eip_state_not_connected); + } else { + message = VpnStatus.getLastCleanLogMessage(context); } + + NotificationCompat.Action.Builder actionBuilder = new NotificationCompat.Action.Builder(0, + context.getString(R.string.vpn_button_turn_on), getStartOpenvpnIntent()); + + buildVpnNotification( + "", + message, + null, + "", + connectionStatus, + OpenVPNService.NOTIFICATION_CHANNEL_NEWSTATUS_ID, + PRIORITY_DEFAULT, + 0, + getMainActivityIntent(), + actionBuilder.build(), + callback + ); } - /** - * @param msg - * @param tickerText - * @param status - * @param when - */ - public void buildOpenVpnNotification(String profileName, boolean isObfuscated, String msg, String tickerText, ConnectionStatus status, long when, String notificationChannelNewstatusId) { + public void buildOpenVpnNotification(String profileName, boolean isObfuscated, String msg, + String tickerText, ConnectionStatus status, long when, + String notificationChannelNewstatusId, VpnServiceCallback vpnServiceCallback) { String cancelString; CharSequence bigmessage = null; String ghostIcon = new String(Character.toChars(0x1f309)); @@ -158,7 +168,7 @@ public class VpnNotificationManager { String appName = context.getString(R.string.app_name); if (isEmpty(profileName)) { title = appName; - } else { + } else { title = context.getString(R.string.notifcation_title_bitmask, appName, profileName); } @@ -167,15 +177,6 @@ public class VpnNotificationManager { contentIntent = getUserInputIntent(msg); else contentIntent = getMainActivityIntent(); - - int priority; - if (OpenVPNService.NOTIFICATION_CHANNEL_NEWSTATUS_ID.equals(notificationChannelNewstatusId)) { - priority = PRIORITY_HIGH; - } else { - // background channel - priority = PRIORITY_MIN; - } - buildVpnNotification( title, msg, @@ -183,10 +184,19 @@ public class VpnNotificationManager { tickerText, status, notificationChannelNewstatusId, - priority, + PRIORITY_DEFAULT, when, contentIntent, - actionBuilder.build()); + actionBuilder.build(), + vpnServiceCallback); + } + + public void buildOpenVpnNotification(String profileName, boolean isObfuscated, String msg, String tickerText, ConnectionStatus status, long when, String notificationChannelNewstatusId) { + buildOpenVpnNotification(profileName, isObfuscated, msg, tickerText, status, when, notificationChannelNewstatusId, null); + } + + public void cancelAll() { + compatNotificationManager.cancelAll(); } @@ -197,16 +207,19 @@ public class VpnNotificationManager { } // Connection status change messages - CharSequence name = context.getString(R.string.channel_name_status); - NotificationChannel mChannel = new NotificationChannel(VoidVpnService.NOTIFICATION_CHANNEL_NEWSTATUS_ID, - name, NotificationManager.IMPORTANCE_DEFAULT); - - mChannel.setDescription(context.getString(R.string.channel_description_status)); - mChannel.enableLights(true); - - mChannel.setLightColor(Color.BLUE); - mChannel.setSound(null, null); - notificationManager.createNotificationChannel(mChannel); + NotificationChannel channel = compatNotificationManager.getNotificationChannel(VoidVpnService.NOTIFICATION_CHANNEL_NEWSTATUS_ID); + if (channel == null) { + CharSequence name = context.getString(R.string.channel_name_status); + channel = new NotificationChannel(VoidVpnService.NOTIFICATION_CHANNEL_NEWSTATUS_ID, + name, NotificationManager.IMPORTANCE_DEFAULT); + + channel.setDescription(context.getString(R.string.channel_description_status)); + channel.enableLights(true); + + channel.setLightColor(Color.BLUE); + channel.setSound(null, null); + compatNotificationManager.createNotificationChannel(channel); + } } @TargetApi(O) @@ -215,29 +228,20 @@ public class VpnNotificationManager { return; } - // Background message - CharSequence name = context.getString(R.string.channel_name_background); - NotificationChannel mChannel = new NotificationChannel(OpenVPNService.NOTIFICATION_CHANNEL_BG_ID, - name, NotificationManager.IMPORTANCE_MIN); - - mChannel.setDescription(context.getString(R.string.channel_description_background)); - mChannel.enableLights(false); - - mChannel.setLightColor(Color.DKGRAY); - notificationManager.createNotificationChannel(mChannel); - // Connection status change messages - name = context.getString(R.string.channel_name_status); - mChannel = new NotificationChannel(OpenVPNService.NOTIFICATION_CHANNEL_NEWSTATUS_ID, - name, NotificationManager.IMPORTANCE_DEFAULT); - - - mChannel.setDescription(context.getString(R.string.channel_description_status)); - mChannel.enableLights(true); - - mChannel.setLightColor(Color.BLUE); - mChannel.setSound(null, null); - notificationManager.createNotificationChannel(mChannel); + NotificationChannel channel = compatNotificationManager.getNotificationChannel(OpenVPNService.NOTIFICATION_CHANNEL_NEWSTATUS_ID); + if (channel == null) { + CharSequence name = context.getString(R.string.channel_name_status); + channel = new NotificationChannel(OpenVPNService.NOTIFICATION_CHANNEL_NEWSTATUS_ID, + name, NotificationManager.IMPORTANCE_DEFAULT); + + channel.setDescription(context.getString(R.string.channel_description_status)); + channel.enableLights(true); + + channel.setLightColor(Color.BLUE); + channel.setSound(null, null); + compatNotificationManager.createNotificationChannel(channel); + } } /** @@ -253,7 +257,9 @@ public class VpnNotificationManager { return remoteViews; } - private void buildVpnNotification(String title, String message, CharSequence bigMessage, String tickerText, ConnectionStatus status, String notificationChannelNewstatusId, int priority, long when, PendingIntent contentIntent, NotificationCompat.Action notificationAction) { + private void buildVpnNotification(String title, String message, CharSequence bigMessage, String tickerText, + ConnectionStatus status, String notificationChannelNewstatusId, int priority, + long when, PendingIntent contentIntent, NotificationCompat.Action notificationAction, VpnServiceCallback vpnServiceCallback) { NotificationCompat.Builder nCompatBuilder = new NotificationCompat.Builder(context, notificationChannelNewstatusId); int icon = getIconByConnectionStatus(status); @@ -291,16 +297,10 @@ public class VpnNotificationManager { Notification notification = nCompatBuilder.build(); int notificationId = notificationChannelNewstatusId.hashCode(); - if (!notificationChannelNewstatusId.equals(lastNotificationChannel)) { - // Cancel old notification - for (String channel : notificationChannels) { - stopNotifications(channel); - } - } - compatNotificationManager.notify(notificationId, notification); - vpnServiceCallback.onNotificationBuild(notificationId, notification); - lastNotificationChannel = notificationChannelNewstatusId; + if (vpnServiceCallback != null) { + vpnServiceCallback.onNotificationBuild(notificationId, notification); + } } private PendingIntent getMainActivityIntent() { @@ -308,6 +308,12 @@ public class VpnNotificationManager { return PendingIntent.getActivity(context, 0, startActivity, PendingIntent.FLAG_CANCEL_CURRENT); } + private PendingIntent getStartOpenvpnIntent() { + Intent startIntent = new Intent(context, EIP.class); + startIntent.setAction(EIP_ACTION_START); + return PendingIntent.getService(context, 0, startIntent, PendingIntent.FLAG_CANCEL_CURRENT); + } + private PendingIntent getStopVoidVpnIntent() { Intent stopVoidVpnIntent = new Intent (context, VoidVpnService.class); stopVoidVpnIntent.setAction(EIP_ACTION_STOP_BLOCKING_VPN); diff --git a/app/src/production/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java b/app/src/production/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java index 96d8ea69..70652365 100644 --- a/app/src/production/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java +++ b/app/src/production/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java @@ -180,7 +180,7 @@ public class ProviderApiManager extends ProviderApiManagerBase { result.putBoolean(BROADCAST_RESULT_KEY, true); } } catch (NullPointerException | JSONException e) { - setErrorResult(result, eipServiceJsonString); + setErrorResult(result, R.string.error_json_exception_user_message, null); } return result; } diff --git a/app/src/sharedTest/java/se.leap.bitmaskclient/testutils/TestSetupHelper.java b/app/src/sharedTest/java/se.leap.bitmaskclient/testutils/TestSetupHelper.java index 729c75ed..0fdc94ca 100644 --- a/app/src/sharedTest/java/se.leap.bitmaskclient/testutils/TestSetupHelper.java +++ b/app/src/sharedTest/java/se.leap.bitmaskclient/testutils/TestSetupHelper.java @@ -53,6 +53,11 @@ public class TestSetupHelper { return getProvider(null, null, null, null, null, null, null, null); } + public static Provider getConfiguredProviderAPIv4() { + return getProvider(null, null, null, null, null, "v4/riseup.net.json", "v4/riseup.service.json", null); + } + + public static Provider getProvider(String domain, String geoipUrl, String providerIp, String providerApiIp, String caCertFile, String providerJson, String eipServiceJson, String geoIpJson) { if (domain == null) domain = "https://riseup.net"; diff --git a/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java b/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java index 38449c20..dbcb86b8 100644 --- a/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java +++ b/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java @@ -63,6 +63,7 @@ import static se.leap.bitmaskclient.testutils.BackendMockResponses.BackendMockPr import static se.leap.bitmaskclient.testutils.BackendMockResponses.BackendMockProvider.TestBackendErrorCase.ERROR_CASE_UPDATED_CERTIFICATE; import static se.leap.bitmaskclient.testutils.BackendMockResponses.BackendMockProvider.TestBackendErrorCase.ERROR_GEOIP_SERVICE_IS_DOWN; import static se.leap.bitmaskclient.testutils.BackendMockResponses.BackendMockProvider.TestBackendErrorCase.NO_ERROR; +import static se.leap.bitmaskclient.testutils.BackendMockResponses.BackendMockProvider.TestBackendErrorCase.NO_ERROR_API_V4; import static se.leap.bitmaskclient.testutils.MockHelper.mockBundle; import static se.leap.bitmaskclient.testutils.MockHelper.mockClientGenerator; import static se.leap.bitmaskclient.testutils.MockHelper.mockConfigHelper; @@ -74,6 +75,7 @@ import static se.leap.bitmaskclient.testutils.MockHelper.mockResources; import static se.leap.bitmaskclient.testutils.MockHelper.mockResultReceiver; import static se.leap.bitmaskclient.testutils.MockHelper.mockTextUtils; import static se.leap.bitmaskclient.testutils.TestSetupHelper.getConfiguredProvider; +import static se.leap.bitmaskclient.testutils.TestSetupHelper.getConfiguredProviderAPIv4; import static se.leap.bitmaskclient.testutils.TestSetupHelper.getInputAsString; import static se.leap.bitmaskclient.testutils.TestSetupHelper.getProvider; @@ -559,4 +561,25 @@ public class ProviderApiManagerTest { providerApiManager.handleIntent(providerApiCommand); } + @Test + public void test_handleIntentSetupProvider_APIv4_happyPath() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, JSONException { + Provider provider = getConfiguredProviderAPIv4(); + + mockFingerprintForCertificate(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); + mockProviderApiConnector(NO_ERROR_API_V4); + providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback()); + Bundle expectedResult = mockBundle(); + + expectedResult.putBoolean(BROADCAST_RESULT_KEY, true); + expectedResult.putParcelable(PROVIDER_KEY, provider); + + Intent providerApiCommand = mockIntent(); + + providerApiCommand.putExtra(PROVIDER_KEY, provider); + providerApiCommand.setAction(ProviderAPI.SET_UP_PROVIDER); + providerApiCommand.putExtra(ProviderAPI.RECEIVER_KEY, mockResultReceiver(PROVIDER_OK, expectedResult)); + + providerApiManager.handleIntent(providerApiCommand); + } + } diff --git a/app/src/test/java/se/leap/bitmaskclient/eip/VpnConfigGeneratorTest.java b/app/src/test/java/se/leap/bitmaskclient/eip/VpnConfigGeneratorTest.java index 7d6d1715..ad6f5d7b 100644 --- a/app/src/test/java/se/leap/bitmaskclient/eip/VpnConfigGeneratorTest.java +++ b/app/src/test/java/se/leap/bitmaskclient/eip/VpnConfigGeneratorTest.java @@ -12,6 +12,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.Mock; +import org.mockito.internal.util.StringUtil; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; @@ -25,6 +26,7 @@ import se.leap.bitmaskclient.testutils.TestSetupHelper; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OPENVPN; +import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -32,6 +34,7 @@ import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.mock; import static org.powermock.api.mockito.PowerMockito.mockStatic; import static org.powermock.api.mockito.PowerMockito.when; +import static se.leap.bitmaskclient.base.models.Constants.OPENVPN_CONFIGURATION; import static se.leap.bitmaskclient.testutils.MockHelper.mockTextUtils; /** @@ -763,6 +766,257 @@ public class VpnConfigGeneratorTest { "keepalive 10 30 \n" + "tls-cipher DHE-RSA-AES128-SHA \n"; + String expectedVPNConfig_v4_ovpn_tcp_udp = "# Config for OpenVPN 2.x\n" + + "# Enables connection to GUI\n" + + "management /data/data/se.leap.bitmask/mgmtsocket unix\n" + + "management-client\n" + + "management-query-passwords\n" + + "management-hold\n" + + "\n" + + "setenv IV_GUI_VER \"se.leap.bitmaskclient 0.9.10\" \n" + + "setenv IV_PLAT_VER \"0 null JUNIT null null null\"\n" + + "machine-readable-output\n" + + "allow-recursive-routing\n" + + "ifconfig-nowarn\n" + + "client\n" + + "verb 4\n" + + "connect-retry 2 300\n" + + "resolv-retry 60\n" + + "dev tun\n" + + "remote 2001:db8:123::1056 1195 tcp-client\n" + + "remote 37.218.247.60 1195 tcp-client\n" + + "remote 2001:db8:123::1056 1195 udp\n" + + "remote 37.218.247.60 1195 udp\n" + + "<ca>\n" + + "-----BEGIN CERTIFICATE-----\n" + + "MIIFbzCCA1egAwIBAgIBATANBgkqhkiG9w0BAQ0FADBKMRgwFgYDVQQDDA9CaXRt\n" + + "YXNrIFJvb3QgQ0ExEDAOBgNVBAoMB0JpdG1hc2sxHDAaBgNVBAsME2h0dHBzOi8v\n" + + "Yml0bWFzay5uZXQwHhcNMTIxMTA2MDAwMDAwWhcNMjIxMTA2MDAwMDAwWjBKMRgw\n" + + "FgYDVQQDDA9CaXRtYXNrIFJvb3QgQ0ExEDAOBgNVBAoMB0JpdG1hc2sxHDAaBgNV\n" + + "BAsME2h0dHBzOi8vYml0bWFzay5uZXQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw\n" + + "ggIKAoICAQC1eV4YvayaU+maJbWrD4OHo3d7S1BtDlcvkIRS1Fw3iYDjsyDkZxai\n" + + "dHp4EUasfNQ+EVtXUvtk6170EmLco6Elg8SJBQ27trE6nielPRPCfX3fQzETRfvB\n" + + "7tNvGw4Jn2YKiYoMD79kkjgyZjkJ2r/bEHUSevmR09BRp86syHZerdNGpXYhcQ84\n" + + "CA1+V+603GFIHnrP+uQDdssW93rgDNYu+exT+Wj6STfnUkugyjmPRPjL7wh0tzy+\n" + + "znCeLl4xiV3g9sjPnc7r2EQKd5uaTe3j71sDPF92KRk0SSUndREz+B1+Dbe/RGk4\n" + + "MEqGFuOzrtsgEhPIX0hplhb0Tgz/rtug+yTT7oJjBa3u20AAOQ38/M99EfdeJvc4\n" + + "lPFF1XBBLh6X9UKF72an2NuANiX6XPySnJgZ7nZ09RiYZqVwu/qt3DfvLfhboq+0\n" + + "bQvLUPXrVDr70onv5UDjpmEA/cLmaIqqrduuTkFZOym65/PfAPvpGnt7crQj/Ibl\n" + + "DEDYZQmP7AS+6zBjoOzNjUGE5r40zWAR1RSi7zliXTu+yfsjXUIhUAWmYR6J3KxB\n" + + "lfsiHBQ+8dn9kC3YrUexWoOqBiqJOAJzZh5Y1tqgzfh+2nmHSB2dsQRs7rDRRlyy\n" + + "YMbkpzL9ZsOUO2eTP1mmar6YjCN+rggYjRrX71K2SpBG6b1zZxOG+wIDAQABo2Aw\n" + + "XjAdBgNVHQ4EFgQUuYGDLL2sswnYpHHvProt1JU+D48wDgYDVR0PAQH/BAQDAgIE\n" + + "MAwGA1UdEwQFMAMBAf8wHwYDVR0jBBgwFoAUuYGDLL2sswnYpHHvProt1JU+D48w\n" + + "DQYJKoZIhvcNAQENBQADggIBADeG67vaFcbITGpi51264kHPYPEWaXUa5XYbtmBl\n" + + "cXYyB6hY5hv/YNuVGJ1gWsDmdeXEyj0j2icGQjYdHRfwhrbEri+h1EZOm1cSBDuY\n" + + "k/P5+ctHyOXx8IE79DBsZ6IL61UKIaKhqZBfLGYcWu17DVV6+LT+AKtHhOrv3TSj\n" + + "RnAcKnCbKqXLhUPXpK0eTjPYS2zQGQGIhIy9sQXVXJJJsGrPgMxna1Xw2JikBOCG\n" + + "htD/JKwt6xBmNwktH0GI/LVtVgSp82Clbn9C4eZN9E5YbVYjLkIEDhpByeC71QhX\n" + + "EIQ0ZR56bFuJA/CwValBqV/G9gscTPQqd+iETp8yrFpAVHOW+YzSFbxjTEkBte1J\n" + + "aF0vmbqdMAWLk+LEFPQRptZh0B88igtx6tV5oVd+p5IVRM49poLhuPNJGPvMj99l\n" + + "mlZ4+AeRUnbOOeAEuvpLJbel4rhwFzmUiGoeTVoPZyMevWcVFq6BMkS+jRR2w0jK\n" + + "G6b0v5XDHlcFYPOgUrtsOBFJVwbutLvxdk6q37kIFnWCd8L3kmES5q4wjyFK47Co\n" + + "Ja8zlx64jmMZPg/t3wWqkZgXZ14qnbyG5/lGsj5CwVtfDljrhN0oCWK1FZaUmW3d\n" + + "69db12/g4f6phldhxiWuGC/W6fCW5kre7nmhshcltqAJJuU47iX+DarBFiIj816e\n" + + "yV8e\n" + + "-----END CERTIFICATE-----\n" + + "\n" + + "</ca>\n" + + "<key>\n" + + "-----BEGIN RSA PRIVATE KEY-----\n" + + "MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDUTYWeGgsHS+fjijmziniNqw6h\n" + + "MBpyK4S/cM6PxV28C33VuOWPTMcIYesctjZANWFCggfFTQSjV5Qaxq9UK4i27tayLbCdlVS6hpbl\n" + + "Vf4DuI3Gj1Pv1rtITBShtvCf3T7yBnjW4wVpOpsUAAOViKUSvUU3kPPMFWhiGQw8yHYr82ts6XMo\n" + + "jwMoonW5Ml4e7C7Cr22QesC63q7emNcpUd0pZGT9C33RgDAHZDMrlyjo4HEp1JbUfB0gbmXElJbE\n" + + "1TNdZ62HhgmMjzTUN1GGrQ1t91AEoEQwaK65o4YSj+yFv6KXZZz5OWaz94tKiN9v26EXtBFmRlyb\n" + + "6+D9ynSd9LghAgMBAAECggEBANPHLRXkhsHVj1EkzqBx7gXr8CEMmiTvknFh9zvltrZhhDoRQjWr\n" + + "chPDkcRHY2Cznvy4N0YyqQDD2ULIlZdSAgPxxothFoBruWSD47yMBmLx08ORsDpcqt/YvPAATJI8\n" + + "IpFNsXcyaXBp/M57oRemgnxp/8UJPJmFdWX99H4hvffh/jdj7POgYiWUaAl37XTYZKZ4nzKU2wpL\n" + + "EDLj9RKPz9gG7CYp2zrLC9LaAsrXVrKwPBw6g+XwbClaqFj97db3mrY4lr6mTo89qmus1AU+fBDH\n" + + "3Xlpmc8JwB+30TvhRNKrpLx9cEjuEj7K1gm8Y4dWCjPi+lNbtAyUBcgPJFa/81ECgYEA7pLoBU/Y\n" + + "ZYjyHFca8FvDBcBh6haHfqJr9doXWtgjDrbi3o2n5wHqfKhFWOH6vPEQozkOVeX1ze6HOiRmGBpW\n" + + "r+r7x8TD25L7I6HJw3M351RWOAfkF0w/RTVdetcTgduQtfN1u6BDhYSVceXMjyQYx7MhfETWI8Gh\n" + + "KSYm8OEDYiUCgYEA489fmbrCcUnXzpTsbswJ5NmSoEXbcX8cLxnQuzE0z9GHhQdrMjOpXR76reTW\n" + + "6jcuudarNcwRUYSWWhjCDKHhpx4HhasWPaHgr7jIzcRw8yZSJRSxKr8sl1qh6g7s47JcmfXOMWLt\n" + + "yuyE933XrT19Th4ODZHY40Uv35mPjMi9d00CgYEAyRNAQtndBRa7GG/B4Ls2T+6pl+aNJIo4e+no\n" + + "rURlp800wWabEPRocdBRQmyULBLxduBr2LIMzhgwGSz8b2wji/l9ZA3PFY135bxClVzSzUIjuO3N\n" + + "rGUzHl2wAAyuAFDSUshzfkPBJRNt8aVBF5PQ3t93ZYmPAmv8LPZe875yX5ECgYEAsUEcwK/ZNW7g\n" + + "dQPZR4iJNkC4Xu6cBZ6Cnn92swBheEYvLSoNlX0vDZ7aLE3/jzQqrjzC8NP8sbH5jtbuvgeDXZX3\n" + + "AmGRp5j6C6A61ihAPmEVz3ZfN8SSfJ3vl//PAIg6lyz0J+cy4Q7RkwSeuVQ72Hl4M8TEvmmKC3Af\n" + + "ispy6Y0CgYEAgl1o2lo+ACyk+oVQPaaPqK3d7WOBFp4eR2nXFor/vsx9igQOlZUgzRDQsR8jo1o9\n" + + "efOSBf87igrZGgssys89pWa2dnXnz5PMmzkKr6bw4D9Ez6u6Puc9UZhGw/8wDYg6fSosdB9utspm\n" + + "M698ycef7jBNMDgmhpSvfw5GctoNQ4s=\n" + + "-----END RSA PRIVATE KEY-----\n" + + "</key>\n" + + "<cert>\n" + + "-----BEGIN CERTIFICATE-----\n" + + "MIIEjDCCAnSgAwIBAgIQG6MBp/cd9DlY+7cdvp3R3jANBgkqhkiG9w0BAQsFADBmMRAwDgYDVQQK\n" + + "DAdCaXRtYXNrMRwwGgYDVQQLDBNodHRwczovL2JpdG1hc2submV0MTQwMgYDVQQDDCtCaXRtYXNr\n" + + "IFJvb3QgQ0EgKGNsaWVudCBjZXJ0aWZpY2F0ZXMgb25seSEpMB4XDTE0MTIwNTAwMDAwMFoXDTE1\n" + + "MDMwNTAwMDAwMFowLTErMCkGA1UEAwwiVU5MSU1JVEVEZDBwZDdkMzE4eTNtOHNkeXllaTFqYmZl\n" + + "eDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANRNhZ4aCwdL5+OKObOKeI2rDqEwGnIr\n" + + "hL9wzo/FXbwLfdW45Y9Mxwhh6xy2NkA1YUKCB8VNBKNXlBrGr1QriLbu1rItsJ2VVLqGluVV/gO4\n" + + "jcaPU+/Wu0hMFKG28J/dPvIGeNbjBWk6mxQAA5WIpRK9RTeQ88wVaGIZDDzIdivza2zpcyiPAyii\n" + + "dbkyXh7sLsKvbZB6wLrert6Y1ylR3SlkZP0LfdGAMAdkMyuXKOjgcSnUltR8HSBuZcSUlsTVM11n\n" + + "rYeGCYyPNNQ3UYatDW33UASgRDBorrmjhhKP7IW/opdlnPk5ZrP3i0qI32/boRe0EWZGXJvr4P3K\n" + + "dJ30uCECAwEAAaNvMG0wHQYDVR0OBBYEFK8bMVAM4GBB5sHptoIOAaIvlYueMAsGA1UdDwQEAwIH\n" + + "gDATBgNVHSUEDDAKBggrBgEFBQcDAjAJBgNVHRMEAjAAMB8GA1UdIwQYMBaAFId+E7bsWFsUWah9\n" + + "vZuPvZ7O+aJsMA0GCSqGSIb3DQEBCwUAA4ICAQAQOX81csVhvP422NKkZH7+g3npBpl+sEHedaGR\n" + + "xYPOu4HrA4TVF9h44sljRoRJyenGNdBZCXcLKHg889eePTf8Z5K3lTojp6hvwyA6tgxOMHT1kESW\n" + + "PfqnRw8mHfHJuE3g+4YNUMwggzwc/VZATdV/7M33sarVN9AUOHou9n9BizgCC+UnYlS+F2POumE3\n" + + "FbOhKo5uubI02MwBYlN2JVO2TBt1Q20w8wc6cU07Xi5Epp+1mkgFiOShkNtPcJmEyBWJhxDtSDOW\n" + + "2doqWYNqH2kq7B5R/kyyfcpFJqAnBTV7xs+C5rTS1mW7LpxfdCUMbYuLCpyxpO3A/DhAm8n47tUH\n" + + "lBtmo8Avdb8VdFpYiGBpB0o9kTFcsWFb2GkWFBduGfSEB8jUI7QtqhgZqocAKK/cweSRV8FwyUcn\n" + + "R0prRm3QEi9fbXqEddzjSY9y/lqWYzT7u+IOAQpKroeZ4wzgYperDNOUFuYk1rP7yuvjP2pV5rcN\n" + + "yPoBP60TPVWMRM4WJm6nTogAz2qBrFsf/XwT/ajzbsjT6HNB7QbRE+wkFkqspoXG5Agp7KQ8lW3L\n" + + "SKCDGOQJz7VIE85pD0tg7QEXBEw8oaRZtMjQ0Gvs25mxXAKka4wGasaWfYH6d0E+iKYcWn86V1rH\n" + + "K2ZoknT+Nno5jgjFuUR3fZseNizEfx7BteooKQ==\n" + + "-----END CERTIFICATE-----\n" + + "</cert>\n" + + "crl-verify file missing in config profile\n" + + "comp-lzo\n" + + "nobind\n"+ + "remote-cert-tls server\n" + + "data-ciphers AES-256-GCM:AES-128-GCM:AES-256-CBC\n" + + "cipher AES-256-CBC\n" + + "auth SHA1\n" + + "persist-tun\n" + + "# persist-tun also enables pre resolving to avoid DNS resolve problem\n" + + "preresolve\n" + + "# Custom configuration options\n" + + "# You are on your on own here :)\n" + + "# These options found in the config file do not map to config settings:\n" + + "keepalive 10 30 \n" + + "tls-cipher DHE-RSA-AES128-SHA \n" + + "sndbuf 0 \n" + + "rcvbuf 0 \n"; + + String expectedVPNConfig_v4_ovpn_udp_tcp = "# Config for OpenVPN 2.x\n" + + "# Enables connection to GUI\n" + + "management /data/data/se.leap.bitmask/mgmtsocket unix\n" + + "management-client\n" + + "management-query-passwords\n" + + "management-hold\n" + + "\n" + + "setenv IV_GUI_VER \"se.leap.bitmaskclient 0.9.10\" \n" + + "setenv IV_PLAT_VER \"0 null JUNIT null null null\"\n" + + "machine-readable-output\n" + + "allow-recursive-routing\n" + + "ifconfig-nowarn\n" + + "client\n" + + "verb 4\n" + + "connect-retry 2 300\n" + + "resolv-retry 60\n" + + "dev tun\n" + + "remote 2001:db8:123::1056 1195 udp\n" + + "remote 37.218.247.60 1195 udp\n" + + "remote 2001:db8:123::1056 1195 tcp-client\n" + + "remote 37.218.247.60 1195 tcp-client\n" + + "<ca>\n" + + "-----BEGIN CERTIFICATE-----\n" + + "MIIFbzCCA1egAwIBAgIBATANBgkqhkiG9w0BAQ0FADBKMRgwFgYDVQQDDA9CaXRt\n" + + "YXNrIFJvb3QgQ0ExEDAOBgNVBAoMB0JpdG1hc2sxHDAaBgNVBAsME2h0dHBzOi8v\n" + + "Yml0bWFzay5uZXQwHhcNMTIxMTA2MDAwMDAwWhcNMjIxMTA2MDAwMDAwWjBKMRgw\n" + + "FgYDVQQDDA9CaXRtYXNrIFJvb3QgQ0ExEDAOBgNVBAoMB0JpdG1hc2sxHDAaBgNV\n" + + "BAsME2h0dHBzOi8vYml0bWFzay5uZXQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw\n" + + "ggIKAoICAQC1eV4YvayaU+maJbWrD4OHo3d7S1BtDlcvkIRS1Fw3iYDjsyDkZxai\n" + + "dHp4EUasfNQ+EVtXUvtk6170EmLco6Elg8SJBQ27trE6nielPRPCfX3fQzETRfvB\n" + + "7tNvGw4Jn2YKiYoMD79kkjgyZjkJ2r/bEHUSevmR09BRp86syHZerdNGpXYhcQ84\n" + + "CA1+V+603GFIHnrP+uQDdssW93rgDNYu+exT+Wj6STfnUkugyjmPRPjL7wh0tzy+\n" + + "znCeLl4xiV3g9sjPnc7r2EQKd5uaTe3j71sDPF92KRk0SSUndREz+B1+Dbe/RGk4\n" + + "MEqGFuOzrtsgEhPIX0hplhb0Tgz/rtug+yTT7oJjBa3u20AAOQ38/M99EfdeJvc4\n" + + "lPFF1XBBLh6X9UKF72an2NuANiX6XPySnJgZ7nZ09RiYZqVwu/qt3DfvLfhboq+0\n" + + "bQvLUPXrVDr70onv5UDjpmEA/cLmaIqqrduuTkFZOym65/PfAPvpGnt7crQj/Ibl\n" + + "DEDYZQmP7AS+6zBjoOzNjUGE5r40zWAR1RSi7zliXTu+yfsjXUIhUAWmYR6J3KxB\n" + + "lfsiHBQ+8dn9kC3YrUexWoOqBiqJOAJzZh5Y1tqgzfh+2nmHSB2dsQRs7rDRRlyy\n" + + "YMbkpzL9ZsOUO2eTP1mmar6YjCN+rggYjRrX71K2SpBG6b1zZxOG+wIDAQABo2Aw\n" + + "XjAdBgNVHQ4EFgQUuYGDLL2sswnYpHHvProt1JU+D48wDgYDVR0PAQH/BAQDAgIE\n" + + "MAwGA1UdEwQFMAMBAf8wHwYDVR0jBBgwFoAUuYGDLL2sswnYpHHvProt1JU+D48w\n" + + "DQYJKoZIhvcNAQENBQADggIBADeG67vaFcbITGpi51264kHPYPEWaXUa5XYbtmBl\n" + + "cXYyB6hY5hv/YNuVGJ1gWsDmdeXEyj0j2icGQjYdHRfwhrbEri+h1EZOm1cSBDuY\n" + + "k/P5+ctHyOXx8IE79DBsZ6IL61UKIaKhqZBfLGYcWu17DVV6+LT+AKtHhOrv3TSj\n" + + "RnAcKnCbKqXLhUPXpK0eTjPYS2zQGQGIhIy9sQXVXJJJsGrPgMxna1Xw2JikBOCG\n" + + "htD/JKwt6xBmNwktH0GI/LVtVgSp82Clbn9C4eZN9E5YbVYjLkIEDhpByeC71QhX\n" + + "EIQ0ZR56bFuJA/CwValBqV/G9gscTPQqd+iETp8yrFpAVHOW+YzSFbxjTEkBte1J\n" + + "aF0vmbqdMAWLk+LEFPQRptZh0B88igtx6tV5oVd+p5IVRM49poLhuPNJGPvMj99l\n" + + "mlZ4+AeRUnbOOeAEuvpLJbel4rhwFzmUiGoeTVoPZyMevWcVFq6BMkS+jRR2w0jK\n" + + "G6b0v5XDHlcFYPOgUrtsOBFJVwbutLvxdk6q37kIFnWCd8L3kmES5q4wjyFK47Co\n" + + "Ja8zlx64jmMZPg/t3wWqkZgXZ14qnbyG5/lGsj5CwVtfDljrhN0oCWK1FZaUmW3d\n" + + "69db12/g4f6phldhxiWuGC/W6fCW5kre7nmhshcltqAJJuU47iX+DarBFiIj816e\n" + + "yV8e\n" + + "-----END CERTIFICATE-----\n" + + "\n" + + "</ca>\n" + + "<key>\n" + + "-----BEGIN RSA PRIVATE KEY-----\n" + + "MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDUTYWeGgsHS+fjijmziniNqw6h\n" + + "MBpyK4S/cM6PxV28C33VuOWPTMcIYesctjZANWFCggfFTQSjV5Qaxq9UK4i27tayLbCdlVS6hpbl\n" + + "Vf4DuI3Gj1Pv1rtITBShtvCf3T7yBnjW4wVpOpsUAAOViKUSvUU3kPPMFWhiGQw8yHYr82ts6XMo\n" + + "jwMoonW5Ml4e7C7Cr22QesC63q7emNcpUd0pZGT9C33RgDAHZDMrlyjo4HEp1JbUfB0gbmXElJbE\n" + + "1TNdZ62HhgmMjzTUN1GGrQ1t91AEoEQwaK65o4YSj+yFv6KXZZz5OWaz94tKiN9v26EXtBFmRlyb\n" + + "6+D9ynSd9LghAgMBAAECggEBANPHLRXkhsHVj1EkzqBx7gXr8CEMmiTvknFh9zvltrZhhDoRQjWr\n" + + "chPDkcRHY2Cznvy4N0YyqQDD2ULIlZdSAgPxxothFoBruWSD47yMBmLx08ORsDpcqt/YvPAATJI8\n" + + "IpFNsXcyaXBp/M57oRemgnxp/8UJPJmFdWX99H4hvffh/jdj7POgYiWUaAl37XTYZKZ4nzKU2wpL\n" + + "EDLj9RKPz9gG7CYp2zrLC9LaAsrXVrKwPBw6g+XwbClaqFj97db3mrY4lr6mTo89qmus1AU+fBDH\n" + + "3Xlpmc8JwB+30TvhRNKrpLx9cEjuEj7K1gm8Y4dWCjPi+lNbtAyUBcgPJFa/81ECgYEA7pLoBU/Y\n" + + "ZYjyHFca8FvDBcBh6haHfqJr9doXWtgjDrbi3o2n5wHqfKhFWOH6vPEQozkOVeX1ze6HOiRmGBpW\n" + + "r+r7x8TD25L7I6HJw3M351RWOAfkF0w/RTVdetcTgduQtfN1u6BDhYSVceXMjyQYx7MhfETWI8Gh\n" + + "KSYm8OEDYiUCgYEA489fmbrCcUnXzpTsbswJ5NmSoEXbcX8cLxnQuzE0z9GHhQdrMjOpXR76reTW\n" + + "6jcuudarNcwRUYSWWhjCDKHhpx4HhasWPaHgr7jIzcRw8yZSJRSxKr8sl1qh6g7s47JcmfXOMWLt\n" + + "yuyE933XrT19Th4ODZHY40Uv35mPjMi9d00CgYEAyRNAQtndBRa7GG/B4Ls2T+6pl+aNJIo4e+no\n" + + "rURlp800wWabEPRocdBRQmyULBLxduBr2LIMzhgwGSz8b2wji/l9ZA3PFY135bxClVzSzUIjuO3N\n" + + "rGUzHl2wAAyuAFDSUshzfkPBJRNt8aVBF5PQ3t93ZYmPAmv8LPZe875yX5ECgYEAsUEcwK/ZNW7g\n" + + "dQPZR4iJNkC4Xu6cBZ6Cnn92swBheEYvLSoNlX0vDZ7aLE3/jzQqrjzC8NP8sbH5jtbuvgeDXZX3\n" + + "AmGRp5j6C6A61ihAPmEVz3ZfN8SSfJ3vl//PAIg6lyz0J+cy4Q7RkwSeuVQ72Hl4M8TEvmmKC3Af\n" + + "ispy6Y0CgYEAgl1o2lo+ACyk+oVQPaaPqK3d7WOBFp4eR2nXFor/vsx9igQOlZUgzRDQsR8jo1o9\n" + + "efOSBf87igrZGgssys89pWa2dnXnz5PMmzkKr6bw4D9Ez6u6Puc9UZhGw/8wDYg6fSosdB9utspm\n" + + "M698ycef7jBNMDgmhpSvfw5GctoNQ4s=\n" + + "-----END RSA PRIVATE KEY-----\n" + + "</key>\n" + + "<cert>\n" + + "-----BEGIN CERTIFICATE-----\n" + + "MIIEjDCCAnSgAwIBAgIQG6MBp/cd9DlY+7cdvp3R3jANBgkqhkiG9w0BAQsFADBmMRAwDgYDVQQK\n" + + "DAdCaXRtYXNrMRwwGgYDVQQLDBNodHRwczovL2JpdG1hc2submV0MTQwMgYDVQQDDCtCaXRtYXNr\n" + + "IFJvb3QgQ0EgKGNsaWVudCBjZXJ0aWZpY2F0ZXMgb25seSEpMB4XDTE0MTIwNTAwMDAwMFoXDTE1\n" + + "MDMwNTAwMDAwMFowLTErMCkGA1UEAwwiVU5MSU1JVEVEZDBwZDdkMzE4eTNtOHNkeXllaTFqYmZl\n" + + "eDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANRNhZ4aCwdL5+OKObOKeI2rDqEwGnIr\n" + + "hL9wzo/FXbwLfdW45Y9Mxwhh6xy2NkA1YUKCB8VNBKNXlBrGr1QriLbu1rItsJ2VVLqGluVV/gO4\n" + + "jcaPU+/Wu0hMFKG28J/dPvIGeNbjBWk6mxQAA5WIpRK9RTeQ88wVaGIZDDzIdivza2zpcyiPAyii\n" + + "dbkyXh7sLsKvbZB6wLrert6Y1ylR3SlkZP0LfdGAMAdkMyuXKOjgcSnUltR8HSBuZcSUlsTVM11n\n" + + "rYeGCYyPNNQ3UYatDW33UASgRDBorrmjhhKP7IW/opdlnPk5ZrP3i0qI32/boRe0EWZGXJvr4P3K\n" + + "dJ30uCECAwEAAaNvMG0wHQYDVR0OBBYEFK8bMVAM4GBB5sHptoIOAaIvlYueMAsGA1UdDwQEAwIH\n" + + "gDATBgNVHSUEDDAKBggrBgEFBQcDAjAJBgNVHRMEAjAAMB8GA1UdIwQYMBaAFId+E7bsWFsUWah9\n" + + "vZuPvZ7O+aJsMA0GCSqGSIb3DQEBCwUAA4ICAQAQOX81csVhvP422NKkZH7+g3npBpl+sEHedaGR\n" + + "xYPOu4HrA4TVF9h44sljRoRJyenGNdBZCXcLKHg889eePTf8Z5K3lTojp6hvwyA6tgxOMHT1kESW\n" + + "PfqnRw8mHfHJuE3g+4YNUMwggzwc/VZATdV/7M33sarVN9AUOHou9n9BizgCC+UnYlS+F2POumE3\n" + + "FbOhKo5uubI02MwBYlN2JVO2TBt1Q20w8wc6cU07Xi5Epp+1mkgFiOShkNtPcJmEyBWJhxDtSDOW\n" + + "2doqWYNqH2kq7B5R/kyyfcpFJqAnBTV7xs+C5rTS1mW7LpxfdCUMbYuLCpyxpO3A/DhAm8n47tUH\n" + + "lBtmo8Avdb8VdFpYiGBpB0o9kTFcsWFb2GkWFBduGfSEB8jUI7QtqhgZqocAKK/cweSRV8FwyUcn\n" + + "R0prRm3QEi9fbXqEddzjSY9y/lqWYzT7u+IOAQpKroeZ4wzgYperDNOUFuYk1rP7yuvjP2pV5rcN\n" + + "yPoBP60TPVWMRM4WJm6nTogAz2qBrFsf/XwT/ajzbsjT6HNB7QbRE+wkFkqspoXG5Agp7KQ8lW3L\n" + + "SKCDGOQJz7VIE85pD0tg7QEXBEw8oaRZtMjQ0Gvs25mxXAKka4wGasaWfYH6d0E+iKYcWn86V1rH\n" + + "K2ZoknT+Nno5jgjFuUR3fZseNizEfx7BteooKQ==\n" + + "-----END CERTIFICATE-----\n" + + "</cert>\n" + + "crl-verify file missing in config profile\n" + + "comp-lzo\n" + + "nobind\n"+ + "remote-cert-tls server\n" + + "data-ciphers AES-256-GCM:AES-128-GCM:AES-256-CBC\n" + + "cipher AES-256-CBC\n" + + "auth SHA1\n" + + "persist-tun\n" + + "# persist-tun also enables pre resolving to avoid DNS resolve problem\n" + + "preresolve\n" + + "# Custom configuration options\n" + + "# You are on your on own here :)\n" + + "# These options found in the config file do not map to config settings:\n" + + "keepalive 10 30 \n" + + "tls-cipher DHE-RSA-AES128-SHA \n" + + "sndbuf 0 \n" + + "rcvbuf 0 \n"; + + @Before public void setUp() throws Exception { @@ -860,4 +1114,31 @@ public class VpnConfigGeneratorTest { System.out.println(vpnProfiles.get(OPENVPN).getConfigFile(context, false)); assertTrue(vpnProfiles.get(OPENVPN).getConfigFile(context, false).trim().equals(expectedVPNConfig_v3_ovpn_udp_tcp_defaultDataCiphers.trim())); } + + @Test + public void testGenerateVpnProfile_v4_ovpn_tcp_udp() throws Exception { + gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("v4/ptdemo_pt_tcp_udp.eip-service.json"))).getJSONArray("gateways").getJSONObject(0); + generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("v4/ptdemo_pt_tcp_udp.eip-service.json"))).getJSONObject(OPENVPN_CONFIGURATION); + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, 4); + HashMap<Connection.TransportType, VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); + assertTrue(vpnProfiles.containsKey(OBFS4)); + assertTrue(vpnProfiles.containsKey(OPENVPN)); + System.out.println(vpnProfiles.get(OPENVPN).getConfigFile(context, false)); + assertTrue(vpnProfiles.get(OPENVPN).getConfigFile(context, false).trim().equals(expectedVPNConfig_v4_ovpn_tcp_udp.trim())); + } + + @Test + public void testGenerateVpnProfile_v4_ovpn_udp_tcp() throws Exception { + gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("v4/ptdemo_pt_udp_tcp.eip-service.json"))).getJSONArray("gateways").getJSONObject(0); + generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("v4/ptdemo_pt_udp_tcp.eip-service.json"))).getJSONObject(OPENVPN_CONFIGURATION); + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, 4); + HashMap<Connection.TransportType, VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); + assertTrue(vpnProfiles.containsKey(OBFS4)); + assertTrue(vpnProfiles.containsKey(OPENVPN)); + System.out.println(vpnProfiles.get(OPENVPN).getConfigFile(context, false)); + System.out.println(expectedVPNConfig_v4_ovpn_udp_tcp.trim()); + assertTrue(vpnProfiles.get(OPENVPN).getConfigFile(context, false).trim().equals(expectedVPNConfig_v4_ovpn_udp_tcp.trim())); + } + + }
\ No newline at end of file diff --git a/app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/BackendMockProvider.java b/app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/BackendMockProvider.java index 3e8dfd5f..d76e4029 100644 --- a/app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/BackendMockProvider.java +++ b/app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/BackendMockProvider.java @@ -45,7 +45,8 @@ public class BackendMockProvider { ERROR_NO_ACCESS, ERROR_INVALID_SESSION_TOKEN, ERROR_NO_CONNECTION, - ERROR_WRONG_SRP_CREDENTIALS + ERROR_WRONG_SRP_CREDENTIALS, + NO_ERROR_API_V4 } @@ -55,6 +56,9 @@ public class BackendMockProvider { case NO_ERROR: new NoErrorBackendResponse(); break; + case NO_ERROR_API_V4: + new NoErrorBackendResponseAPIv4(); + break; case ERROR_CASE_UPDATED_CERTIFICATE: new UpdatedCertificateBackendResponse(); break; diff --git a/app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/NoErrorBackendResponseAPIv4.java b/app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/NoErrorBackendResponseAPIv4.java new file mode 100644 index 00000000..3b77834f --- /dev/null +++ b/app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/NoErrorBackendResponseAPIv4.java @@ -0,0 +1,92 @@ +/** + * 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.testutils.BackendMockResponses; + +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.io.IOException; + +import static se.leap.bitmaskclient.testutils.TestSetupHelper.getInputAsString; + +/** + * Created by cyberta on 10.01.18. + */ + +public class NoErrorBackendResponseAPIv4 extends BaseBackendResponse { + public NoErrorBackendResponseAPIv4() throws IOException { + super(); + } + + @Override + public Answer<String> getAnswerForRequestStringFromServer() { + return new Answer<String>() { + @Override + public String answer(InvocationOnMock invocation) throws Throwable { + String url = (String) invocation.getArguments()[0]; + String requestMethod = (String) invocation.getArguments()[1]; + String jsonPayload = (String) invocation.getArguments()[2]; + + if (url.contains("/provider.json")) { + //download provider json + return getInputAsString(getClass().getClassLoader().getResourceAsStream("v4/riseup.net.json")); + } else if (url.contains("/ca.crt")) { + //download provider ca cert + return getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.pem")); + } else if (url.contains("config/eip-service.json")) { + // download provider service json containing gateways, locations and openvpn settings + return getInputAsString(getClass().getClassLoader().getResourceAsStream("v4/riseup.service.json")); + } else if (url.contains(":9001/json")) { + // download geoip json, containing a sorted list of gateways + return getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.geoip.json")); + } else if (url.contains("/users.json")) { + //create new user + //TODO: implement me + } else if (url.contains("/sessions.json")) { + //srp auth: sendAToSRPServer + //TODO: implement me + } else if (url.contains("/sessions/parmegvtest10.json")){ + //srp auth: sendM1ToSRPServer + //TODO: implement me + } + + return null; + } + }; + } + + @Override + public Answer<Boolean> getAnswerForCanConnect() { + return new Answer<Boolean>() { + @Override + public Boolean answer(InvocationOnMock invocation) throws Throwable { + return true; + } + }; + } + + @Override + public Answer<Boolean> getAnswerForDelete() { + return new Answer<Boolean>() { + @Override + public Boolean answer(InvocationOnMock invocation) throws Throwable { + return true; + } + }; + } + +} diff --git a/app/src/test/resources/v4/ipv6_two_openvpn_one_pt_gateways.json b/app/src/test/resources/v4/ipv6_two_openvpn_one_pt_gateways.json new file mode 100644 index 00000000..ad91581a --- /dev/null +++ b/app/src/test/resources/v4/ipv6_two_openvpn_one_pt_gateways.json @@ -0,0 +1,123 @@ +{ + "gateways":[ + { + "capabilities":{ + "adblock":false, + "filter_dns":false, + "limited":false, + "transport":[ + { + "type":"openvpn", + "protocols":[ + "tcp" + ], + "ports":[ + "1194" + ] + } + ], + "user_ips":false + }, + "host":"pt.demo.bitmask.net", + "ip_address":"37.218.247.60", + "ip_address6":"2001:db8:123::1058", + "location":"Amsterdam" + }, + { + "capabilities":{ + "adblock":false, + "filter_dns":false, + "limited":false, + "transport":[ + { + "type":"openvpn", + "protocols":[ + "tcp" + ], + "ports":[ + "1194" + ] + } + ], + "user_ips":false + }, + "host":"moscow.bitmask.net", + "ip_address":"3.21.247.89", + "ip_address6":"2001:db8:123::1057", + "location":"moscow" + }, + { + "capabilities":{ + "adblock":false, + "filter_dns":false, + "limited":false, + "transport":[ + { + "type":"obfs4", + "protocols":[ + "tcp" + ], + "ports":[ + "23049" + ], + "options": { + "cert": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX1", + "iatMode": "0" + } + }, + { + "type":"openvpn", + "protocols":[ + "udp", + "tcp" + ], + "ports":[ + "1194" + ] + } + ], + "user_ips":false + }, + "host":"manila.bitmask.net", + "ip_address":"37.12.247.10", + "ip_address6":"2001:db8:123::1056", + "location":"manila" + } + ], + "locations":{ + "Amsterdam":{ + "country_code":"NL", + "hemisphere":"N", + "name":"Amsterdam", + "timezone":"-1" + }, + "moscow": { + "country_code": "RU", + "hemisphere": "N", + "name": "Moscow", + "timezone": "+3" + }, + "manila": { + "country_code": "PH", + "hemisphere": "N", + "name": "Manila", + "timezone": "+8" + } + }, + "openvpn_configuration":{ + "auth":"SHA1", + "cipher":"AES-256-CBC", + "keepalive":"10 30", + "tls-cipher":"DHE-RSA-AES128-SHA", + "tun-ipv6":true, + "dev" : "tun", + "sndbuf" : "0", + "rcvbuf" : "0", + "nobind" : true, + "persist-key" : true, + "key-direction" : "1", + "verb" : "3" + }, + "serial":4, + "version":4 +}
\ No newline at end of file diff --git a/app/src/test/resources/v4/ptdemo_pt_tcp_udp.eip-service.json b/app/src/test/resources/v4/ptdemo_pt_tcp_udp.eip-service.json new file mode 100644 index 00000000..61a9f003 --- /dev/null +++ b/app/src/test/resources/v4/ptdemo_pt_tcp_udp.eip-service.json @@ -0,0 +1,66 @@ +{ + "gateways":[ + { + "capabilities":{ + "adblock":false, + "filter_dns":false, + "limited":false, + "transport":[ + { + "type":"obfs4", + "protocols":[ + "tcp" + ], + "ports":[ + "23049" + ], + "options": { + "cert": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "iatMode": "0" + } + }, + { + "type":"openvpn", + "protocols":[ + "tcp", + "udp" + ], + "ports":[ + "1195" + ] + } + ], + "user_ips":false + }, + "host":"pt.demo.bitmask.net", + "ip_address":"37.218.247.60", + "ip_address6":"2001:db8:123::1056", + "location":"Amsterdam" + } + ], + "locations":{ + "Amsterdam":{ + "country_code":"NL", + "hemisphere":"N", + "name":"Amsterdam", + "timezone":"-1" + } + }, + "openvpn_configuration":{ + "auth":"SHA1", + "cipher":"AES-256-CBC", + "keepalive":"10 30", + "tls-cipher":"DHE-RSA-AES128-SHA", + "tun-ipv6":true, + "dev" : "tun", + "sndbuf" : "0", + "rcvbuf" : "0", + "nobind" : true, + "persist-key" : true, + "comp-lzo" : true, + "key-direction" : "1", + "verb" : "3" + }, + "serial":4, + "version":4 +}
\ No newline at end of file diff --git a/app/src/test/resources/v4/ptdemo_pt_udp_tcp.eip-service.json b/app/src/test/resources/v4/ptdemo_pt_udp_tcp.eip-service.json new file mode 100644 index 00000000..456967cd --- /dev/null +++ b/app/src/test/resources/v4/ptdemo_pt_udp_tcp.eip-service.json @@ -0,0 +1,66 @@ +{ + "gateways":[ + { + "capabilities":{ + "adblock":false, + "filter_dns":false, + "limited":false, + "transport":[ + { + "type":"obfs4", + "protocols":[ + "tcp" + ], + "ports":[ + "23049" + ], + "options": { + "cert": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "iatMode": "0" + } + }, + { + "type":"openvpn", + "protocols":[ + "udp", + "tcp" + ], + "ports":[ + "1195" + ] + } + ], + "user_ips":false + }, + "host":"pt.demo.bitmask.net", + "ip_address":"37.218.247.60", + "ip_address6":"2001:db8:123::1056", + "location":"Amsterdam" + } + ], + "locations":{ + "Amsterdam":{ + "country_code":"NL", + "hemisphere":"N", + "name":"Amsterdam", + "timezone":"-1" + } + }, + "openvpn_configuration":{ + "auth":"SHA1", + "cipher":"AES-256-CBC", + "keepalive":"10 30", + "tls-cipher":"DHE-RSA-AES128-SHA", + "tun-ipv6":true, + "dev" : "tun", + "sndbuf" : "0", + "rcvbuf" : "0", + "nobind" : true, + "persist-key" : true, + "comp-lzo" : true, + "key-direction" : "1", + "verb" : "3" + }, + "serial":4, + "version":4 +}
\ No newline at end of file diff --git a/app/src/test/resources/v4/riseup.geoip.json b/app/src/test/resources/v4/riseup.geoip.json new file mode 100644 index 00000000..b646052e --- /dev/null +++ b/app/src/test/resources/v4/riseup.geoip.json @@ -0,0 +1,20 @@ +{ + "ip":"51.158.144.32", + "cc":"FR", + "city":"Paris", + "lat":48.8628, + "lon":2.3292, + "gateways":[ + "mouette.riseup.net", + "hoatzin.riseup.net", + "zarapito.riseup.net", + "redshank.riseup.net", + "shag.riseup.net", + "yal.riseup.net", + "gaei.riseup.net", + "cisne.riseup.net", + "swan.riseup.net", + "garza.riseup.net", + "gaviota.riseup.net" + ] +}
\ No newline at end of file diff --git a/app/src/test/resources/v4/riseup.net.json b/app/src/test/resources/v4/riseup.net.json new file mode 100644 index 00000000..10e12df9 --- /dev/null +++ b/app/src/test/resources/v4/riseup.net.json @@ -0,0 +1,25 @@ +{ + "api_uri": "https://api.black.riseup.net:443", + "api_version": "1", + "ca_cert_fingerprint": "SHA256: a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494", + "ca_cert_uri": "https://black.riseup.net/ca.crt", + "default_language": "en", + "description": { + "en": "Riseup is a non-profit collective in Seattle that provides online communication tools for people and groups working toward liberatory social change." + }, + "domain": "riseup.net", + "enrollment_policy": "open", + "languages": [ + "en" + ], + "name": { + "en": "Riseup Networks" + }, + "service": { + "allow_anonymous": false, + "allow_registration": true + }, + "services": [ + "openvpn" + ] +}
\ No newline at end of file diff --git a/app/src/test/resources/v4/riseup.service.json b/app/src/test/resources/v4/riseup.service.json new file mode 100644 index 00000000..c27f8e18 --- /dev/null +++ b/app/src/test/resources/v4/riseup.service.json @@ -0,0 +1,96 @@ +{ + "gateways":[ + { + "capabilities":{ + "adblock":false, + "filter_dns":false, + "limited":false, + "ports":[ + "443" + ], + "protocols":[ + "tcp" + ], + "transport":[ + "openvpn" + ], + "user_ips":false + }, + "host":"garza.riseup.net", + "ip_address":"198.252.153.28", + "ip_address6":"2001:db8:123::1056", + "location":"seattle" + }, + { + "capabilities":{ + "adblock":false, + "filter_dns":false, + "limited":false, + "ports":[ + "443" + ], + "protocols":[ + "tcp" + ], + "transport":[ + "openvpn" + ], + "user_ips":false + }, + "host":"tenca.riseup.net", + "ip_address":"5.79.86.180", + "ip_address6":"2001:db8:123::1056", + "location":"amsterdam" + }, + { + "capabilities":{ + "adblock":false, + "filter_dns":false, + "limited":false, + "ports":[ + "443" + ], + "protocols":[ + "tcp" + ], + "transport":[ + "openvpn" + ], + "user_ips":false + }, + "host":"yal.riseup.net", + "ip_address":"199.58.81.145", + "ip_address6":"2001:db8:123::1056", + "location":"montreal" + } + ], + "locations":{ + "amsterdam":{ + "country_code":"NL", + "hemisphere":"N", + "name":"Amsterdam", + "timezone":"+2" + }, + "montreal":{ + "country_code":"CA", + "hemisphere":"N", + "name":"Montreal", + "timezone":"-5" + }, + "seattle":{ + "country_code":"US", + "hemisphere":"N", + "name":"Seattle", + "timezone":"-7" + } + }, + "openvpn_configuration":{ + "auth": "SHA512", + "cipher": "AES-128-CBC", + "keepalive": "10 30", + "tls-cipher": "TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384", + "tun-ipv6": true + }, + "serial":4, + "version":4 +}
\ No newline at end of file diff --git a/build.gradle b/build.gradle index bb336511..a995fd04 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:3.4.1' + classpath 'com.android.tools.build:gradle:4.1.2' } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e264661d..5e689c00 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sun May 19 00:15:53 CEST 2019 +#Thu Feb 11 23:53:25 CET 2021 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip |