From db1e1a2045a2e6456d54765be3cf95186ce987f7 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Fri, 24 May 2019 18:01:03 +0200 Subject: squashed commit for Pluggable Transports * implement handling of different provider API version (v1 and v2) * detect provider's obfs support * shapeshifter-dispatcher installation * necessary changes to control shapeshifter-dispatcher from Bitmask * route openvpn traffic over shapeshifter-dispatcher --- .../main/java/se/leap/bitmaskclient/Constants.java | 18 ++ .../java/se/leap/bitmaskclient/StartActivity.java | 85 +++++++- .../java/se/leap/bitmaskclient/eip/Gateway.java | 73 ++++--- .../se/leap/bitmaskclient/eip/GatewaySelector.java | 2 +- .../se/leap/bitmaskclient/eip/GatewaysManager.java | 41 +++- .../leap/bitmaskclient/eip/VpnConfigGenerator.java | 177 +++++++++++++--- .../pluggableTransports/BinaryInstaller.java | 204 ++++++++++++++++++ .../pluggableTransports/Dispatcher.java | 229 +++++++++++++++++++++ 8 files changed, 754 insertions(+), 75 deletions(-) create mode 100644 app/src/main/java/se/leap/bitmaskclient/pluggableTransports/BinaryInstaller.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Dispatcher.java (limited to 'app/src/main/java/se') diff --git a/app/src/main/java/se/leap/bitmaskclient/Constants.java b/app/src/main/java/se/leap/bitmaskclient/Constants.java index 42df6d1d..7503d29f 100644 --- a/app/src/main/java/se/leap/bitmaskclient/Constants.java +++ b/app/src/main/java/se/leap/bitmaskclient/Constants.java @@ -113,4 +113,22 @@ public interface Constants { String FIRST_TIME_USER_DATE = "first_time_user_date"; + ////////////////////////////////////////////// + // JSON KEYS + ///////////////////////////////////////////// + String IP_ADDRESS = "ip_address"; + String REMOTE = "remote"; + String PORTS = "ports"; + String PROTOCOLS = "protocols"; + String CAPABILITIES = "capabilities"; + String TRANSPORT = "transport"; + String TYPE = "type"; + String OPTIONS = "options"; + String VERSION = "version"; + String NAME = "name"; + String TIMEZONE = "timezone"; + String LOCATIONS = "locations"; + String LOCATION = "location"; + String OPENVPN_CONFIGURATION = "openvpn_configuration"; + String GATEWAYS = "gateways"; } diff --git a/app/src/main/java/se/leap/bitmaskclient/StartActivity.java b/app/src/main/java/se/leap/bitmaskclient/StartActivity.java index d8aca351..945429fd 100644 --- a/app/src/main/java/se/leap/bitmaskclient/StartActivity.java +++ b/app/src/main/java/se/leap/bitmaskclient/StartActivity.java @@ -25,20 +25,36 @@ import android.support.annotation.IntDef; import android.support.annotation.Nullable; import android.util.Log; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import de.blinkt.openvpn.core.Preferences; import de.blinkt.openvpn.core.VpnStatus; import se.leap.bitmaskclient.eip.EipCommand; +import se.leap.bitmaskclient.utils.PreferenceHelper; +import static se.leap.bitmaskclient.BuildConfig.useDemoConfig; import static se.leap.bitmaskclient.Constants.APP_ACTION_CONFIGURE_ALWAYS_ON_PROFILE; import static se.leap.bitmaskclient.Constants.EIP_RESTART_ON_BOOT; import static se.leap.bitmaskclient.Constants.PREFERENCES_APP_VERSION; +import static se.leap.bitmaskclient.Constants.PROVIDER_CONFIGURED; import static se.leap.bitmaskclient.Constants.PROVIDER_EIP_DEFINITION; import static se.leap.bitmaskclient.Constants.PROVIDER_KEY; +import static se.leap.bitmaskclient.Constants.PROVIDER_PRIVATE_KEY; +import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE; import static se.leap.bitmaskclient.Constants.REQUEST_CODE_CONFIGURE_LEAP; import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; import static se.leap.bitmaskclient.MainActivity.ACTION_SHOW_VPN_FRAGMENT; +import static se.leap.bitmaskclient.Provider.CA_CERT; +import static se.leap.bitmaskclient.Provider.MAIN_URL; import static se.leap.bitmaskclient.utils.ConfigHelper.isDefaultBitmask; import static se.leap.bitmaskclient.utils.PreferenceHelper.getSavedProviderFromSharedPreferences; import static se.leap.bitmaskclient.utils.PreferenceHelper.providerInSharedPreferences; @@ -90,6 +106,10 @@ public class StartActivity extends Activity{ // initialize app necessities VpnStatus.initLogCache(getApplicationContext().getCacheDir()); + if (useDemoConfig) { + demoSetup(); + } + prepareEIP(); } @@ -162,8 +182,8 @@ public class StartActivity extends Activity{ } private void prepareEIP() { - boolean provider_exists = providerInSharedPreferences(preferences); - if (provider_exists) { + boolean providerExists = providerInSharedPreferences(preferences); + if (providerExists) { Provider provider = getSavedProviderFromSharedPreferences(preferences); if(!provider.isConfigured()) { configureLeapProvider(); @@ -216,4 +236,65 @@ public class StartActivity extends Activity{ finish(); } + private String getInputAsString(InputStream fileAsInputStream) throws IOException { + BufferedReader br = new BufferedReader(new InputStreamReader(fileAsInputStream)); + StringBuilder sb = new StringBuilder(); + String line = br.readLine(); + while (line != null) { + sb.append(line); + line = br.readLine(); + } + + return sb.toString(); + } + + private void demoSetup() { + try { + //set demo data + String demoEipServiceJson = getInputAsString(getAssets().open("ptdemo.bitmask.eip-service.json")); + String secrets = getInputAsString(getAssets().open("ptdemo.bitmask.secrets.json")); + String provider = getInputAsString(getAssets().open("ptdemo.bitmask.net.json")); + + Log.d(TAG, "setup provider: " + provider); + Log.d(TAG, "setup eip json: " + demoEipServiceJson); + JSONObject secretsJson = new JSONObject(secrets); + + preferences.edit().putString(PROVIDER_EIP_DEFINITION+".demo.bitmask.net", demoEipServiceJson). + putString(PROVIDER_EIP_DEFINITION, demoEipServiceJson). + putString(CA_CERT, secretsJson.getString(CA_CERT)). + putString(PROVIDER_PRIVATE_KEY, secretsJson.getString(PROVIDER_PRIVATE_KEY)). + putString(PROVIDER_VPN_CERTIFICATE, secretsJson.getString(PROVIDER_VPN_CERTIFICATE)). + putString(Provider.KEY, provider). + putString(MAIN_URL, "https://demo.bitmask.net"). + putBoolean(PROVIDER_CONFIGURED, true).commit(); + + PreferenceHelper.getSavedProviderFromSharedPreferences(preferences); + ProviderObservable.getInstance().updateProvider(PreferenceHelper.getSavedProviderFromSharedPreferences(preferences)); + + // remove last used profiles + SharedPreferences prefs = Preferences.getDefaultSharedPreferences(this); + SharedPreferences.Editor prefsedit = prefs.edit(); + prefsedit.remove("lastConnectedProfile").commit(); + File f = new File(this.getCacheDir().getAbsolutePath() + "/android.conf"); + if (f.exists()) { + Log.d(TAG, "android.conf exists -> delete:" + f.delete()); + } + + File filesDirectory = new File(this.getFilesDir().getAbsolutePath()); + if (filesDirectory.exists() && filesDirectory.isDirectory()) { + File[] filesInDirectory = filesDirectory.listFiles(); + for (File file : filesInDirectory) { + Log.d(TAG, "delete profile: " + file.getName() + ": "+ file.delete()); + + } + } else Log.d(TAG, "file folder doesn't exist"); + + } catch (IOException e) { + e.printStackTrace(); + } catch (JSONException e) { + e.printStackTrace(); + } + + } + } 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 55ade1ae..b1554af0 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java @@ -22,11 +22,17 @@ import org.json.JSONException; import org.json.JSONObject; import java.io.IOException; -import java.io.StringReader; import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.core.ConfigParser; +import static se.leap.bitmaskclient.Constants.LOCATION; +import static se.leap.bitmaskclient.Constants.LOCATIONS; +import static se.leap.bitmaskclient.Constants.NAME; +import static se.leap.bitmaskclient.Constants.OPENVPN_CONFIGURATION; +import static se.leap.bitmaskclient.Constants.TIMEZONE; +import static se.leap.bitmaskclient.Constants.VERSION; + /** * Gateway provides objects defining gateways and their metadata. * Each instance contains a VpnProfile for OpenVPN specific data and member @@ -34,6 +40,7 @@ import de.blinkt.openvpn.core.ConfigParser; * * @author Sean Leonard * @author Parménides GV + * @author cyberta */ public class Gateway { @@ -44,50 +51,57 @@ public class Gateway { private JSONObject secrets; private JSONObject gateway; - private String mName; + private String name; private int timezone; - private VpnProfile mVpnProfile; + private int apiVersion; + private VpnProfile vpnProfile; /** * Build a gateway object from a JSON OpenVPN gateway definition in eip-service.json * and create a VpnProfile belonging to it. */ - public Gateway(JSONObject eip_definition, JSONObject secrets, JSONObject gateway) { + public Gateway(JSONObject eipDefinition, JSONObject secrets, JSONObject gateway) { this.gateway = gateway; this.secrets = secrets; - generalConfiguration = getGeneralConfiguration(eip_definition); - timezone = getTimezone(eip_definition); - mName = locationAsName(eip_definition); - - mVpnProfile = createVPNProfile(); - mVpnProfile.mName = mName; + generalConfiguration = getGeneralConfiguration(eipDefinition); + timezone = getTimezone(eipDefinition); + name = locationAsName(eipDefinition); + apiVersion = getApiVersion(eipDefinition); + vpnProfile = createVPNProfile(); + if (vpnProfile != null) { + vpnProfile.mName = name; + } } - private JSONObject getGeneralConfiguration(JSONObject eip_definition) { + private JSONObject getGeneralConfiguration(JSONObject eipDefinition) { try { - return eip_definition.getJSONObject("openvpn_configuration"); + return eipDefinition.getJSONObject(OPENVPN_CONFIGURATION); } catch (JSONException e) { return new JSONObject(); } } - private int getTimezone(JSONObject eip_definition) { - JSONObject location = getLocationInfo(eip_definition); - return location.optInt("timezone"); + private int getTimezone(JSONObject eipDefinition) { + JSONObject location = getLocationInfo(eipDefinition); + return location.optInt(TIMEZONE); + } + + private int getApiVersion(JSONObject eipDefinition) { + return eipDefinition.optInt(VERSION); } - private String locationAsName(JSONObject eip_definition) { - JSONObject location = getLocationInfo(eip_definition); - return location.optString("name"); + private String locationAsName(JSONObject eipDefinition) { + JSONObject location = getLocationInfo(eipDefinition); + return location.optString(NAME); } private JSONObject getLocationInfo(JSONObject eipDefinition) { try { - JSONObject locations = eipDefinition.getJSONObject("locations"); + JSONObject locations = eipDefinition.getJSONObject(LOCATIONS); - return locations.getJSONObject(gateway.getString("location")); + return locations.getJSONObject(gateway.getString(LOCATION)); } catch (JSONException e) { return new JSONObject(); } @@ -98,18 +112,9 @@ public class Gateway { */ private VpnProfile createVPNProfile() { try { - ConfigParser cp = new ConfigParser(); - - VpnConfigGenerator vpnConfigurationGenerator = new VpnConfigGenerator(generalConfiguration, secrets, gateway); - String configuration = vpnConfigurationGenerator.generate(); - - cp.parseConfig(new StringReader(configuration)); - return cp.convertProfile(); - } catch (ConfigParser.ConfigParseError e) { - // FIXME We didn't get a VpnProfile! Error handling! and log level - e.printStackTrace(); - return null; - } catch (IOException e) { + VpnConfigGenerator vpnConfigurationGenerator = new VpnConfigGenerator(generalConfiguration, secrets, gateway, apiVersion); + return vpnConfigurationGenerator.generateVpnProfile(); + } catch (ConfigParser.ConfigParseError | IOException | CloneNotSupportedException | JSONException e) { // FIXME We didn't get a VpnProfile! Error handling! and log level e.printStackTrace(); return null; @@ -117,11 +122,11 @@ public class Gateway { } public String getName() { - return mName; + return name; } public VpnProfile getProfile() { - return mVpnProfile; + return vpnProfile; } public int getTimezone() { diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaySelector.java b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaySelector.java index 2bd666bf..0ba0f207 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaySelector.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaySelector.java @@ -36,7 +36,7 @@ public class GatewaySelector { } } - Log.e(TAG, "There are less than " + nClosest + " Gateways available."); + Log.e(TAG, "There are less than " + (nClosest + 1) + " Gateways available."); return null; } diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java index 060843fd..c650938c 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java @@ -30,11 +30,18 @@ import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; +import de.blinkt.openvpn.VpnProfile; +import de.blinkt.openvpn.core.connection.Connection; import se.leap.bitmaskclient.Provider; import se.leap.bitmaskclient.utils.PreferenceHelper; +import static se.leap.bitmaskclient.Constants.CAPABILITIES; +import static se.leap.bitmaskclient.Constants.GATEWAYS; import static se.leap.bitmaskclient.Constants.PROVIDER_PRIVATE_KEY; import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.Constants.TRANSPORT; +import static se.leap.bitmaskclient.Constants.TYPE; +import static se.leap.bitmaskclient.Constants.VERSION; /** * @author parmegv @@ -88,10 +95,11 @@ public class GatewaysManager { */ void fromEipServiceJson(JSONObject eipDefinition) { try { - JSONArray gatewaysDefined = eipDefinition.getJSONArray("gateways"); + JSONArray gatewaysDefined = eipDefinition.getJSONArray(GATEWAYS); + int apiVersion = eipDefinition.getInt(VERSION); for (int i = 0; i < gatewaysDefined.length(); i++) { JSONObject gw = gatewaysDefined.getJSONObject(i); - if (isOpenVpnGateway(gw)) { + if (isOpenVpnGateway(gw, apiVersion)) { JSONObject secrets = secretsConfiguration(); Gateway aux = new Gateway(eipDefinition, secrets, gw); if (!gateways.contains(aux)) { @@ -110,12 +118,29 @@ public class GatewaysManager { * @param gateway to check * @return true if gateway is an OpenVpn gateway otherwise false */ - private boolean isOpenVpnGateway(JSONObject gateway) { - try { - String transport = gateway.getJSONObject("capabilities").getJSONArray("transport").toString(); - return transport.contains("openvpn"); - } catch (JSONException e) { - return false; + private boolean isOpenVpnGateway(JSONObject gateway, int apiVersion) { + switch (apiVersion) { + default: + case 1: + try { + String transport = gateway.getJSONObject(CAPABILITIES).getJSONArray(TRANSPORT).toString(); + return transport.contains("openvpn"); + } catch (JSONException e) { + return false; + } + case 2: + try { + JSONArray transports = gateway.getJSONObject(CAPABILITIES).getJSONArray(TRANSPORT); + for (int i = 0; i < transports.length(); i++) { + JSONObject transport = transports.getJSONObject(i); + if (transport.optString(TYPE).equals("openvpn")) { + return true; + } + } + return false; + } catch (JSONException e) { + return false; + } } } 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 6f0ccf18..7f09d21e 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java @@ -20,48 +20,133 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import java.io.IOException; +import java.io.StringReader; import java.util.Iterator; +import de.blinkt.openvpn.VpnProfile; +import de.blinkt.openvpn.core.ConfigParser; +import de.blinkt.openvpn.core.connection.Connection; +import de.blinkt.openvpn.core.connection.Obfs4Connection; import se.leap.bitmaskclient.Provider; +import static se.leap.bitmaskclient.Constants.CAPABILITIES; +import static se.leap.bitmaskclient.Constants.IP_ADDRESS; +import static se.leap.bitmaskclient.Constants.OPTIONS; +import static se.leap.bitmaskclient.Constants.PORTS; +import static se.leap.bitmaskclient.Constants.PROTOCOLS; import static se.leap.bitmaskclient.Constants.PROVIDER_PRIVATE_KEY; import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.Constants.REMOTE; +import static se.leap.bitmaskclient.Constants.TRANSPORT; +import static se.leap.bitmaskclient.Constants.TYPE; public class VpnConfigGenerator { - private JSONObject general_configuration; + private JSONObject generalConfiguration; private JSONObject gateway; private JSONObject secrets; + private JSONObject obfs4Transport; + private int apiVersion; + + private ConfigParser icsOpenvpnConfigParser = new ConfigParser(); + public final static String TAG = VpnConfigGenerator.class.getSimpleName(); private final String newLine = System.getProperty("line.separator"); // Platform new line - public VpnConfigGenerator(JSONObject general_configuration, JSONObject secrets, JSONObject gateway) { - this.general_configuration = general_configuration; + public VpnConfigGenerator(JSONObject generalConfiguration, JSONObject secrets, JSONObject gateway, int apiVersion) { + this.generalConfiguration = generalConfiguration; this.gateway = gateway; this.secrets = secrets; + this.apiVersion = apiVersion; + checkCapabilities(); } - public String generate() { - return - generalConfiguration() + public void checkCapabilities() { + + try { + switch (apiVersion) { + case 2: + JSONArray supportedTransports = gateway.getJSONObject(CAPABILITIES).getJSONArray(TRANSPORT); + for (int i = 0; i < supportedTransports.length(); i++) { + JSONObject transport = supportedTransports.getJSONObject(i); + if (transport.getString(TYPE).equals("obfs4")) { + obfs4Transport = transport; + } + } + break; + default: + break; + } + + } catch (JSONException e) { + e.printStackTrace(); + } + } + + public VpnProfile generateVpnProfile() throws IllegalStateException, + IOException, + ConfigParser.ConfigParseError, + CloneNotSupportedException, + JSONException, + NumberFormatException { + + VpnProfile profile = createOvpnProfile(); + if (supportsObfs4()) { + addPluggableTransportConnections(profile); + } + return profile; + } + + private boolean supportsObfs4(){ + return obfs4Transport != null; + } + + private void addPluggableTransportConnections(VpnProfile profile) throws JSONException, CloneNotSupportedException { + JSONArray ports = obfs4Transport.getJSONArray(PORTS); + Connection[] updatedConnections = new Connection[profile.mConnections.length + ports.length()]; + + for (int i = 0; i < ports.length(); i++) { + String port = ports.getString(i); + Obfs4Connection obfs4Connection = new Obfs4Connection(); + obfs4Connection.setObfs4RemoteProxyName(gateway.getString(IP_ADDRESS)); + obfs4Connection.setObfs4RemoteProxyPort(port); + obfs4Connection.setTransportOptions(obfs4Transport.optJSONObject(OPTIONS)); + updatedConnections[i] = obfs4Connection; + } + int k = 0; + for (int i = ports.length(); i < updatedConnections.length; i++, k++) { + updatedConnections[i] = profile.mConnections[k].clone(); + } + profile.mConnections = updatedConnections; + } + + private String getConfigurationString() { + return generalConfiguration() + newLine - + gatewayConfiguration() + + ovpnGatewayConfiguration() + newLine + secretsConfiguration() + newLine + androidCustomizations(); } + private VpnProfile createOvpnProfile() throws IOException, ConfigParser.ConfigParseError { + String configuration = getConfigurationString(); + icsOpenvpnConfigParser.parseConfig(new StringReader(configuration)); + return icsOpenvpnConfigParser.convertProfile(); + } + private String generalConfiguration() { String commonOptions = ""; try { - Iterator keys = general_configuration.keys(); + Iterator keys = generalConfiguration.keys(); while (keys.hasNext()) { String key = keys.next().toString(); commonOptions += key + " "; - for (String word : String.valueOf(general_configuration.get(key)).split(" ")) + for (String word : String.valueOf(generalConfiguration.get(key)).split(" ")) commonOptions += word + " "; commonOptions += newLine; @@ -76,41 +161,73 @@ public class VpnConfigGenerator { return commonOptions; } - private String gatewayConfiguration() { + private String ovpnGatewayConfiguration() { String remotes = ""; - String ipAddressKeyword = "ip_address"; - String remoteKeyword = "remote"; - String portsKeyword = "ports"; - String protocolKeyword = "protocols"; - String capabilitiesKeyword = "capabilities"; - + StringBuilder stringBuilder = new StringBuilder(); try { - String ip_address = gateway.getString(ipAddressKeyword); - JSONObject capabilities = gateway.getJSONObject(capabilitiesKeyword); - JSONArray ports = capabilities.getJSONArray(portsKeyword); - for (int i = 0; i < ports.length(); i++) { - String port_specific_remotes = ""; - int port = ports.getInt(i); - JSONArray protocols = capabilities.getJSONArray(protocolKeyword); - for (int j = 0; j < protocols.length(); j++) { - String protocol = protocols.optString(j); - String new_remote = remoteKeyword + " " + ip_address + " " + port + " " + protocol + newLine; - - port_specific_remotes += new_remote; - } - remotes += port_specific_remotes; + String ipAddress = gateway.getString(IP_ADDRESS); + JSONObject capabilities = gateway.getJSONObject(CAPABILITIES); + JSONArray transports = capabilities.getJSONArray(TRANSPORT); + switch (apiVersion) { + default: + case 1: + ovpnGatewayConfigApiv1(stringBuilder, ipAddress, capabilities); + break; + case 2: + ovpnGatewayConfigApiv2(stringBuilder, ipAddress, transports); + break; } } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } + + remotes = stringBuilder.toString(); if (remotes.endsWith(newLine)) { remotes = remotes.substring(0, remotes.lastIndexOf(newLine)); } return remotes; } + private void ovpnGatewayConfigApiv1(StringBuilder stringBuilder, String ipAddress, JSONObject capabilities) throws JSONException { + int port; + String protocol; + + JSONArray ports = capabilities.getJSONArray(PORTS); + for (int i = 0; i < ports.length(); i++) { + port = ports.getInt(i); + JSONArray protocols = capabilities.getJSONArray(PROTOCOLS); + for (int j = 0; j < protocols.length(); j++) { + protocol = protocols.optString(j); + String newRemote = REMOTE + " " + ipAddress + " " + port + " " + protocol + newLine; + stringBuilder.append(newRemote); + } + } + } + + private void ovpnGatewayConfigApiv2(StringBuilder stringBuilder, String ipAddress, JSONArray transports) throws JSONException { + String port; + String protocol; + for (int i = 0; i < transports.length(); i++) { + JSONObject transport = transports.getJSONObject(i); + if (!transport.getString(TYPE).equals("openvpn")) { + continue; + } + JSONArray ports = transport.getJSONArray(PORTS); + for (int j = 0; j < ports.length(); j++) { + port = ports.getString(j); + JSONArray protocols = transport.getJSONArray(PROTOCOLS); + for (int k = 0; k < protocols.length(); k++) { + protocol = protocols.optString(k); + String newRemote = REMOTE + " " + ipAddress + " " + port + " " + protocol + newLine; + stringBuilder.append(newRemote); + } + } + } + } + + private String secretsConfiguration() { try { String ca = diff --git a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/BinaryInstaller.java b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/BinaryInstaller.java new file mode 100644 index 00000000..0d6aa61e --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/BinaryInstaller.java @@ -0,0 +1,204 @@ +/* Copyright (c) 2009, Nathan Freitas, Orbot / The Guardian Project - http://openideals.com/guardian */ +/* See LICENSE for licensing information */ + +package se.leap.bitmaskclient.pluggableTransports; + +import android.content.Context; +import android.util.Log; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.concurrent.TimeoutException; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +public class BinaryInstaller { + + File installFolder; + Context context; + + public BinaryInstaller(Context context, File installFolder) + { + this.installFolder = installFolder; + + this.context = context; + } + + public void deleteDirectory(File file) { + if( file.exists() ) { + if (file.isDirectory()) { + File[] files = file.listFiles(); + for(int i=0; i 0) + { + + stmOut.write(buffer, 0, bytecount); + + } + + stmOut.close(); + stm.close(); + + if (zis != null) + zis.close(); + + + return true; + + } + + //copy the file from inputstream to File output - alternative impl + public static boolean copyFile (InputStream is, File outputFile) + { + + try { + if (outputFile.exists()) + outputFile.delete(); + + boolean newFile = outputFile.createNewFile(); + DataOutputStream out = new DataOutputStream(new FileOutputStream(outputFile)); + DataInputStream in = new DataInputStream(is); + + int b = -1; + byte[] data = new byte[1024]; + + while ((b = in.read(data)) != -1) { + out.write(data); + } + + if (b == -1); //rejoice + + // + out.flush(); + out.close(); + in.close(); + // chmod? + + return newFile; + + + } catch (IOException ex) { + Log.e("Binaryinstaller", "error copying binary", ex); + return false; + } + + } + + /** + * Copies a raw resource file, given its ID to the given location + * @param ctx context + * @param resid resource id + * @param file destination file + * @param mode file permissions (E.g.: "755") + * @throws IOException on error + * @throws InterruptedException when interrupted + */ + public static void copyRawFile(Context ctx, int resid, File file, String mode, boolean isZipd) throws IOException, InterruptedException + { + final String abspath = file.getAbsolutePath(); + // Write the iptables binary + final FileOutputStream out = new FileOutputStream(file); + InputStream is = ctx.getResources().openRawResource(resid); + + if (isZipd) + { + ZipInputStream zis = new ZipInputStream(is); + ZipEntry ze = zis.getNextEntry(); + is = zis; + } + + byte buf[] = new byte[1024]; + int len; + while ((len = is.read(buf)) > 0) { + out.write(buf, 0, len); + } + out.close(); + is.close(); + // Change the permissions + Runtime.getRuntime().exec("chmod "+mode+" "+abspath).waitFor(); + } + + + private void setExecutable(File fileBin) { + fileBin.setReadable(true); + fileBin.setExecutable(true); + fileBin.setWritable(false); + fileBin.setWritable(true, true); + } + +} diff --git a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Dispatcher.java b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Dispatcher.java new file mode 100644 index 00000000..ac846fd9 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Dispatcher.java @@ -0,0 +1,229 @@ +/** + * Copyright (c) 2019 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 . + */ +package se.leap.bitmaskclient.pluggableTransports; + +import android.content.Context; +import android.support.annotation.WorkerThread; +import android.util.Log; + +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.util.StringTokenizer; + + +/** + * Created by cyberta on 22.02.19. + */ + +public class Dispatcher { + private static final String ASSET_KEY = "piedispatcher"; + private static final String TAG = Dispatcher.class.getName(); + private final String remoteIP; + private final String remotePort; + private final String certificate; + private final String iatMode; + private File fileDispatcher; + private Context context; + private String port = ""; + private Thread dispatcherThread = null; + private int dipatcherPid = -1; + + public Dispatcher(Context context, String remoteIP, String remotePort, String certificate, String iatMode) { + this.context = context.getApplicationContext(); + this.remoteIP = remoteIP; + this.remotePort = remotePort; + this.certificate = certificate; + this.iatMode = iatMode; + } + + @WorkerThread + public void initSync() { + try { + fileDispatcher = installDispatcher(); + + // start dispatcher + dispatcherThread = new Thread(() -> { + try { + StringBuilder dispatcherLog = new StringBuilder(); + String dispatcherCommand = fileDispatcher.getCanonicalPath() + + " -transparent" + + " -client" + + " -state " + context.getFilesDir().getCanonicalPath() + "/state" + + " -target " + remoteIP + ":" + remotePort + + " -transports obfs4" + + " -options \"" + String.format("{\\\"cert\\\": \\\"%s\\\", \\\"iatMode\\\": \\\"%s\\\"}\"", certificate, iatMode) + + " -logLevel DEBUG -enableLogging"; + + runBlockingCmd(new String[]{dispatcherCommand}, dispatcherLog); + } catch (IOException e) { + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } + }); + dispatcherThread.start(); + + // get pid of dispatcher + StringBuilder log = new StringBuilder(); + String pidCommand = "ps | grep " + fileDispatcher.getCanonicalPath(); + runBlockingCmd(new String[]{pidCommand}, log); + String output = log.toString(); + StringTokenizer st = new StringTokenizer(output, " "); + st.nextToken(); // proc owner + dipatcherPid = Integer.parseInt(st.nextToken().trim()); + + // get open port of dispatcher + String getPortCommand = "cat " + context.getFilesDir().getCanonicalPath() + "/state/dispatcher.log | grep \"obfs4 - registered listener\""; + long timeout = System.currentTimeMillis() + 5000; + int i = 1; + while (this.port.length() == 0 && System.currentTimeMillis() < timeout) { + log = new StringBuilder(); + Log.d(TAG, i + ". try to get port"); + runBlockingCmd(new String[]{getPortCommand}, log); + output = log.toString(); + if (output.length() > 0) { + Log.d(TAG, "dispatcher log: \n =================\n" + output); + } + + String dispatcherLog[] = output.split(" "); + if (dispatcherLog.length > 0) { + String localAddressAndPort = dispatcherLog[dispatcherLog.length - 1]; + if (localAddressAndPort.contains(":")) { + this.port = localAddressAndPort.split(":")[1].replace(System.getProperty("line.separator"), ""); + Log.d(TAG, "local port is: " + this.port); + } + } + i += 1; + } + + } catch(Exception e){ + if (dispatcherThread.isAlive()) { + Log.e(TAG, e.getMessage() + ". Shutting down Dispatcher thread."); + stop(); + } + } + } + + public String getPort() { + return port; + } + + public void stop() { + Log.d(TAG, "Shutting down Dispatcher thread."); + if (dispatcherThread != null && dispatcherThread.isAlive()) { + try { + killProcess(dipatcherPid); + } catch (Exception e) { + e.printStackTrace(); + } + dispatcherThread.interrupt(); + } + } + + private void killProcess(int pid) throws Exception { + String killPid = "kill -9 " + pid; + runCmd(new String[]{killPid}, null, false); + } + + public boolean isRunning() { + return dispatcherThread != null && dispatcherThread.isAlive(); + } + + private File installDispatcher(){ + File fileDispatcher = null; + BinaryInstaller bi = new BinaryInstaller(context,context.getFilesDir()); + + String arch = System.getProperty("os.arch"); + if (arch.contains("arm")) + arch = "arm"; + else + arch = "x86"; + + try { + fileDispatcher = bi.installResource(arch, ASSET_KEY, false); + } catch (Exception ioe) { + Log.d(TAG,"Couldn't install dispatcher: " + ioe); + } + + return fileDispatcher; + } + + @WorkerThread + private void runBlockingCmd(String[] cmds, StringBuilder log) throws Exception { + runCmd(cmds, log, true); + } + + @WorkerThread + private int runCmd(String[] cmds, StringBuilder log, + boolean waitFor) throws Exception { + + int exitCode = -1; + Process proc = Runtime.getRuntime().exec("sh"); + OutputStreamWriter out = new OutputStreamWriter(proc.getOutputStream()); + + try { + for (String cmd : cmds) { + Log.d(TAG, "executing CMD: " + cmd); + out.write(cmd); + out.write("\n"); + } + + out.flush(); + out.write("exit\n"); + out.flush(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + out.close(); + } + + if (waitFor) { + // Consume the "stdout" + InputStreamReader reader = new InputStreamReader(proc.getInputStream()); + readToLogString(reader, log); + + // Consume the "stderr" + reader = new InputStreamReader(proc.getErrorStream()); + readToLogString(reader, log); + + try { + exitCode = proc.waitFor(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + return exitCode; + } + + private void readToLogString(InputStreamReader reader, StringBuilder log) throws IOException { + final char buf[] = new char[10]; + int read = 0; + try { + while ((read = reader.read(buf)) != -1) { + if (log != null) + log.append(buf, 0, read); + } + } catch (IOException e) { + reader.close(); + throw new IOException(e); + } + reader.close(); + } +} -- cgit v1.2.3 From 04df0d72d5f085a60e3b75b1b7df6244f04940f0 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Wed, 12 Jun 2019 17:39:14 +0200 Subject: define local port to run shapeshifter-dispatcher on startup, fix pid lookup, quick fix architecture for arm --- .../pluggableTransports/Dispatcher.java | 38 +++++----------------- 1 file changed, 8 insertions(+), 30 deletions(-) (limited to 'app/src/main/java/se') diff --git a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Dispatcher.java b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Dispatcher.java index ac846fd9..05ce2256 100644 --- a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Dispatcher.java +++ b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Dispatcher.java @@ -24,6 +24,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; +import java.util.NoSuchElementException; import java.util.StringTokenizer; @@ -33,6 +34,7 @@ import java.util.StringTokenizer; public class Dispatcher { private static final String ASSET_KEY = "piedispatcher"; + private static final String DISPATCHER_PORT = "4430"; private static final String TAG = Dispatcher.class.getName(); private final String remoteIP; private final String remotePort; @@ -40,7 +42,6 @@ public class Dispatcher { private final String iatMode; private File fileDispatcher; private Context context; - private String port = ""; private Thread dispatcherThread = null; private int dipatcherPid = -1; @@ -68,8 +69,10 @@ public class Dispatcher { " -target " + remoteIP + ":" + remotePort + " -transports obfs4" + " -options \"" + String.format("{\\\"cert\\\": \\\"%s\\\", \\\"iatMode\\\": \\\"%s\\\"}\"", certificate, iatMode) + - " -logLevel DEBUG -enableLogging"; + " -logLevel DEBUG -enableLogging" + + " -proxylistenaddr 127.0.0.1:" + DISPATCHER_PORT; + Log.d(TAG, "dispatcher command: " + dispatcherCommand); runBlockingCmd(new String[]{dispatcherCommand}, dispatcherLog); } catch (IOException e) { e.printStackTrace(); @@ -81,37 +84,12 @@ public class Dispatcher { // get pid of dispatcher StringBuilder log = new StringBuilder(); - String pidCommand = "ps | grep " + fileDispatcher.getCanonicalPath(); + String pidCommand = "ps | grep piedispatcher"; runBlockingCmd(new String[]{pidCommand}, log); String output = log.toString(); StringTokenizer st = new StringTokenizer(output, " "); st.nextToken(); // proc owner dipatcherPid = Integer.parseInt(st.nextToken().trim()); - - // get open port of dispatcher - String getPortCommand = "cat " + context.getFilesDir().getCanonicalPath() + "/state/dispatcher.log | grep \"obfs4 - registered listener\""; - long timeout = System.currentTimeMillis() + 5000; - int i = 1; - while (this.port.length() == 0 && System.currentTimeMillis() < timeout) { - log = new StringBuilder(); - Log.d(TAG, i + ". try to get port"); - runBlockingCmd(new String[]{getPortCommand}, log); - output = log.toString(); - if (output.length() > 0) { - Log.d(TAG, "dispatcher log: \n =================\n" + output); - } - - String dispatcherLog[] = output.split(" "); - if (dispatcherLog.length > 0) { - String localAddressAndPort = dispatcherLog[dispatcherLog.length - 1]; - if (localAddressAndPort.contains(":")) { - this.port = localAddressAndPort.split(":")[1].replace(System.getProperty("line.separator"), ""); - Log.d(TAG, "local port is: " + this.port); - } - } - i += 1; - } - } catch(Exception e){ if (dispatcherThread.isAlive()) { Log.e(TAG, e.getMessage() + ". Shutting down Dispatcher thread."); @@ -121,7 +99,7 @@ public class Dispatcher { } public String getPort() { - return port; + return DISPATCHER_PORT; } public void stop() { @@ -151,7 +129,7 @@ public class Dispatcher { String arch = System.getProperty("os.arch"); if (arch.contains("arm")) - arch = "arm"; + arch = "armeabi-v7a"; else arch = "x86"; -- cgit v1.2.3 From 8f7146a89fba31bcb9a204415a38e796cfa7d403 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Fri, 14 Jun 2019 18:18:18 +0200 Subject: * refactor vpn profile generation * fix lzo-comp flag parsing in ConfigParser --- .../java/se/leap/bitmaskclient/eip/Gateway.java | 2 +- .../se/leap/bitmaskclient/eip/GatewaysManager.java | 10 +- .../leap/bitmaskclient/eip/VpnConfigGenerator.java | 145 +++++++++++---------- .../pluggableTransports/Dispatcher.java | 35 +++-- .../pluggableTransports/DispatcherOptions.java | 18 +++ 5 files changed, 123 insertions(+), 87 deletions(-) create mode 100644 app/src/main/java/se/leap/bitmaskclient/pluggableTransports/DispatcherOptions.java (limited to 'app/src/main/java/se') 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 b1554af0..50fe74b7 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java @@ -114,7 +114,7 @@ public class Gateway { try { VpnConfigGenerator vpnConfigurationGenerator = new VpnConfigGenerator(generalConfiguration, secrets, gateway, apiVersion); return vpnConfigurationGenerator.generateVpnProfile(); - } catch (ConfigParser.ConfigParseError | IOException | CloneNotSupportedException | JSONException e) { + } catch (ConfigParser.ConfigParseError | IOException | JSONException e) { // FIXME We didn't get a VpnProfile! Error handling! and log level e.printStackTrace(); return null; diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java index c650938c..cd3ec1c6 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java @@ -99,12 +99,10 @@ public class GatewaysManager { int apiVersion = eipDefinition.getInt(VERSION); for (int i = 0; i < gatewaysDefined.length(); i++) { JSONObject gw = gatewaysDefined.getJSONObject(i); - if (isOpenVpnGateway(gw, apiVersion)) { - JSONObject secrets = secretsConfiguration(); - Gateway aux = new Gateway(eipDefinition, secrets, gw); - if (!gateways.contains(aux)) { - addGateway(aux); - } + JSONObject secrets = secretsConfiguration(); + Gateway aux = new Gateway(eipDefinition, secrets, gw); + if (!gateways.contains(aux)) { + addGateway(aux); } } } catch (JSONException e) { 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 7f09d21e..a131bdd8 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java @@ -27,9 +27,11 @@ import java.util.Iterator; import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.core.ConfigParser; import de.blinkt.openvpn.core.connection.Connection; -import de.blinkt.openvpn.core.connection.Obfs4Connection; import se.leap.bitmaskclient.Provider; +import se.leap.bitmaskclient.pluggableTransports.DispatcherOptions; +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.Constants.CAPABILITIES; import static se.leap.bitmaskclient.Constants.IP_ADDRESS; import static se.leap.bitmaskclient.Constants.OPTIONS; @@ -40,9 +42,10 @@ import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE; import static se.leap.bitmaskclient.Constants.REMOTE; import static se.leap.bitmaskclient.Constants.TRANSPORT; import static se.leap.bitmaskclient.Constants.TYPE; +import static se.leap.bitmaskclient.pluggableTransports.Dispatcher.DISPATCHER_IP; +import static se.leap.bitmaskclient.pluggableTransports.Dispatcher.DISPATCHER_PORT; public class VpnConfigGenerator { - private JSONObject generalConfiguration; private JSONObject gateway; private JSONObject secrets; @@ -66,18 +69,14 @@ public class VpnConfigGenerator { public void checkCapabilities() { try { - switch (apiVersion) { - case 2: - JSONArray supportedTransports = gateway.getJSONObject(CAPABILITIES).getJSONArray(TRANSPORT); - for (int i = 0; i < supportedTransports.length(); i++) { - JSONObject transport = supportedTransports.getJSONObject(i); - if (transport.getString(TYPE).equals("obfs4")) { - obfs4Transport = transport; - } + if (apiVersion == 2) { + JSONArray supportedTransports = gateway.getJSONObject(CAPABILITIES).getJSONArray(TRANSPORT); + for (int i = 0; i < supportedTransports.length(); i++) { + JSONObject transport = supportedTransports.getJSONObject(i); + if (transport.getString(TYPE).equals(OBFS4.toString())) { + obfs4Transport = transport; } - break; - default: - break; + } } } catch (JSONException e) { @@ -88,54 +87,45 @@ public class VpnConfigGenerator { public VpnProfile generateVpnProfile() throws IllegalStateException, IOException, ConfigParser.ConfigParseError, - CloneNotSupportedException, - JSONException, - NumberFormatException { + NumberFormatException, JSONException { - VpnProfile profile = createOvpnProfile(); if (supportsObfs4()) { - addPluggableTransportConnections(profile); + return createProfile(OBFS4); } - return profile; + + return createProfile(OPENVPN); } private boolean supportsObfs4(){ return obfs4Transport != null; } - private void addPluggableTransportConnections(VpnProfile profile) throws JSONException, CloneNotSupportedException { - JSONArray ports = obfs4Transport.getJSONArray(PORTS); - Connection[] updatedConnections = new Connection[profile.mConnections.length + ports.length()]; - - for (int i = 0; i < ports.length(); i++) { - String port = ports.getString(i); - Obfs4Connection obfs4Connection = new Obfs4Connection(); - obfs4Connection.setObfs4RemoteProxyName(gateway.getString(IP_ADDRESS)); - obfs4Connection.setObfs4RemoteProxyPort(port); - obfs4Connection.setTransportOptions(obfs4Transport.optJSONObject(OPTIONS)); - updatedConnections[i] = obfs4Connection; - } - int k = 0; - for (int i = ports.length(); i < updatedConnections.length; i++, k++) { - updatedConnections[i] = profile.mConnections[k].clone(); - } - profile.mConnections = updatedConnections; - } - - private String getConfigurationString() { + private String getConfigurationString(Connection.TransportType transportType) { return generalConfiguration() - + newLine - + ovpnGatewayConfiguration() - + newLine - + secretsConfiguration() - + newLine - + androidCustomizations(); + + newLine + + gatewayConfiguration(transportType) + + newLine + + androidCustomizations() + + newLine + + secretsConfiguration(); } - private VpnProfile createOvpnProfile() throws IOException, ConfigParser.ConfigParseError { - String configuration = getConfigurationString(); + private VpnProfile createProfile(Connection.TransportType transportType) throws IOException, ConfigParser.ConfigParseError, JSONException { + String configuration = getConfigurationString(transportType); icsOpenvpnConfigParser.parseConfig(new StringReader(configuration)); - return icsOpenvpnConfigParser.convertProfile(); + if (transportType == OBFS4) { + icsOpenvpnConfigParser.setDispatcherOptions(getDispatcherOptions()); + } + return icsOpenvpnConfigParser.convertProfile(transportType); + } + + private DispatcherOptions getDispatcherOptions() throws JSONException { + JSONObject transportOptions = obfs4Transport.getJSONObject(OPTIONS); + String iatMode = transportOptions.getString("iat-mode"); + String cert = transportOptions.getString("cert"); + String port = obfs4Transport.getJSONArray(PORTS).getString(0); + String ip = gateway.getString(IP_ADDRESS); + return new DispatcherOptions(ip, port, cert, iatMode); } private String generalConfiguration() { @@ -161,21 +151,21 @@ public class VpnConfigGenerator { return commonOptions; } - private String ovpnGatewayConfiguration() { + private String gatewayConfiguration(Connection.TransportType transportType) { String remotes = ""; StringBuilder stringBuilder = new StringBuilder(); try { String ipAddress = gateway.getString(IP_ADDRESS); JSONObject capabilities = gateway.getJSONObject(CAPABILITIES); - JSONArray transports = capabilities.getJSONArray(TRANSPORT); switch (apiVersion) { default: case 1: - ovpnGatewayConfigApiv1(stringBuilder, ipAddress, capabilities); + gatewayConfigApiv1(stringBuilder, ipAddress, capabilities); break; case 2: - ovpnGatewayConfigApiv2(stringBuilder, ipAddress, transports); + JSONArray transports = capabilities.getJSONArray(TRANSPORT); + gatewayConfigApiv2(transportType, stringBuilder, ipAddress, transports); break; } } catch (JSONException e) { @@ -190,10 +180,17 @@ public class VpnConfigGenerator { return remotes; } - private void ovpnGatewayConfigApiv1(StringBuilder stringBuilder, String ipAddress, JSONObject capabilities) throws JSONException { + private void gatewayConfigApiv2(Connection.TransportType transportType, StringBuilder stringBuilder, String ipAddress, JSONArray transports) throws JSONException { + if (transportType == OBFS4) { + obfs4GatewayConfigApiv2(stringBuilder, ipAddress, transports); + } else { + ovpnGatewayConfigApi2(stringBuilder, ipAddress, transports); + } + } + + private void gatewayConfigApiv1(StringBuilder stringBuilder, String ipAddress, JSONObject capabilities) throws JSONException { int port; String protocol; - JSONArray ports = capabilities.getJSONArray(PORTS); for (int i = 0; i < ports.length(); i++) { port = ports.getInt(i); @@ -206,27 +203,41 @@ public class VpnConfigGenerator { } } - private void ovpnGatewayConfigApiv2(StringBuilder stringBuilder, String ipAddress, JSONArray transports) throws JSONException { + private void ovpnGatewayConfigApi2(StringBuilder stringBuilder, String ipAddress, JSONArray transports) throws JSONException { String port; String protocol; + JSONObject openvpnTransport = getTransport(transports, OPENVPN); + JSONArray ports = openvpnTransport.getJSONArray(PORTS); + for (int j = 0; j < ports.length(); j++) { + port = ports.getString(j); + 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); + } + } + } + + private JSONObject getTransport(JSONArray transports, Connection.TransportType transportType) throws JSONException { + JSONObject selectedTransport = new JSONObject(); for (int i = 0; i < transports.length(); i++) { JSONObject transport = transports.getJSONObject(i); - if (!transport.getString(TYPE).equals("openvpn")) { - continue; - } - JSONArray ports = transport.getJSONArray(PORTS); - for (int j = 0; j < ports.length(); j++) { - port = ports.getString(j); - JSONArray protocols = transport.getJSONArray(PROTOCOLS); - for (int k = 0; k < protocols.length(); k++) { - protocol = protocols.optString(k); - String newRemote = REMOTE + " " + ipAddress + " " + port + " " + protocol + newLine; - stringBuilder.append(newRemote); - } + if (transport.getString(TYPE).equals(transportType.toString())) { + selectedTransport = transport; + break; } } + return selectedTransport; } + private void obfs4GatewayConfigApiv2(StringBuilder stringBuilder, String ipAddress, JSONArray transports) throws JSONException { + JSONObject obfs4Transport = getTransport(transports, OBFS4); + 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; + stringBuilder.append(remote); + } private String secretsConfiguration() { try { diff --git a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Dispatcher.java b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Dispatcher.java index 05ce2256..240dae75 100644 --- a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Dispatcher.java +++ b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Dispatcher.java @@ -18,13 +18,13 @@ package se.leap.bitmaskclient.pluggableTransports; import android.content.Context; import android.support.annotation.WorkerThread; +import android.text.TextUtils; import android.util.Log; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; -import java.util.NoSuchElementException; import java.util.StringTokenizer; @@ -34,7 +34,8 @@ import java.util.StringTokenizer; public class Dispatcher { private static final String ASSET_KEY = "piedispatcher"; - private static final String DISPATCHER_PORT = "4430"; + public static final String DISPATCHER_PORT = "4430"; + public static final String DISPATCHER_IP = "127.0.0.1"; private static final String TAG = Dispatcher.class.getName(); private final String remoteIP; private final String remotePort; @@ -43,14 +44,14 @@ public class Dispatcher { private File fileDispatcher; private Context context; private Thread dispatcherThread = null; - private int dipatcherPid = -1; + private int dispatcherPid = -1; - public Dispatcher(Context context, String remoteIP, String remotePort, String certificate, String iatMode) { + public Dispatcher(Context context, DispatcherOptions dispatcherOptions) { this.context = context.getApplicationContext(); - this.remoteIP = remoteIP; - this.remotePort = remotePort; - this.certificate = certificate; - this.iatMode = iatMode; + this.remoteIP = dispatcherOptions.remoteIP; + this.remotePort = dispatcherOptions.remotePort; + this.certificate = dispatcherOptions.cert; + this.iatMode = dispatcherOptions.iatMode; } @WorkerThread @@ -70,7 +71,7 @@ public class Dispatcher { " -transports obfs4" + " -options \"" + String.format("{\\\"cert\\\": \\\"%s\\\", \\\"iatMode\\\": \\\"%s\\\"}\"", certificate, iatMode) + " -logLevel DEBUG -enableLogging" + - " -proxylistenaddr 127.0.0.1:" + DISPATCHER_PORT; + " -proxylistenaddr "+ DISPATCHER_IP + ":" + DISPATCHER_PORT; Log.d(TAG, "dispatcher command: " + dispatcherCommand); runBlockingCmd(new String[]{dispatcherCommand}, dispatcherLog); @@ -82,14 +83,22 @@ public class Dispatcher { }); dispatcherThread.start(); - // get pid of dispatcher + // get pid of dispatcher, try several times in case the dispatcher + // process is not spawned yet StringBuilder log = new StringBuilder(); String pidCommand = "ps | grep piedispatcher"; - runBlockingCmd(new String[]{pidCommand}, log); + for (int i = 0; i < 5; i++) { + runBlockingCmd(new String[]{pidCommand}, log); + if (!TextUtils.isEmpty(log)) { + break; + } + Thread.sleep(100); + } + String output = log.toString(); StringTokenizer st = new StringTokenizer(output, " "); st.nextToken(); // proc owner - dipatcherPid = Integer.parseInt(st.nextToken().trim()); + dispatcherPid = Integer.parseInt(st.nextToken().trim()); } catch(Exception e){ if (dispatcherThread.isAlive()) { Log.e(TAG, e.getMessage() + ". Shutting down Dispatcher thread."); @@ -106,7 +115,7 @@ public class Dispatcher { Log.d(TAG, "Shutting down Dispatcher thread."); if (dispatcherThread != null && dispatcherThread.isAlive()) { try { - killProcess(dipatcherPid); + killProcess(dispatcherPid); } catch (Exception e) { e.printStackTrace(); } diff --git a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/DispatcherOptions.java b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/DispatcherOptions.java new file mode 100644 index 00000000..76ccbd79 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/DispatcherOptions.java @@ -0,0 +1,18 @@ +package se.leap.bitmaskclient.pluggableTransports; + +import java.io.Serializable; + +public class DispatcherOptions implements Serializable { + public String cert; + public String iatMode; + public String remoteIP; + public String remotePort; + + public DispatcherOptions(String remoteIP, String remotePort, String cert, String iatMode) { + this.cert = cert; + this.iatMode = iatMode; + this.remoteIP = remoteIP; + this.remotePort = remotePort; + } + +} -- cgit v1.2.3 From bdea6b5469d6a3a136e6a847d1097fbd56ee1053 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Thu, 27 Jun 2019 15:57:25 +0200 Subject: use api version 3 for pluggable Transports --- .../java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'app/src/main/java/se') 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 a131bdd8..2214b592 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java @@ -58,7 +58,7 @@ public class VpnConfigGenerator { public final static String TAG = VpnConfigGenerator.class.getSimpleName(); private final String newLine = System.getProperty("line.separator"); // Platform new line - public VpnConfigGenerator(JSONObject generalConfiguration, JSONObject secrets, JSONObject gateway, int apiVersion) { + public VpnConfigGenerator(JSONObject generalConfiguration, JSONObject secrets, JSONObject gateway, int apiVersion) throws ConfigParser.ConfigParseError { this.generalConfiguration = generalConfiguration; this.gateway = gateway; this.secrets = secrets; @@ -66,21 +66,22 @@ public class VpnConfigGenerator { checkCapabilities(); } - public void checkCapabilities() { + public void checkCapabilities() throws ConfigParser.ConfigParseError { try { - if (apiVersion == 2) { + if (apiVersion == 3) { JSONArray supportedTransports = gateway.getJSONObject(CAPABILITIES).getJSONArray(TRANSPORT); for (int i = 0; i < supportedTransports.length(); i++) { JSONObject transport = supportedTransports.getJSONObject(i); if (transport.getString(TYPE).equals(OBFS4.toString())) { obfs4Transport = transport; + break; } } } } catch (JSONException e) { - e.printStackTrace(); + throw new ConfigParser.ConfigParseError("Api version ("+ apiVersion +") did not match required JSON fields"); } } @@ -161,9 +162,10 @@ public class VpnConfigGenerator { switch (apiVersion) { default: case 1: + case 2: gatewayConfigApiv1(stringBuilder, ipAddress, capabilities); break; - case 2: + case 3: JSONArray transports = capabilities.getJSONArray(TRANSPORT); gatewayConfigApiv2(transportType, stringBuilder, ipAddress, transports); break; -- cgit v1.2.3 From 63d1ccce6173445efba0028cc0fee1562e4540aa Mon Sep 17 00:00:00 2001 From: cyBerta Date: Wed, 3 Jul 2019 19:10:19 +0200 Subject: show a little ghost and extra information in notifications when trying or using an obfuscated connection --- .../leap/bitmaskclient/VpnNotificationManager.java | 46 ++++++++++++++++------ 1 file changed, 33 insertions(+), 13 deletions(-) (limited to 'app/src/main/java/se') diff --git a/app/src/main/java/se/leap/bitmaskclient/VpnNotificationManager.java b/app/src/main/java/se/leap/bitmaskclient/VpnNotificationManager.java index 9107568c..44a69f5c 100644 --- a/app/src/main/java/se/leap/bitmaskclient/VpnNotificationManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/VpnNotificationManager.java @@ -43,8 +43,8 @@ import static android.support.v4.app.NotificationCompat.PRIORITY_MIN; import static android.text.TextUtils.isEmpty; 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.Constants.EIP_ACTION_STOP_BLOCKING_VPN; import static se.leap.bitmaskclient.Constants.ASK_TO_CANCEL_VPN; +import static se.leap.bitmaskclient.Constants.EIP_ACTION_STOP_BLOCKING_VPN; import static se.leap.bitmaskclient.MainActivity.ACTION_SHOW_VPN_FRAGMENT; /** @@ -83,6 +83,7 @@ public class VpnNotificationManager { buildVpnNotification( context.getString(R.string.void_vpn_title), msg, + null, tickerText, status, VoidVpnService.NOTIFICATION_CHANNEL_NEWSTATUS_ID, @@ -110,8 +111,11 @@ public class VpnNotificationManager { * @param status * @param when */ - public void buildOpenVpnNotification(String profileName, final 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) { String cancelString; + String bigmessage = null; + String ghostIcon = new String(Character.toChars(0x1F47B)); + switch (status) { // show cancel if no connection case LEVEL_START: @@ -119,11 +123,24 @@ public class VpnNotificationManager { case LEVEL_CONNECTING_SERVER_REPLIED: case LEVEL_CONNECTING_NO_SERVER_REPLY_YET: cancelString = context.getString(R.string.cancel); + if (isObfuscated && Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { + bigmessage = context.getString(R.string.obfuscated_connection_try) + " " + ghostIcon + "\n" + msg; + } break; + // show disconnect if connection exists + case LEVEL_CONNECTED: + if (isObfuscated && Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { + bigmessage = context.getString(R.string.obfuscated_connection) + " " + ghostIcon + "\n" + msg; + } default: cancelString = context.getString(R.string.cancel_connection); } + + if (isObfuscated) { + msg = ghostIcon + " " + msg; + } + NotificationCompat.Action.Builder actionBuilder = new NotificationCompat.Action. Builder(R.drawable.ic_menu_close_clear_cancel, cancelString, getDisconnectIntent()); String title; @@ -151,6 +168,7 @@ public class VpnNotificationManager { buildVpnNotification( title, msg, + bigmessage, tickerText, status, notificationChannelNewstatusId, @@ -224,28 +242,30 @@ public class VpnNotificationManager { return remoteViews; } - private void buildVpnNotification(String title, final String msg, String tickerText, ConnectionStatus status, String notificationChannelNewstatusId, int priority, long when, PendingIntent contentIntent, NotificationCompat.Action notificationAction) { + private void buildVpnNotification(String title, String message, String bigMessage, String tickerText, ConnectionStatus status, String notificationChannelNewstatusId, int priority, long when, PendingIntent contentIntent, NotificationCompat.Action notificationAction) { NotificationCompat.Builder nCompatBuilder = new NotificationCompat.Builder(context, notificationChannelNewstatusId); int icon = getIconByConnectionStatus(status); // this is a workaround to avoid confusion between the Android's system vpn notification // showing a filled out key icon and the bitmask icon indicating a different state. if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT && - notificationChannelNewstatusId.equals(OpenVPNService.NOTIFICATION_CHANNEL_NEWSTATUS_ID) && - status != LEVEL_NONETWORK - ) { - // removes the icon from the system status bar - icon = android.R.color.transparent; - // adds the icon to the notification in the notification drawer - nCompatBuilder.setContent(getKitkatCustomRemoteView(status, title, msg)); + notificationChannelNewstatusId.equals(OpenVPNService.NOTIFICATION_CHANNEL_NEWSTATUS_ID)) { + if (status != LEVEL_NONETWORK) { + // removes the icon from the system status bar + icon = android.R.color.transparent; + // adds the icon to the notification in the notification drawer + nCompatBuilder.setContent(getKitkatCustomRemoteView(status, title, message)); + } } else { - nCompatBuilder.addAction(notificationAction); + nCompatBuilder.setStyle(new NotificationCompat.BigTextStyle(). + setBigContentTitle(title). + bigText(bigMessage)); } - + nCompatBuilder.addAction(notificationAction); nCompatBuilder.setContentTitle(title); nCompatBuilder.setCategory(NotificationCompat.CATEGORY_SERVICE); nCompatBuilder.setLocalOnly(true); - nCompatBuilder.setContentText(msg); + nCompatBuilder.setContentText(message); nCompatBuilder.setOnlyAlertOnce(true); nCompatBuilder.setSmallIcon(icon); nCompatBuilder.setPriority(priority); -- cgit v1.2.3 From 2a8bb775183dd08388460c711dc650d3110ca76c Mon Sep 17 00:00:00 2001 From: cyBerta Date: Wed, 3 Jul 2019 19:16:43 +0200 Subject: update method name for gateway configuration v3 --- .../java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'app/src/main/java/se') 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 2214b592..18c557dc 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java @@ -167,7 +167,7 @@ public class VpnConfigGenerator { break; case 3: JSONArray transports = capabilities.getJSONArray(TRANSPORT); - gatewayConfigApiv2(transportType, stringBuilder, ipAddress, transports); + gatewayConfigApiv3(transportType, stringBuilder, ipAddress, transports); break; } } catch (JSONException e) { @@ -182,11 +182,11 @@ public class VpnConfigGenerator { return remotes; } - private void gatewayConfigApiv2(Connection.TransportType transportType, StringBuilder stringBuilder, String ipAddress, JSONArray transports) throws JSONException { + private void gatewayConfigApiv3(Connection.TransportType transportType, StringBuilder stringBuilder, String ipAddress, JSONArray transports) throws JSONException { if (transportType == OBFS4) { - obfs4GatewayConfigApiv2(stringBuilder, ipAddress, transports); + obfs4GatewayConfigApiv3(stringBuilder, ipAddress, transports); } else { - ovpnGatewayConfigApi2(stringBuilder, ipAddress, transports); + ovpnGatewayConfigApi3(stringBuilder, ipAddress, transports); } } @@ -205,7 +205,7 @@ public class VpnConfigGenerator { } } - private void ovpnGatewayConfigApi2(StringBuilder stringBuilder, String ipAddress, JSONArray transports) throws JSONException { + private void ovpnGatewayConfigApi3(StringBuilder stringBuilder, String ipAddress, JSONArray transports) throws JSONException { String port; String protocol; JSONObject openvpnTransport = getTransport(transports, OPENVPN); @@ -233,7 +233,7 @@ public class VpnConfigGenerator { return selectedTransport; } - private void obfs4GatewayConfigApiv2(StringBuilder stringBuilder, String ipAddress, JSONArray transports) throws JSONException { + private void obfs4GatewayConfigApiv3(StringBuilder stringBuilder, String ipAddress, JSONArray transports) throws JSONException { JSONObject obfs4Transport = getTransport(transports, OBFS4); String route = "route " + ipAddress + " 255.255.255.255 net_gateway" + newLine; stringBuilder.append(route); -- cgit v1.2.3 From 283e7531d551521dc48efa9b010127ff54316326 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Thu, 4 Jul 2019 16:44:35 +0200 Subject: create one vpnprofile per transport per gateway. implement basis to switch between obfs4 and plain openvpn connections --- .../main/java/se/leap/bitmaskclient/eip/EIP.java | 8 ++-- .../java/se/leap/bitmaskclient/eip/Gateway.java | 37 ++++++++++++---- .../se/leap/bitmaskclient/eip/GatewaysManager.java | 50 +++------------------- .../leap/bitmaskclient/eip/VpnConfigGenerator.java | 19 ++++---- 4 files changed, 49 insertions(+), 65 deletions(-) (limited to 'app/src/main/java/se') 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 a5434871..1d702ec1 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java @@ -53,6 +53,8 @@ import se.leap.bitmaskclient.R; import static android.app.Activity.RESULT_CANCELED; import static android.app.Activity.RESULT_OK; import static android.content.Intent.CATEGORY_DEFAULT; +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.Constants.BROADCAST_EIP_EVENT; import static se.leap.bitmaskclient.Constants.BROADCAST_GATEWAY_SETUP_OBSERVER_EVENT; import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_CODE; @@ -203,7 +205,7 @@ public final class EIP extends JobIntentService implements Observer { GatewaysManager gatewaysManager = gatewaysFromPreferences(); Gateway gateway = gatewaysManager.select(nClosestGateway); - if (gateway != null && gateway.getProfile() != null) { + if (gateway != null && !gateway.getProfiles().isEmpty()) { launchActiveGateway(gateway, nClosestGateway); tellToReceiverOrBroadcast(EIP_ACTION_START, RESULT_OK); } else @@ -218,7 +220,7 @@ public final class EIP extends JobIntentService implements Observer { GatewaysManager gatewaysManager = gatewaysFromPreferences(); Gateway gateway = gatewaysManager.select(0); - if (gateway != null && gateway.getProfile() != null) { + if (gateway != null && !gateway.getProfiles().isEmpty()) { launchActiveGateway(gateway, 0); } else { Log.d(TAG, "startEIPAlwaysOnVpn no active profile available!"); @@ -242,7 +244,7 @@ public final class EIP extends JobIntentService implements Observer { */ private void launchActiveGateway(@NonNull Gateway gateway, int nClosestGateway) { Intent intent = new Intent(BROADCAST_GATEWAY_SETUP_OBSERVER_EVENT); - intent.putExtra(PROVIDER_PROFILE, gateway.getProfile()); + intent.putExtra(PROVIDER_PROFILE, gateway.getProfile(OPENVPN)); intent.putExtra(Gateway.KEY_N_CLOSEST_GATEWAY, nClosestGateway); LocalBroadcastManager.getInstance(this).sendBroadcast(intent); 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 50fe74b7..9bc07764 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java @@ -16,16 +16,21 @@ */ package se.leap.bitmaskclient.eip; +import android.support.annotation.NonNull; + import com.google.gson.Gson; import org.json.JSONException; import org.json.JSONObject; import java.io.IOException; +import java.util.HashMap; import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.core.ConfigParser; +import de.blinkt.openvpn.core.connection.Connection; +import static se.leap.bitmaskclient.Constants.IP_ADDRESS; import static se.leap.bitmaskclient.Constants.LOCATION; import static se.leap.bitmaskclient.Constants.LOCATIONS; import static se.leap.bitmaskclient.Constants.NAME; @@ -54,7 +59,7 @@ public class Gateway { private String name; private int timezone; private int apiVersion; - private VpnProfile vpnProfile; + private HashMap vpnProfiles; /** * Build a gateway object from a JSON OpenVPN gateway definition in eip-service.json @@ -69,9 +74,13 @@ public class Gateway { timezone = getTimezone(eipDefinition); name = locationAsName(eipDefinition); apiVersion = getApiVersion(eipDefinition); - vpnProfile = createVPNProfile(); - if (vpnProfile != null) { - vpnProfile.mName = name; + vpnProfiles = createVPNProfiles(); + } + + private void addProfileInfos(HashMap profiles) { + for (VpnProfile profile : profiles.values()) { + profile.mName = name; + profile.mGatewayIp = gateway.optString(IP_ADDRESS); } } @@ -92,6 +101,10 @@ public class Gateway { return eipDefinition.optInt(VERSION); } + public String getRemoteIP() { + return gateway.optString(IP_ADDRESS); + } + private String locationAsName(JSONObject eipDefinition) { JSONObject location = getLocationInfo(eipDefinition); return location.optString(NAME); @@ -110,23 +123,29 @@ public class Gateway { /** * Create and attach the VpnProfile to our gateway object */ - private VpnProfile createVPNProfile() { + private @NonNull HashMap createVPNProfiles() { + HashMap profiles = new HashMap<>(); try { VpnConfigGenerator vpnConfigurationGenerator = new VpnConfigGenerator(generalConfiguration, secrets, gateway, apiVersion); - return vpnConfigurationGenerator.generateVpnProfile(); + profiles = vpnConfigurationGenerator.generateVpnProfiles(); + addProfileInfos(profiles); } catch (ConfigParser.ConfigParseError | IOException | JSONException e) { // FIXME We didn't get a VpnProfile! Error handling! and log level e.printStackTrace(); - return null; } + return profiles; } public String getName() { return name; } - public VpnProfile getProfile() { - return vpnProfile; + public HashMap getProfiles() { + return vpnProfiles; + } + + public VpnProfile getProfile(Connection.TransportType transportType) { + return vpnProfiles.get(transportType); } public int getTimezone() { diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java index cd3ec1c6..f7038bab 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java @@ -28,20 +28,14 @@ import org.json.JSONObject; import java.lang.reflect.Type; import java.util.ArrayList; -import java.util.List; +import java.util.LinkedHashMap; -import de.blinkt.openvpn.VpnProfile; -import de.blinkt.openvpn.core.connection.Connection; import se.leap.bitmaskclient.Provider; import se.leap.bitmaskclient.utils.PreferenceHelper; -import static se.leap.bitmaskclient.Constants.CAPABILITIES; import static se.leap.bitmaskclient.Constants.GATEWAYS; import static se.leap.bitmaskclient.Constants.PROVIDER_PRIVATE_KEY; import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE; -import static se.leap.bitmaskclient.Constants.TRANSPORT; -import static se.leap.bitmaskclient.Constants.TYPE; -import static se.leap.bitmaskclient.Constants.VERSION; /** * @author parmegv @@ -52,7 +46,7 @@ public class GatewaysManager { private Context context; private SharedPreferences preferences; - private List gateways = new ArrayList<>(); + private LinkedHashMap gateways = new LinkedHashMap<>(); private Type listType = new TypeToken>() {}.getType(); GatewaysManager(Context context, SharedPreferences preferences) { @@ -65,7 +59,7 @@ public class GatewaysManager { * @return the n closest Gateway */ public Gateway select(int nClosest) { - GatewaySelector gatewaySelector = new GatewaySelector(gateways); + GatewaySelector gatewaySelector = new GatewaySelector(new ArrayList<>(gateways.values())); return gatewaySelector.select(nClosest); } @@ -96,52 +90,20 @@ public class GatewaysManager { void fromEipServiceJson(JSONObject eipDefinition) { try { JSONArray gatewaysDefined = eipDefinition.getJSONArray(GATEWAYS); - int apiVersion = eipDefinition.getInt(VERSION); for (int i = 0; i < gatewaysDefined.length(); i++) { JSONObject gw = gatewaysDefined.getJSONObject(i); JSONObject secrets = secretsConfiguration(); Gateway aux = new Gateway(eipDefinition, secrets, gw); - if (!gateways.contains(aux)) { + if (gateways.get(aux.getRemoteIP()) == null) { addGateway(aux); } } - } catch (JSONException e) { + } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } - /** - * check if a gateway is an OpenVpn gateway - * @param gateway to check - * @return true if gateway is an OpenVpn gateway otherwise false - */ - private boolean isOpenVpnGateway(JSONObject gateway, int apiVersion) { - switch (apiVersion) { - default: - case 1: - try { - String transport = gateway.getJSONObject(CAPABILITIES).getJSONArray(TRANSPORT).toString(); - return transport.contains("openvpn"); - } catch (JSONException e) { - return false; - } - case 2: - try { - JSONArray transports = gateway.getJSONObject(CAPABILITIES).getJSONArray(TRANSPORT); - for (int i = 0; i < transports.length(); i++) { - JSONObject transport = transports.getJSONObject(i); - if (transport.optString(TYPE).equals("openvpn")) { - return true; - } - } - return false; - } catch (JSONException e) { - return false; - } - } - } - private JSONObject secretsConfiguration() { JSONObject result = new JSONObject(); try { @@ -160,7 +122,7 @@ public class GatewaysManager { } private void addGateway(Gateway gateway) { - gateways.add(gateway); + gateways.put(gateway.getRemoteIP(), gateway); } /** 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 18c557dc..6b76e8d9 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java @@ -22,6 +22,7 @@ import org.json.JSONObject; import java.io.IOException; import java.io.StringReader; +import java.util.HashMap; import java.util.Iterator; import de.blinkt.openvpn.VpnProfile; @@ -52,8 +53,6 @@ public class VpnConfigGenerator { private JSONObject obfs4Transport; private int apiVersion; - private ConfigParser icsOpenvpnConfigParser = new ConfigParser(); - public final static String TAG = VpnConfigGenerator.class.getSimpleName(); private final String newLine = System.getProperty("line.separator"); // Platform new line @@ -85,16 +84,17 @@ public class VpnConfigGenerator { } } - public VpnProfile generateVpnProfile() throws IllegalStateException, - IOException, + public HashMap generateVpnProfiles() throws ConfigParser.ConfigParseError, - NumberFormatException, JSONException { - + NumberFormatException, + JSONException, + IOException { + HashMap profiles = new HashMap<>(); + profiles.put(OPENVPN, createProfile(OPENVPN)); if (supportsObfs4()) { - return createProfile(OBFS4); + profiles.put(OBFS4, createProfile(OBFS4)); } - - return createProfile(OPENVPN); + return profiles; } private boolean supportsObfs4(){ @@ -113,6 +113,7 @@ public class VpnConfigGenerator { private VpnProfile createProfile(Connection.TransportType transportType) throws IOException, ConfigParser.ConfigParseError, JSONException { String configuration = getConfigurationString(transportType); + ConfigParser icsOpenvpnConfigParser = new ConfigParser(); icsOpenvpnConfigParser.parseConfig(new StringReader(configuration)); if (transportType == OBFS4) { icsOpenvpnConfigParser.setDispatcherOptions(getDispatcherOptions()); -- cgit v1.2.3 From 2ae968a8cfcfeb51874a206fd1abbf40f06cf98b Mon Sep 17 00:00:00 2001 From: cyBerta Date: Thu, 4 Jul 2019 17:08:29 +0200 Subject: 'using an obfusctation connection' in italic --- .../se/leap/bitmaskclient/VpnNotificationManager.java | 18 ++++++++++++++---- app/src/main/java/se/leap/bitmaskclient/eip/EIP.java | 2 +- 2 files changed, 15 insertions(+), 5 deletions(-) (limited to 'app/src/main/java/se') diff --git a/app/src/main/java/se/leap/bitmaskclient/VpnNotificationManager.java b/app/src/main/java/se/leap/bitmaskclient/VpnNotificationManager.java index 44a69f5c..abf71803 100644 --- a/app/src/main/java/se/leap/bitmaskclient/VpnNotificationManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/VpnNotificationManager.java @@ -24,11 +24,17 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.graphics.Color; +import android.graphics.Typeface; import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationManagerCompat; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.SpannableStringBuilder; +import android.text.TextUtils; +import android.text.style.StyleSpan; import android.widget.RemoteViews; import de.blinkt.openvpn.LaunchVPN; @@ -113,7 +119,7 @@ public class VpnNotificationManager { */ public void buildOpenVpnNotification(String profileName, boolean isObfuscated, String msg, String tickerText, ConnectionStatus status, long when, String notificationChannelNewstatusId) { String cancelString; - String bigmessage = null; + CharSequence bigmessage = null; String ghostIcon = new String(Character.toChars(0x1F47B)); switch (status) { @@ -124,14 +130,18 @@ public class VpnNotificationManager { case LEVEL_CONNECTING_NO_SERVER_REPLY_YET: cancelString = context.getString(R.string.cancel); if (isObfuscated && Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { - bigmessage = context.getString(R.string.obfuscated_connection_try) + " " + ghostIcon + "\n" + msg; + Spannable spannable = new SpannableString(context.getString(R.string.obfuscated_connection_try)); + spannable.setSpan(new StyleSpan(Typeface.ITALIC), 0, spannable.length() -1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + bigmessage = TextUtils.concat(spannable, " " + ghostIcon + "\n" + msg); } break; // show disconnect if connection exists case LEVEL_CONNECTED: if (isObfuscated && Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { - bigmessage = context.getString(R.string.obfuscated_connection) + " " + ghostIcon + "\n" + msg; + Spannable spannable = new SpannableString(context.getString(R.string.obfuscated_connection)); + spannable.setSpan(new StyleSpan(Typeface.ITALIC), 0, spannable.length() -1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + bigmessage = TextUtils.concat(spannable, " " + ghostIcon + "\n" + msg); } default: cancelString = context.getString(R.string.cancel_connection); @@ -242,7 +252,7 @@ public class VpnNotificationManager { return remoteViews; } - private void buildVpnNotification(String title, String message, String 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) { NotificationCompat.Builder nCompatBuilder = new NotificationCompat.Builder(context, notificationChannelNewstatusId); int icon = getIconByConnectionStatus(status); 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 1d702ec1..9b5176e7 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java @@ -244,7 +244,7 @@ public final class EIP extends JobIntentService implements Observer { */ private void launchActiveGateway(@NonNull Gateway gateway, int nClosestGateway) { Intent intent = new Intent(BROADCAST_GATEWAY_SETUP_OBSERVER_EVENT); - intent.putExtra(PROVIDER_PROFILE, gateway.getProfile(OPENVPN)); + intent.putExtra(PROVIDER_PROFILE, gateway.getProfile(OBFS4)); intent.putExtra(Gateway.KEY_N_CLOSEST_GATEWAY, nClosestGateway); LocalBroadcastManager.getInstance(this).sendBroadcast(intent); -- cgit v1.2.3 From 1d3ecd7bb6cbb9ecac394cc494b095ef830dadee Mon Sep 17 00:00:00 2001 From: cyBerta Date: Sun, 14 Jul 2019 20:11:44 +0200 Subject: remove equals method in Gateway class after rebasing onto latest master --- app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java | 13 ------------- 1 file changed, 13 deletions(-) (limited to 'app/src/main/java/se') 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 9bc07764..352d6b18 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java @@ -157,17 +157,4 @@ public class Gateway { return new Gson().toJson(this, Gateway.class); } - @Override - public boolean equals(Object obj) { - return obj instanceof Gateway && - (this.mVpnProfile != null && - ((Gateway) obj).mVpnProfile != null && - this.mVpnProfile.mConnections != null && - ((Gateway) obj).mVpnProfile != null && - this.mVpnProfile.mConnections.length > 0 && - ((Gateway) obj).mVpnProfile.mConnections.length > 0 && - this.mVpnProfile.mConnections[0].mServerName != null && - this.mVpnProfile.mConnections[0].mServerName.equals(((Gateway) obj).mVpnProfile.mConnections[0].mServerName)) || - this.mVpnProfile == null && ((Gateway) obj).mVpnProfile == null; - } } -- cgit v1.2.3 From 0917317c006b8a3b0a8ee1c991a6287623c9562a Mon Sep 17 00:00:00 2001 From: cyBerta Date: Fri, 19 Jul 2019 14:44:05 +0200 Subject: adapt error handling during eip setup: refresh UI if gateway profile could not be started --- .../java/se/leap/bitmaskclient/EipSetupObserver.java | 1 + app/src/main/java/se/leap/bitmaskclient/eip/EIP.java | 19 +++++++++++++------ .../java/se/leap/bitmaskclient/eip/EipStatus.java | 11 +++++++---- 3 files changed, 21 insertions(+), 10 deletions(-) (limited to 'app/src/main/java/se') diff --git a/app/src/main/java/se/leap/bitmaskclient/EipSetupObserver.java b/app/src/main/java/se/leap/bitmaskclient/EipSetupObserver.java index a8aa2dfb..7327c416 100644 --- a/app/src/main/java/se/leap/bitmaskclient/EipSetupObserver.java +++ b/app/src/main/java/se/leap/bitmaskclient/EipSetupObserver.java @@ -168,6 +168,7 @@ class EipSetupObserver extends BroadcastReceiver implements VpnStatus.StateListe if (resultCode == RESULT_CANCELED) { //setup failed finishGatewaySetup(false); + EipStatus.refresh(); } break; default: 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 9b5176e7..3855fa12 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java @@ -43,6 +43,7 @@ import java.util.Observer; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; +import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.core.ConnectionStatus; import de.blinkt.openvpn.core.IOpenVPNServiceInternal; import de.blinkt.openvpn.core.OpenVPNService; @@ -53,7 +54,6 @@ import se.leap.bitmaskclient.R; import static android.app.Activity.RESULT_CANCELED; import static android.app.Activity.RESULT_OK; import static android.content.Intent.CATEGORY_DEFAULT; -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.Constants.BROADCAST_EIP_EVENT; import static se.leap.bitmaskclient.Constants.BROADCAST_GATEWAY_SETUP_OBSERVER_EVENT; @@ -205,11 +205,11 @@ public final class EIP extends JobIntentService implements Observer { GatewaysManager gatewaysManager = gatewaysFromPreferences(); Gateway gateway = gatewaysManager.select(nClosestGateway); - if (gateway != null && !gateway.getProfiles().isEmpty()) { - launchActiveGateway(gateway, nClosestGateway); + if (launchActiveGateway(gateway, nClosestGateway)) { tellToReceiverOrBroadcast(EIP_ACTION_START, RESULT_OK); - } else + } else { tellToReceiverOrBroadcast(EIP_ACTION_START, RESULT_CANCELED); + } } /** @@ -242,11 +242,18 @@ public final class EIP extends JobIntentService implements Observer { * * @param gateway to connect to */ - private void launchActiveGateway(@NonNull Gateway gateway, int nClosestGateway) { + private boolean launchActiveGateway(Gateway gateway, int nClosestGateway) { + VpnProfile profile; + if (gateway == null || + (profile = gateway.getProfile(OPENVPN)) == null) { + return false; + } + Intent intent = new Intent(BROADCAST_GATEWAY_SETUP_OBSERVER_EVENT); - intent.putExtra(PROVIDER_PROFILE, gateway.getProfile(OBFS4)); + intent.putExtra(PROVIDER_PROFILE, profile); intent.putExtra(Gateway.KEY_N_CLOSEST_GATEWAY, nClosestGateway); LocalBroadcastManager.getInstance(this).sendBroadcast(intent); + return true; } 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 64904816..69fc483a 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java @@ -78,8 +78,7 @@ public class EipStatus extends Observable implements VpnStatus.StateListener { currentStatus.setLevel(level); currentStatus.setEipLevel(level); if (tmp != currentStatus.getLevel() || "RECONNECTING".equals(state)) { - currentStatus.setChanged(); - currentStatus.notifyObservers(); + refresh(); } } @@ -174,8 +173,7 @@ public class EipStatus extends Observable implements VpnStatus.StateListener { default: break; } - currentStatus.setChanged(); - currentStatus.notifyObservers(); + refresh(); } } } @@ -286,4 +284,9 @@ public class EipStatus extends Observable implements VpnStatus.StateListener { return "State: " + state + " Level: " + vpnLevel.toString(); } + public static void refresh() { + currentStatus.setChanged(); + currentStatus.notifyObservers(); + } + } -- cgit v1.2.3 From 1a27e86bb86e70830dc79a210b0efb80fffbb5c7 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Fri, 19 Jul 2019 18:39:57 +0200 Subject: add shared preferences for pluggable transport usage --- app/src/main/java/se/leap/bitmaskclient/Constants.java | 1 + .../se/leap/bitmaskclient/utils/PreferenceHelper.java | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) (limited to 'app/src/main/java/se') diff --git a/app/src/main/java/se/leap/bitmaskclient/Constants.java b/app/src/main/java/se/leap/bitmaskclient/Constants.java index 7503d29f..0bc3c944 100644 --- a/app/src/main/java/se/leap/bitmaskclient/Constants.java +++ b/app/src/main/java/se/leap/bitmaskclient/Constants.java @@ -13,6 +13,7 @@ public interface Constants { String ALWAYS_ON_SHOW_DIALOG = "DIALOG.ALWAYS_ON_SHOW_DIALOG"; String CLEARLOG = "clearlogconnect"; String LAST_USED_PROFILE = "last_used_profile"; + String USE_PLUGGABLE_TRANSPORTS = "usePluggableTransports"; ////////////////////////////////////////////// diff --git a/app/src/main/java/se/leap/bitmaskclient/utils/PreferenceHelper.java b/app/src/main/java/se/leap/bitmaskclient/utils/PreferenceHelper.java index f5cf590b..09d941f5 100644 --- a/app/src/main/java/se/leap/bitmaskclient/utils/PreferenceHelper.java +++ b/app/src/main/java/se/leap/bitmaskclient/utils/PreferenceHelper.java @@ -29,6 +29,7 @@ import static se.leap.bitmaskclient.Constants.PROVIDER_EIP_DEFINITION; import static se.leap.bitmaskclient.Constants.PROVIDER_PRIVATE_KEY; import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE; import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; +import static se.leap.bitmaskclient.Constants.USE_PLUGGABLE_TRANSPORTS; /** * Created by cyberta on 18.03.18. @@ -210,6 +211,22 @@ public class PreferenceHelper { apply(); } + public static boolean getUsePluggableTransports(Context context) { + if (context == null) { + return false; + } + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); + return preferences.getBoolean(USE_PLUGGABLE_TRANSPORTS, false); + } + + public static void usePluggableTransports(Context context, boolean isEnabled) { + if (context == null) { + return; + } + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); + preferences.edit().putBoolean(USE_PLUGGABLE_TRANSPORTS, isEnabled).apply(); + } + public static void saveBattery(Context context, boolean isEnabled) { if (context == null) { return; -- cgit v1.2.3 From 990c4fa151adf9f91dc729c21b44edb0f0b55ccf Mon Sep 17 00:00:00 2001 From: cyBerta Date: Fri, 19 Jul 2019 18:46:12 +0200 Subject: add simple UI to enable/disable PT usage --- .../leap/bitmaskclient/DrawerSettingsAdapter.java | 1 + .../main/java/se/leap/bitmaskclient/Provider.java | 5 +++ .../drawer/NavigationDrawerFragment.java | 37 ++++++++++++++++++---- .../main/java/se/leap/bitmaskclient/eip/EIP.java | 6 +++- 4 files changed, 41 insertions(+), 8 deletions(-) (limited to 'app/src/main/java/se') diff --git a/app/src/main/java/se/leap/bitmaskclient/DrawerSettingsAdapter.java b/app/src/main/java/se/leap/bitmaskclient/DrawerSettingsAdapter.java index 89aeb4be..e2b21db9 100644 --- a/app/src/main/java/se/leap/bitmaskclient/DrawerSettingsAdapter.java +++ b/app/src/main/java/se/leap/bitmaskclient/DrawerSettingsAdapter.java @@ -45,6 +45,7 @@ public class DrawerSettingsAdapter extends BaseAdapter { public static final int BATTERY_SAVER = 3; public static final int ALWAYS_ON = 4; public static final int DONATE = 5; + public static final int PLUGGABLE_TRANSPORTS = 6; //view types public final static int VIEW_SIMPLE_TEXT = 0; diff --git a/app/src/main/java/se/leap/bitmaskclient/Provider.java b/app/src/main/java/se/leap/bitmaskclient/Provider.java index c81f5739..561453af 100644 --- a/app/src/main/java/se/leap/bitmaskclient/Provider.java +++ b/app/src/main/java/se/leap/bitmaskclient/Provider.java @@ -119,6 +119,11 @@ public final class Provider implements Parcelable { hasPrivateKey(); } + //TODO: implement me! + public boolean supportsPluggableTransports() { + return true; + } + public void setMainUrl(URL url) { mainUrl.setUrl(url); } diff --git a/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java b/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java index 640c143a..8c022b98 100644 --- a/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java @@ -55,10 +55,12 @@ import se.leap.bitmaskclient.FragmentManagerEnhanced; import se.leap.bitmaskclient.MainActivity; import se.leap.bitmaskclient.Provider; import se.leap.bitmaskclient.ProviderListActivity; +import se.leap.bitmaskclient.ProviderObservable; import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.fragments.AboutFragment; import se.leap.bitmaskclient.fragments.AlwaysOnDialog; import se.leap.bitmaskclient.fragments.LogFragment; +import se.leap.bitmaskclient.utils.PreferenceHelper; import static android.content.Context.MODE_PRIVATE; import static se.leap.bitmaskclient.BitmaskApp.getRefWatcher; @@ -74,6 +76,7 @@ import static se.leap.bitmaskclient.DrawerSettingsAdapter.DONATE; import static se.leap.bitmaskclient.DrawerSettingsAdapter.DrawerSettingsItem.getSimpleTextInstance; import static se.leap.bitmaskclient.DrawerSettingsAdapter.DrawerSettingsItem.getSwitchInstance; import static se.leap.bitmaskclient.DrawerSettingsAdapter.LOG; +import static se.leap.bitmaskclient.DrawerSettingsAdapter.PLUGGABLE_TRANSPORTS; import static se.leap.bitmaskclient.DrawerSettingsAdapter.SWITCH_PROVIDER; import static se.leap.bitmaskclient.R.string.about_fragment_title; import static se.leap.bitmaskclient.R.string.donate_title; @@ -84,7 +87,9 @@ import static se.leap.bitmaskclient.utils.PreferenceHelper.getProviderName; import static se.leap.bitmaskclient.utils.PreferenceHelper.getSaveBattery; import static se.leap.bitmaskclient.utils.PreferenceHelper.getSavedProviderFromSharedPreferences; import static se.leap.bitmaskclient.utils.PreferenceHelper.getShowAlwaysOnDialog; +import static se.leap.bitmaskclient.utils.PreferenceHelper.getUsePluggableTransports; import static se.leap.bitmaskclient.utils.PreferenceHelper.saveBattery; +import static se.leap.bitmaskclient.utils.PreferenceHelper.usePluggableTransports; /** * Fragment used for managing interactions for and presentation of a navigation drawer. @@ -254,14 +259,26 @@ public class NavigationDrawerFragment extends Fragment { private void setupSettingsListAdapter() { settingsListAdapter = new DrawerSettingsAdapter(getLayoutInflater()); - if (getContext() != null) { + if (getContext() == null) { + return; + } + + Provider currentProvider = ProviderObservable.getInstance().getCurrentProvider(); + if (currentProvider.supportsPluggableTransports()) { settingsListAdapter.addItem(getSwitchInstance(getContext(), - getString(R.string.save_battery), - R.drawable.ic_battery_36, - getSaveBattery(getContext()), - BATTERY_SAVER, - (buttonView, newStateIsChecked) -> onSwitchItemSelected(BATTERY_SAVER, newStateIsChecked))); + getString(R.string.nav_drawer_obfuscated_connection), + R.drawable.ic_bridge_36, + getUsePluggableTransports(getContext()), + PLUGGABLE_TRANSPORTS, + (buttonView, newStateIsChecked) -> onSwitchItemSelected(PLUGGABLE_TRANSPORTS, newStateIsChecked))); } + + settingsListAdapter.addItem(getSwitchInstance(getContext(), + getString(R.string.save_battery), + R.drawable.ic_battery_36, + getSaveBattery(getContext()), + BATTERY_SAVER, + (buttonView, newStateIsChecked) -> onSwitchItemSelected(BATTERY_SAVER, newStateIsChecked))); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { settingsListAdapter.addItem(getSimpleTextInstance(getContext(), getString(R.string.always_on_vpn), R.drawable.ic_always_on_36, ALWAYS_ON)); } @@ -440,10 +457,16 @@ public class NavigationDrawerFragment extends Fragment { if (newStateIsChecked) { showExperimentalFeatureAlert(); } else { - saveBattery(this.getContext(), false); + saveBattery(getContext(), false); disableSwitch(BATTERY_SAVER); } break; + case PLUGGABLE_TRANSPORTS: + if (getUsePluggableTransports(getContext()) == newStateIsChecked) { + //initial ui setup, ignore + return; + } + usePluggableTransports(getContext(), newStateIsChecked); default: break; } 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 3855fa12..9431ef61 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java @@ -48,12 +48,14 @@ import de.blinkt.openvpn.core.ConnectionStatus; import de.blinkt.openvpn.core.IOpenVPNServiceInternal; import de.blinkt.openvpn.core.OpenVPNService; import de.blinkt.openvpn.core.VpnStatus; +import de.blinkt.openvpn.core.connection.Connection; import se.leap.bitmaskclient.OnBootReceiver; import se.leap.bitmaskclient.R; import static android.app.Activity.RESULT_CANCELED; import static android.app.Activity.RESULT_OK; import static android.content.Intent.CATEGORY_DEFAULT; +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.Constants.BROADCAST_EIP_EVENT; import static se.leap.bitmaskclient.Constants.BROADCAST_GATEWAY_SETUP_OBSERVER_EVENT; @@ -76,6 +78,7 @@ import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; import static se.leap.bitmaskclient.MainActivityErrorDialog.DOWNLOAD_ERRORS.ERROR_INVALID_VPN_CERTIFICATE; import static se.leap.bitmaskclient.R.string.vpn_certificate_is_invalid; import static se.leap.bitmaskclient.utils.ConfigHelper.ensureNotOnMainThread; +import static se.leap.bitmaskclient.utils.PreferenceHelper.getUsePluggableTransports; /** * EIP is the abstract base class for interacting with and managing the Encrypted @@ -244,8 +247,9 @@ public final class EIP extends JobIntentService implements Observer { */ private boolean launchActiveGateway(Gateway gateway, int nClosestGateway) { VpnProfile profile; + Connection.TransportType transportType = getUsePluggableTransports(this) ? OBFS4 : OPENVPN; if (gateway == null || - (profile = gateway.getProfile(OPENVPN)) == null) { + (profile = gateway.getProfile(transportType)) == null) { return false; } -- cgit v1.2.3 From 84d5cf342d8e085be1996980c005b6c014f20379 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Fri, 19 Jul 2019 18:46:41 +0200 Subject: minor cleanup in EIP --- app/src/main/java/se/leap/bitmaskclient/eip/EIP.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'app/src/main/java/se') 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 9431ef61..fd498df8 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java @@ -223,9 +223,7 @@ public final class EIP extends JobIntentService implements Observer { GatewaysManager gatewaysManager = gatewaysFromPreferences(); Gateway gateway = gatewaysManager.select(0); - if (gateway != null && !gateway.getProfiles().isEmpty()) { - launchActiveGateway(gateway, 0); - } else { + if (!launchActiveGateway(gateway, 0)) { Log.d(TAG, "startEIPAlwaysOnVpn no active profile available!"); } } -- cgit v1.2.3 From 2dbca18ebd0ca4de48ad8e04a295daf31ea9083e Mon Sep 17 00:00:00 2001 From: cyBerta Date: Fri, 19 Jul 2019 18:57:11 +0200 Subject: use bridge by night unicode character instead of ghost in notifications --- app/src/main/java/se/leap/bitmaskclient/VpnNotificationManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/src/main/java/se') diff --git a/app/src/main/java/se/leap/bitmaskclient/VpnNotificationManager.java b/app/src/main/java/se/leap/bitmaskclient/VpnNotificationManager.java index abf71803..b276a402 100644 --- a/app/src/main/java/se/leap/bitmaskclient/VpnNotificationManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/VpnNotificationManager.java @@ -120,7 +120,7 @@ public class VpnNotificationManager { public void buildOpenVpnNotification(String profileName, boolean isObfuscated, String msg, String tickerText, ConnectionStatus status, long when, String notificationChannelNewstatusId) { String cancelString; CharSequence bigmessage = null; - String ghostIcon = new String(Character.toChars(0x1F47B)); + String ghostIcon = new String(Character.toChars(0x1f309)); switch (status) { // show cancel if no connection -- cgit v1.2.3 From 5a883a2119500c1d25a7f7dc650f62d5262cb9cc Mon Sep 17 00:00:00 2001 From: cyBerta Date: Thu, 1 Aug 2019 23:16:22 +0200 Subject: add Shapeshifter class managing shapeshifter go library --- .../pluggableTransports/Shapeshifter.java | 48 ++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Shapeshifter.java (limited to 'app/src/main/java/se') diff --git a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Shapeshifter.java b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Shapeshifter.java new file mode 100644 index 00000000..513a310e --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Shapeshifter.java @@ -0,0 +1,48 @@ +package se.leap.bitmaskclient.pluggableTransports; + +import android.util.Log; + +import shapeshifter.ShapeShifter; + +public class Shapeshifter { + + public static final String DISPATCHER_PORT = "4430"; + public static final String DISPATCHER_IP = "127.0.0.1"; + private static final String TAG = Shapeshifter.class.getSimpleName(); + + ShapeShifter shapeShifter; + + public Shapeshifter(DispatcherOptions options) { + shapeShifter = new ShapeShifter(); + shapeShifter.setIatMode(Long.valueOf(options.iatMode)); + shapeShifter.setSocksAddr(DISPATCHER_IP+":"+DISPATCHER_PORT); + shapeShifter.setTarget(options.remoteIP+":"+options.remotePort); + shapeShifter.setCert(options.cert); + Log.d(TAG, "shapeshifter initialized with: iat - " + shapeShifter.getIatMode() + + "; socksAddr - " + shapeShifter.getSocksAddr() + + "; target addr - " + shapeShifter.getTarget() + + "; cert - " + shapeShifter.getCert()); + } + + public boolean start() { + try { + shapeShifter.open(); + Log.d(TAG, "shapeshifter opened"); + return true; + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + public boolean stop() { + try { + shapeShifter.close(); + Log.d(TAG, "shapeshifter closed"); + return true; + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } +} -- cgit v1.2.3 From 5144166172e3620a5bd9f6df7436222afeb4d133 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Fri, 2 Aug 2019 00:46:10 +0200 Subject: rename DispatcherOptions to Obfs4Options --- .../se/leap/bitmaskclient/eip/VpnConfigGenerator.java | 8 ++++---- .../bitmaskclient/pluggableTransports/Dispatcher.java | 10 +++++----- .../pluggableTransports/DispatcherOptions.java | 18 ------------------ .../pluggableTransports/Obfs4Options.java | 18 ++++++++++++++++++ .../pluggableTransports/Shapeshifter.java | 19 ++++++++++++++++++- 5 files changed, 45 insertions(+), 28 deletions(-) delete mode 100644 app/src/main/java/se/leap/bitmaskclient/pluggableTransports/DispatcherOptions.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Obfs4Options.java (limited to 'app/src/main/java/se') 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 6b76e8d9..d9bf5dd3 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java @@ -29,7 +29,7 @@ import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.core.ConfigParser; import de.blinkt.openvpn.core.connection.Connection; import se.leap.bitmaskclient.Provider; -import se.leap.bitmaskclient.pluggableTransports.DispatcherOptions; +import se.leap.bitmaskclient.pluggableTransports.Obfs4Options; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OPENVPN; @@ -116,18 +116,18 @@ public class VpnConfigGenerator { ConfigParser icsOpenvpnConfigParser = new ConfigParser(); icsOpenvpnConfigParser.parseConfig(new StringReader(configuration)); if (transportType == OBFS4) { - icsOpenvpnConfigParser.setDispatcherOptions(getDispatcherOptions()); + icsOpenvpnConfigParser.setObfs4Options(getObfs4Options()); } return icsOpenvpnConfigParser.convertProfile(transportType); } - private DispatcherOptions getDispatcherOptions() throws JSONException { + private Obfs4Options getObfs4Options() throws JSONException { JSONObject transportOptions = obfs4Transport.getJSONObject(OPTIONS); String iatMode = transportOptions.getString("iat-mode"); String cert = transportOptions.getString("cert"); String port = obfs4Transport.getJSONArray(PORTS).getString(0); String ip = gateway.getString(IP_ADDRESS); - return new DispatcherOptions(ip, port, cert, iatMode); + return new Obfs4Options(ip, port, cert, iatMode); } private String generalConfiguration() { diff --git a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Dispatcher.java b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Dispatcher.java index 240dae75..8e787b57 100644 --- a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Dispatcher.java +++ b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Dispatcher.java @@ -46,12 +46,12 @@ public class Dispatcher { private Thread dispatcherThread = null; private int dispatcherPid = -1; - public Dispatcher(Context context, DispatcherOptions dispatcherOptions) { + public Dispatcher(Context context, Obfs4Options obfs4Options) { this.context = context.getApplicationContext(); - this.remoteIP = dispatcherOptions.remoteIP; - this.remotePort = dispatcherOptions.remotePort; - this.certificate = dispatcherOptions.cert; - this.iatMode = dispatcherOptions.iatMode; + this.remoteIP = obfs4Options.remoteIP; + this.remotePort = obfs4Options.remotePort; + this.certificate = obfs4Options.cert; + this.iatMode = obfs4Options.iatMode; } @WorkerThread diff --git a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/DispatcherOptions.java b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/DispatcherOptions.java deleted file mode 100644 index 76ccbd79..00000000 --- a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/DispatcherOptions.java +++ /dev/null @@ -1,18 +0,0 @@ -package se.leap.bitmaskclient.pluggableTransports; - -import java.io.Serializable; - -public class DispatcherOptions implements Serializable { - public String cert; - public String iatMode; - public String remoteIP; - public String remotePort; - - public DispatcherOptions(String remoteIP, String remotePort, String cert, String iatMode) { - this.cert = cert; - this.iatMode = iatMode; - this.remoteIP = remoteIP; - this.remotePort = remotePort; - } - -} diff --git a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Obfs4Options.java b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Obfs4Options.java new file mode 100644 index 00000000..2f9cb732 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Obfs4Options.java @@ -0,0 +1,18 @@ +package se.leap.bitmaskclient.pluggableTransports; + +import java.io.Serializable; + +public class Obfs4Options implements Serializable { + public String cert; + public String iatMode; + public String remoteIP; + public String remotePort; + + public Obfs4Options(String remoteIP, String remotePort, String cert, String iatMode) { + this.cert = cert; + this.iatMode = iatMode; + this.remoteIP = remoteIP; + this.remotePort = remotePort; + } + +} diff --git a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Shapeshifter.java b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Shapeshifter.java index 513a310e..175e236a 100644 --- a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Shapeshifter.java +++ b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Shapeshifter.java @@ -1,3 +1,20 @@ +/** + * Copyright (c) 2019 LEAP Encryption Access Project and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package se.leap.bitmaskclient.pluggableTransports; import android.util.Log; @@ -12,7 +29,7 @@ public class Shapeshifter { ShapeShifter shapeShifter; - public Shapeshifter(DispatcherOptions options) { + public Shapeshifter(Obfs4Options options) { shapeShifter = new ShapeShifter(); shapeShifter.setIatMode(Long.valueOf(options.iatMode)); shapeShifter.setSocksAddr(DISPATCHER_IP+":"+DISPATCHER_PORT); -- cgit v1.2.3 From fa04548dbdf1403e67682fc984bef9941e2f4c78 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Fri, 2 Aug 2019 00:47:26 +0200 Subject: remove unused context from GatewaysManager --- app/src/main/java/se/leap/bitmaskclient/eip/EIP.java | 2 +- app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) (limited to 'app/src/main/java/se') 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 fd498df8..19c539e8 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java @@ -288,7 +288,7 @@ public final class EIP extends JobIntentService implements Observer { * @return GatewaysManager */ private GatewaysManager gatewaysFromPreferences() { - GatewaysManager gatewaysManager = new GatewaysManager(this, preferences); + GatewaysManager gatewaysManager = new GatewaysManager(preferences); gatewaysManager.configureFromPreferences(); return gatewaysManager; } diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java index f7038bab..740df97f 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java @@ -44,13 +44,11 @@ public class GatewaysManager { private static final String TAG = GatewaysManager.class.getSimpleName(); - private Context context; private SharedPreferences preferences; private LinkedHashMap gateways = new LinkedHashMap<>(); private Type listType = new TypeToken>() {}.getType(); - GatewaysManager(Context context, SharedPreferences preferences) { - this.context = context; + GatewaysManager(SharedPreferences preferences) { this.preferences = preferences; } -- cgit v1.2.3 From 37f9cf65b3267f081c4b80c62240fc7754a30325 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Sat, 3 Aug 2019 19:00:05 +0200 Subject: create custom UI classes navigation drawer entries --- .../leap/bitmaskclient/views/IconSwitchEntry.java | 96 ++++++++++++++++++++++ .../se/leap/bitmaskclient/views/IconTextEntry.java | 84 +++++++++++++++++++ 2 files changed, 180 insertions(+) create mode 100644 app/src/main/java/se/leap/bitmaskclient/views/IconSwitchEntry.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/views/IconTextEntry.java (limited to 'app/src/main/java/se') diff --git a/app/src/main/java/se/leap/bitmaskclient/views/IconSwitchEntry.java b/app/src/main/java/se/leap/bitmaskclient/views/IconSwitchEntry.java new file mode 100644 index 00000000..82e02a6e --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/views/IconSwitchEntry.java @@ -0,0 +1,96 @@ +package se.leap.bitmaskclient.views; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.support.annotation.DrawableRes; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.v7.widget.SwitchCompat; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.CompoundButton; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import se.leap.bitmaskclient.R; + +public class IconSwitchEntry extends LinearLayout { + + private TextView textView; + private ImageView iconView; + private SwitchCompat switchView; + private CompoundButton.OnCheckedChangeListener checkedChangeListener; + + public IconSwitchEntry(Context context) { + super(context); + initLayout(context, null); + } + + public IconSwitchEntry(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + initLayout(context, attrs); + } + + public IconSwitchEntry(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initLayout(context, attrs); + } + + @TargetApi(21) + public IconSwitchEntry(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + initLayout(context, attrs); + } + + void initLayout(Context context, AttributeSet attrs) { + LayoutInflater inflater = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + View rootview = inflater.inflate(R.layout.v_switch_list_item, this, true); + textView = rootview.findViewById(android.R.id.text1); + iconView = rootview.findViewById(R.id.material_icon); + switchView = rootview.findViewById(R.id.option_switch); + + if (attrs != null) { + TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.IconSwitchEntry); + + String entryText = typedArray.getString(R.styleable.IconTextEntry_text); + if (entryText != null) { + textView.setText(entryText); + } + + Drawable drawable = typedArray.getDrawable(R.styleable.IconTextEntry_icon); + if (drawable != null) { + iconView.setImageDrawable(drawable); + } + + typedArray.recycle(); + } + } + + public void setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener listener) { + checkedChangeListener = listener; + switchView.setOnCheckedChangeListener(checkedChangeListener); + } + + public void setText(@StringRes int id) { + textView.setText(id); + } + + public void setIcon(@DrawableRes int id) { + iconView.setImageResource(id); + } + + public void setChecked(boolean isChecked) { + switchView.setChecked(isChecked); + } + + public void setCheckedQuietly(boolean isChecked) { + switchView.setOnCheckedChangeListener(null); + switchView.setChecked(isChecked); + switchView.setOnCheckedChangeListener(checkedChangeListener); + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/views/IconTextEntry.java b/app/src/main/java/se/leap/bitmaskclient/views/IconTextEntry.java new file mode 100644 index 00000000..5689f429 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/views/IconTextEntry.java @@ -0,0 +1,84 @@ +package se.leap.bitmaskclient.views; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.support.annotation.DrawableRes; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import se.leap.bitmaskclient.R; + + +public class IconTextEntry extends LinearLayout { + + private TextView textView; + private ImageView iconView; + + public IconTextEntry(Context context) { + super(context); + initLayout(context, null); + } + + public IconTextEntry(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + initLayout(context, attrs); + } + + public IconTextEntry(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initLayout(context, attrs); + } + + @TargetApi(21) + public IconTextEntry(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + initLayout(context, attrs); + } + + void initLayout(Context context, AttributeSet attrs) { + LayoutInflater inflater = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + View rootview = inflater.inflate(R.layout.v_icon_text_list_item, this, true); + textView = rootview.findViewById(android.R.id.text1); + iconView = rootview.findViewById(R.id.material_icon); + + if (attrs != null) { + TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.IconTextEntry); + + String entryText = typedArray.getString(R.styleable.IconTextEntry_text); + if (entryText != null) { + textView.setText(entryText); + } + + Drawable drawable = typedArray.getDrawable(R.styleable.IconTextEntry_icon); + if (drawable != null) { + iconView.setImageDrawable(drawable); + } + + typedArray.recycle(); + } + + + } + + public void setText(@StringRes int id) { + textView.setText(id); + } + + public void setText(CharSequence text) { + textView.setText(text); + } + + public void setIcon(@DrawableRes int id) { + iconView.setImageResource(id); + } + +} -- cgit v1.2.3 From e45a11bb75d81ed4c395e4a1a9a80226a85b8742 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Sat, 3 Aug 2019 19:09:26 +0200 Subject: rewrite Navigation drawer - improves layout on small screens, fixes switch entry UI bug --- .../leap/bitmaskclient/DrawerSettingsAdapter.java | 246 ----------------- .../drawer/NavigationDrawerFragment.java | 302 +++++++++------------ 2 files changed, 121 insertions(+), 427 deletions(-) delete mode 100644 app/src/main/java/se/leap/bitmaskclient/DrawerSettingsAdapter.java (limited to 'app/src/main/java/se') diff --git a/app/src/main/java/se/leap/bitmaskclient/DrawerSettingsAdapter.java b/app/src/main/java/se/leap/bitmaskclient/DrawerSettingsAdapter.java deleted file mode 100644 index e2b21db9..00000000 --- a/app/src/main/java/se/leap/bitmaskclient/DrawerSettingsAdapter.java +++ /dev/null @@ -1,246 +0,0 @@ -/** - * 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 . - */ -package se.leap.bitmaskclient; - -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.support.annotation.DrawableRes; -import android.support.annotation.NonNull; -import android.support.v7.widget.SwitchCompat; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.CompoundButton; -import android.widget.ImageView; -import android.widget.TextView; - -import java.util.ArrayList; - -/** - * Created by cyberta on 21.02.18. - */ - -public class DrawerSettingsAdapter extends BaseAdapter { - - //item types - public static final int NONE = -1; - public static final int SWITCH_PROVIDER = 0; - public static final int LOG = 1; - public static final int ABOUT = 2; - public static final int BATTERY_SAVER = 3; - public static final int ALWAYS_ON = 4; - public static final int DONATE = 5; - public static final int PLUGGABLE_TRANSPORTS = 6; - - //view types - public final static int VIEW_SIMPLE_TEXT = 0; - public final static int VIEW_SWITCH = 1; - - public static class DrawerSettingsItem { - private String description = ""; - private int viewType = VIEW_SIMPLE_TEXT; - private boolean isChecked = false; - private int itemType = NONE; - private CompoundButton.OnCheckedChangeListener callback; - private Drawable iconResource; - - private DrawerSettingsItem(Context context, String description, @DrawableRes int iconResource, int viewType, boolean isChecked, int itemType, CompoundButton.OnCheckedChangeListener callback) { - this.description = description; - this.viewType = viewType; - this.isChecked = isChecked; - this.itemType = itemType; - this.callback = callback; - try { - this.iconResource = context.getResources().getDrawable(iconResource); - } catch (RuntimeException e) { - e.printStackTrace(); - } - } - - public static DrawerSettingsItem getSimpleTextInstance(Context context, String description, @DrawableRes int iconResource, int itemType) { - return new DrawerSettingsItem(context, description, iconResource, VIEW_SIMPLE_TEXT, false, itemType, null); - } - - public static DrawerSettingsItem getSwitchInstance(Context context, String description, @DrawableRes int iconResource, boolean isChecked, int itemType, CompoundButton.OnCheckedChangeListener callback) { - return new DrawerSettingsItem(context, description, iconResource, VIEW_SWITCH, isChecked, itemType, callback); - } - - public int getItemType() { - return itemType; - } - - public void setChecked(boolean checked) { - isChecked = checked; - } - - public boolean isChecked() { - return isChecked; - } - } - - private ArrayList mData = new ArrayList<>(); - private LayoutInflater mInflater; - - public DrawerSettingsAdapter(LayoutInflater layoutInflater) { - mInflater = layoutInflater; - } - - public void addItem(final DrawerSettingsItem item) { - mData.add(item); - notifyDataSetChanged(); - } - - @Override - public int getItemViewType(int position) { - DrawerSettingsItem item = mData.get(position); - return item.viewType; - } - - @Override - public int getViewTypeCount() { - boolean hasSwitchItem = false; - for (DrawerSettingsItem item : mData) { - if (item.viewType == VIEW_SWITCH) { - hasSwitchItem = true; - break; - } - } - return hasSwitchItem ? 2 : 1; - } - - @Override - public int getCount() { - return mData.size(); - } - - @Override - public DrawerSettingsItem getItem(int position) { - return mData.get(position); - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - - DrawerSettingsItem drawerSettingsItem = mData.get(position); - ViewHolder holder = null; - int type = getItemViewType(position); - if (convertView == null) { - holder = new ViewHolder(); - switch(type) { - case VIEW_SIMPLE_TEXT: - convertView = initTextViewBinding(holder); - bindSimpleText(drawerSettingsItem, holder); - break; - case VIEW_SWITCH: - convertView = initSwitchBinding(holder); - bindSwitch(drawerSettingsItem, holder); - break; - } - convertView.setTag(holder); - } else { - holder = (ViewHolder)convertView.getTag(); - switch (type) { - case VIEW_SIMPLE_TEXT: - if (holder.isSwitchViewHolder()) { - holder.resetSwitchView(); - convertView = initTextViewBinding(holder); - } - bindSimpleText(drawerSettingsItem, holder); - break; - case VIEW_SWITCH: - if (!holder.isSwitchViewHolder()) { - holder.resetTextView(); - convertView = initSwitchBinding(holder); - } - bindSwitch(drawerSettingsItem, holder); - break; - } - convertView.setTag(holder); - } - return convertView; - } - - private void bindSimpleText(DrawerSettingsItem drawerSettingsItem, ViewHolder holder) { - holder.textView.setText(drawerSettingsItem.description); - if (drawerSettingsItem.iconResource != null) { - holder.iconView.setImageDrawable(drawerSettingsItem.iconResource); - } - } - - private void bindSwitch(DrawerSettingsItem drawerSettingsItem, ViewHolder holder) { - holder.switchView.setChecked(drawerSettingsItem.isChecked); - holder.textView.setText(drawerSettingsItem.description); - holder.switchView.setOnCheckedChangeListener(drawerSettingsItem.callback); - if (drawerSettingsItem.iconResource != null) { - holder.iconView.setImageDrawable(drawerSettingsItem.iconResource); - } - } - - @NonNull - private View initSwitchBinding(ViewHolder holder) { - View convertView = mInflater.inflate(R.layout.v_switch_list_item, null); - holder.switchView = convertView.findViewById(R.id.option_switch); - holder.textView = convertView.findViewById(android.R.id.text1); - holder.iconView = convertView.findViewById(R.id.material_icon); - return convertView; - } - - @NonNull - private View initTextViewBinding(ViewHolder holder) { - View convertView = mInflater.inflate(R.layout.v_icon_text_list_item, null); - holder.textView = convertView.findViewById(android.R.id.text1); - holder.iconView = convertView.findViewById(R.id.material_icon); - return convertView; - } - - public DrawerSettingsItem getDrawerItem(int elementType) { - for (DrawerSettingsItem item : mData) { - if (item.itemType == elementType) { - return item; - } - } - return null; - } - - static class ViewHolder { - TextView textView; - ImageView iconView; - SwitchCompat switchView; - - boolean isSwitchViewHolder() { - return switchView != null; - } - - void resetSwitchView() { - switchView.setOnCheckedChangeListener(null); - switchView = null; - } - - void resetTextView() { - textView = null; - } - } -} - - - diff --git a/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java b/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java index 8c022b98..380b765e 100644 --- a/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018 LEAP Encryption Access Project and contributers + * Copyright (c) 2019 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 @@ -18,7 +18,6 @@ package se.leap.bitmaskclient.drawer; import android.app.Activity; -import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Configuration; @@ -44,12 +43,7 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.ListView; -import se.leap.bitmaskclient.DrawerSettingsAdapter; -import se.leap.bitmaskclient.DrawerSettingsAdapter.DrawerSettingsItem; import se.leap.bitmaskclient.EipFragment; import se.leap.bitmaskclient.FragmentManagerEnhanced; import se.leap.bitmaskclient.MainActivity; @@ -60,32 +54,22 @@ import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.fragments.AboutFragment; import se.leap.bitmaskclient.fragments.AlwaysOnDialog; import se.leap.bitmaskclient.fragments.LogFragment; -import se.leap.bitmaskclient.utils.PreferenceHelper; +import se.leap.bitmaskclient.views.IconSwitchEntry; +import se.leap.bitmaskclient.views.IconTextEntry; import static android.content.Context.MODE_PRIVATE; +import static android.view.View.GONE; +import static android.view.View.VISIBLE; import static se.leap.bitmaskclient.BitmaskApp.getRefWatcher; import static se.leap.bitmaskclient.Constants.DONATION_URL; import static se.leap.bitmaskclient.Constants.ENABLE_DONATION; import static se.leap.bitmaskclient.Constants.PROVIDER_KEY; import static se.leap.bitmaskclient.Constants.REQUEST_CODE_SWITCH_PROVIDER; import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; -import static se.leap.bitmaskclient.DrawerSettingsAdapter.ABOUT; -import static se.leap.bitmaskclient.DrawerSettingsAdapter.ALWAYS_ON; -import static se.leap.bitmaskclient.DrawerSettingsAdapter.BATTERY_SAVER; -import static se.leap.bitmaskclient.DrawerSettingsAdapter.DONATE; -import static se.leap.bitmaskclient.DrawerSettingsAdapter.DrawerSettingsItem.getSimpleTextInstance; -import static se.leap.bitmaskclient.DrawerSettingsAdapter.DrawerSettingsItem.getSwitchInstance; -import static se.leap.bitmaskclient.DrawerSettingsAdapter.LOG; -import static se.leap.bitmaskclient.DrawerSettingsAdapter.PLUGGABLE_TRANSPORTS; -import static se.leap.bitmaskclient.DrawerSettingsAdapter.SWITCH_PROVIDER; import static se.leap.bitmaskclient.R.string.about_fragment_title; -import static se.leap.bitmaskclient.R.string.donate_title; import static se.leap.bitmaskclient.R.string.log_fragment_title; -import static se.leap.bitmaskclient.R.string.switch_provider_menu_option; import static se.leap.bitmaskclient.utils.ConfigHelper.isDefaultBitmask; -import static se.leap.bitmaskclient.utils.PreferenceHelper.getProviderName; import static se.leap.bitmaskclient.utils.PreferenceHelper.getSaveBattery; -import static se.leap.bitmaskclient.utils.PreferenceHelper.getSavedProviderFromSharedPreferences; import static se.leap.bitmaskclient.utils.PreferenceHelper.getShowAlwaysOnDialog; import static se.leap.bitmaskclient.utils.PreferenceHelper.getUsePluggableTransports; import static se.leap.bitmaskclient.utils.PreferenceHelper.saveBattery; @@ -114,11 +98,10 @@ public class NavigationDrawerFragment extends Fragment { private DrawerLayout drawerLayout; private View drawerView; - private ListView drawerAccountsListView; private View fragmentContainerView; - private ArrayAdapter accountListAdapter; - private DrawerSettingsAdapter settingsListAdapter; private Toolbar toolbar; + private IconTextEntry account; + private IconSwitchEntry saveBattery; private boolean userLearnedDrawer; private volatile boolean wasPaused; @@ -188,14 +171,8 @@ public class NavigationDrawerFragment extends Fragment { this.drawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START); toolbar = this.drawerLayout.findViewById(R.id.toolbar); - final ActionBar actionBar = setupActionBar(); - setupSettingsListAdapter(); - setupSettingsListView(); - accountListAdapter = new ArrayAdapter<>(actionBar.getThemedContext(), - R.layout.v_icon_text_list_item, - android.R.id.text1); - refreshAccountListAdapter(); - setupAccountsListView(); + setupActionBar(); + setupEntries(); setupActionBarDrawerToggle(activity); if (!userLearnedDrawer) { @@ -245,51 +222,123 @@ public class NavigationDrawerFragment extends Fragment { }; } - private void setupAccountsListView() { - drawerAccountsListView = drawerView.findViewById(R.id.accountList); - drawerAccountsListView.setAdapter(accountListAdapter); - drawerAccountsListView.setOnItemClickListener((parent, view, position, id) -> selectItem(parent, position)); + private void setupEntries() { + initAccountEntry(); + initSwitchProviderEntry(); + initUseBridgesEntry(); + initSaveBatteryEntry(); + initAlwaysOnVpnEntry(); + initDonateEntry(); + initLogEntry(); + initAboutEntry(); } - private void setupSettingsListView() { - ListView drawerSettingsListView = drawerView.findViewById(R.id.settingsList); - drawerSettingsListView.setOnItemClickListener((parent, view, position, id) -> selectItem(parent, position)); - drawerSettingsListView.setAdapter(settingsListAdapter); + private void initAccountEntry() { + account = drawerView.findViewById(R.id.account); + FragmentManagerEnhanced fragmentManager = new FragmentManagerEnhanced(getActivity().getSupportFragmentManager()); + Provider currentProvider = ProviderObservable.getInstance().getCurrentProvider(); + account.setText(currentProvider.getName()); + account.setOnClickListener((buttonView) -> { + Fragment fragment = new EipFragment(); + Bundle arguments = new Bundle(); + arguments.putParcelable(PROVIDER_KEY, currentProvider); + fragment.setArguments(arguments); + hideActionBarSubTitle(); + fragmentManager.replace(R.id.main_container, fragment, MainActivity.TAG); + closeDrawer(); + }); } - private void setupSettingsListAdapter() { - settingsListAdapter = new DrawerSettingsAdapter(getLayoutInflater()); - if (getContext() == null) { - return; + private void initSwitchProviderEntry() { + if (isDefaultBitmask()) { + IconTextEntry switchProvider = drawerView.findViewById(R.id.switch_provider); + switchProvider.setVisibility(VISIBLE); + switchProvider.setOnClickListener(v -> + getActivity().startActivityForResult(new Intent(getActivity(), ProviderListActivity.class), REQUEST_CODE_SWITCH_PROVIDER)); } + } - Provider currentProvider = ProviderObservable.getInstance().getCurrentProvider(); - if (currentProvider.supportsPluggableTransports()) { - settingsListAdapter.addItem(getSwitchInstance(getContext(), - getString(R.string.nav_drawer_obfuscated_connection), - R.drawable.ic_bridge_36, - getUsePluggableTransports(getContext()), - PLUGGABLE_TRANSPORTS, - (buttonView, newStateIsChecked) -> onSwitchItemSelected(PLUGGABLE_TRANSPORTS, newStateIsChecked))); + private void initUseBridgesEntry() { + IconSwitchEntry useBridges = drawerView.findViewById(R.id.bridges_switch); + if (ProviderObservable.getInstance().getCurrentProvider().supportsPluggableTransports()) { + useBridges.setVisibility(VISIBLE); + useBridges.setChecked(getUsePluggableTransports(getContext())); + useBridges.setOnCheckedChangeListener((buttonView, isChecked) -> + usePluggableTransports(getContext(), isChecked)); + } else { + useBridges.setVisibility(GONE); } + } - settingsListAdapter.addItem(getSwitchInstance(getContext(), - getString(R.string.save_battery), - R.drawable.ic_battery_36, - getSaveBattery(getContext()), - BATTERY_SAVER, - (buttonView, newStateIsChecked) -> onSwitchItemSelected(BATTERY_SAVER, newStateIsChecked))); + private void initSaveBatteryEntry() { + saveBattery = drawerView.findViewById(R.id.battery_switch); + saveBattery.setChecked(getSaveBattery(getContext())); + saveBattery.setOnCheckedChangeListener(((buttonView, isChecked) -> { + if (isChecked) { + showExperimentalFeatureAlert(); + } else { + saveBattery(getContext(), false); + } + })); + } + + private void initAlwaysOnVpnEntry() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - settingsListAdapter.addItem(getSimpleTextInstance(getContext(), getString(R.string.always_on_vpn), R.drawable.ic_always_on_36, ALWAYS_ON)); - } - if (isDefaultBitmask()) { - settingsListAdapter.addItem(getSimpleTextInstance(getContext(), getString(switch_provider_menu_option), R.drawable.ic_switch_provider_36, SWITCH_PROVIDER)); + IconTextEntry alwaysOnVpn = drawerView.findViewById(R.id.always_on_vpn); + alwaysOnVpn.setVisibility(VISIBLE); + alwaysOnVpn.setOnClickListener((buttonView) -> { + closeDrawer(); + if (getShowAlwaysOnDialog(getContext())) { + showAlwaysOnDialog(); + } else { + Intent intent = new Intent("android.net.vpn.SETTINGS"); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } + }); } - settingsListAdapter.addItem(getSimpleTextInstance(getContext(), getString(log_fragment_title), R.drawable.ic_log_36, LOG)); + } + + private void initDonateEntry() { if (ENABLE_DONATION) { - settingsListAdapter.addItem(getSimpleTextInstance(getContext(), getString(donate_title), R.drawable.ic_donate_36, DONATE)); + IconTextEntry donate = drawerView.findViewById(R.id.donate); + donate.setVisibility(VISIBLE); + donate.setOnClickListener((buttonView) -> { + closeDrawer(); + Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(DONATION_URL)); + startActivity(browserIntent); + + }); + } + } + + private void initLogEntry() { + IconTextEntry log = drawerView.findViewById(R.id.log); + FragmentManagerEnhanced fragmentManager = new FragmentManagerEnhanced(getActivity().getSupportFragmentManager()); + log.setOnClickListener((buttonView) -> { + closeDrawer(); + Fragment fragment = new LogFragment(); + setActionBarTitle(log_fragment_title); + fragmentManager.replace(R.id.main_container, fragment, MainActivity.TAG); + }); + + } + + private void initAboutEntry() { + IconTextEntry about = drawerView.findViewById(R.id.about); + FragmentManagerEnhanced fragmentManager = new FragmentManagerEnhanced(getActivity().getSupportFragmentManager()); + about.setOnClickListener((buttonView) -> { + closeDrawer(); + Fragment fragment = new AboutFragment(); + setActionBarTitle(about_fragment_title); + fragmentManager.replace(R.id.main_container, fragment, MainActivity.TAG); + }); + } + + private void closeDrawer() { + if (drawerLayout != null) { + drawerLayout.closeDrawer(fragmentContainerView); } - settingsListAdapter.addItem(getSimpleTextInstance(getContext(), getString(about_fragment_title), R.drawable.ic_about_36, ABOUT)); } private ActionBar setupActionBar() { @@ -337,16 +386,6 @@ public class NavigationDrawerFragment extends Fragment { }, TWO_SECONDS); } - private void selectItem(AdapterView list, int position) { - if (list != null) { - ((ListView) list).setItemChecked(position, true); - } - if (drawerLayout != null) { - drawerLayout.closeDrawer(fragmentContainerView); - } - onTextItemSelected(list, position); - } - @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); @@ -374,17 +413,11 @@ public class NavigationDrawerFragment extends Fragment { .setTitle(activity.getString(R.string.save_battery)) .setMessage(activity.getString(R.string.save_battery_message)) .setPositiveButton((android.R.string.yes), (dialog, which) -> { - DrawerSettingsItem item = settingsListAdapter.getDrawerItem(BATTERY_SAVER); - item.setChecked(true); - settingsListAdapter.notifyDataSetChanged(); - saveBattery(getContext(), item.isChecked()); + saveBattery(getContext(), true); }) - .setNegativeButton(activity.getString(android.R.string.no), (dialog, which) -> disableSwitch(BATTERY_SAVER)).setOnDismissListener(new DialogInterface.OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialog) { - showEnableExperimentalFeature = false; - } - }).setOnCancelListener(dialog -> disableSwitch(BATTERY_SAVER)).show(); + .setNegativeButton(activity.getString(android.R.string.no), (dialog, which) -> saveBattery.setCheckedQuietly(false)) + .setOnDismissListener(dialog -> showEnableExperimentalFeature = false) + .setOnCancelListener(dialog -> saveBattery.setCheckedQuietly(false)).show(); } catch (IllegalStateException e) { e.printStackTrace(); } @@ -447,87 +480,6 @@ public class NavigationDrawerFragment extends Fragment { return ((AppCompatActivity) getActivity()).getSupportActionBar(); } - private void onSwitchItemSelected(int elementType, boolean newStateIsChecked) { - switch (elementType) { - case BATTERY_SAVER: - if (getSaveBattery(getContext()) == newStateIsChecked) { - //initial ui setup, ignore - return; - } - if (newStateIsChecked) { - showExperimentalFeatureAlert(); - } else { - saveBattery(getContext(), false); - disableSwitch(BATTERY_SAVER); - } - break; - case PLUGGABLE_TRANSPORTS: - if (getUsePluggableTransports(getContext()) == newStateIsChecked) { - //initial ui setup, ignore - return; - } - usePluggableTransports(getContext(), newStateIsChecked); - default: - break; - } - } - - private void disableSwitch(int elementType) { - DrawerSettingsItem item = settingsListAdapter.getDrawerItem(elementType); - item.setChecked(false); - settingsListAdapter.notifyDataSetChanged(); - } - - public void onTextItemSelected(AdapterView parent, int position) { - // update the main content by replacing fragments - FragmentManagerEnhanced fragmentManager = new FragmentManagerEnhanced(getActivity().getSupportFragmentManager()); - Fragment fragment = null; - - if (parent == drawerAccountsListView) { - fragment = new EipFragment(); - Bundle arguments = new Bundle(); - Provider currentProvider = getSavedProviderFromSharedPreferences(preferences); - arguments.putParcelable(PROVIDER_KEY, currentProvider); - fragment.setArguments(arguments); - hideActionBarSubTitle(); - } else { - DrawerSettingsItem settingsItem = settingsListAdapter.getItem(position); - switch (settingsItem.getItemType()) { - case SWITCH_PROVIDER: - getActivity().startActivityForResult(new Intent(getActivity(), ProviderListActivity.class), REQUEST_CODE_SWITCH_PROVIDER); - break; - case LOG: - fragment = new LogFragment(); - setActionBarTitle(log_fragment_title); - break; - case ABOUT: - fragment = new AboutFragment(); - setActionBarTitle(about_fragment_title); - break; - case ALWAYS_ON: - if (getShowAlwaysOnDialog(getContext())) { - showAlwaysOnDialog(); - } else { - Intent intent = new Intent("android.net.vpn.SETTINGS"); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); - } - break; - case DONATE: - Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(DONATION_URL)); - startActivity(browserIntent); - break; - default: - break; - } - } - - if (fragment != null) { - fragmentManager.replace(R.id.main_container, fragment, MainActivity.TAG); - } - - } - private void setActionBarTitle(@StringRes int resId) { ActionBar actionBar = getActionBar(); if (actionBar != null) { @@ -542,22 +494,10 @@ public class NavigationDrawerFragment extends Fragment { } } - public void refresh() { - refreshAccountListAdapter(); - accountListAdapter.notifyDataSetChanged(); - drawerAccountsListView.setAdapter(accountListAdapter); - } - - private void refreshAccountListAdapter() { - accountListAdapter.clear(); - String providerName = getProviderName(preferences); - if (providerName == null) { - //TODO: ADD A header to the ListView containing a useful message. - //TODO 2: disable switchProvider - } else { - accountListAdapter.add(providerName); - } + Provider currentProvider = ProviderObservable.getInstance().getCurrentProvider(); + account.setText(currentProvider.getName()); + initUseBridgesEntry(); } } -- cgit v1.2.3 From 9044c3fdc13f02b7c21bb181d769fadb71924b75 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Thu, 19 Sep 2019 22:41:30 +0200 Subject: add subtitles to navigation drawer items - better explanation for PT --- .../main/java/se/leap/bitmaskclient/views/IconSwitchEntry.java | 8 ++++++++ app/src/main/java/se/leap/bitmaskclient/views/IconTextEntry.java | 8 ++++++++ 2 files changed, 16 insertions(+) (limited to 'app/src/main/java/se') diff --git a/app/src/main/java/se/leap/bitmaskclient/views/IconSwitchEntry.java b/app/src/main/java/se/leap/bitmaskclient/views/IconSwitchEntry.java index 82e02a6e..02347b05 100644 --- a/app/src/main/java/se/leap/bitmaskclient/views/IconSwitchEntry.java +++ b/app/src/main/java/se/leap/bitmaskclient/views/IconSwitchEntry.java @@ -21,6 +21,7 @@ import se.leap.bitmaskclient.R; public class IconSwitchEntry extends LinearLayout { private TextView textView; + private TextView subtitleView; private ImageView iconView; private SwitchCompat switchView; private CompoundButton.OnCheckedChangeListener checkedChangeListener; @@ -51,6 +52,7 @@ public class IconSwitchEntry extends LinearLayout { .getSystemService(Context.LAYOUT_INFLATER_SERVICE); View rootview = inflater.inflate(R.layout.v_switch_list_item, this, true); textView = rootview.findViewById(android.R.id.text1); + subtitleView = rootview.findViewById(R.id.subtitle); iconView = rootview.findViewById(R.id.material_icon); switchView = rootview.findViewById(R.id.option_switch); @@ -62,6 +64,12 @@ public class IconSwitchEntry extends LinearLayout { textView.setText(entryText); } + String subtitle = typedArray.getString(R.styleable.IconTextEntry_subtitle); + if (subtitle != null) { + subtitleView.setText(subtitle); + subtitleView.setVisibility(VISIBLE); + } + Drawable drawable = typedArray.getDrawable(R.styleable.IconTextEntry_icon); if (drawable != null) { iconView.setImageDrawable(drawable); diff --git a/app/src/main/java/se/leap/bitmaskclient/views/IconTextEntry.java b/app/src/main/java/se/leap/bitmaskclient/views/IconTextEntry.java index 5689f429..0e86f506 100644 --- a/app/src/main/java/se/leap/bitmaskclient/views/IconTextEntry.java +++ b/app/src/main/java/se/leap/bitmaskclient/views/IconTextEntry.java @@ -21,6 +21,7 @@ public class IconTextEntry extends LinearLayout { private TextView textView; private ImageView iconView; + private TextView subtitleView; public IconTextEntry(Context context) { super(context); @@ -48,6 +49,7 @@ public class IconTextEntry extends LinearLayout { .getSystemService(Context.LAYOUT_INFLATER_SERVICE); View rootview = inflater.inflate(R.layout.v_icon_text_list_item, this, true); textView = rootview.findViewById(android.R.id.text1); + subtitleView = rootview.findViewById(R.id.subtitle); iconView = rootview.findViewById(R.id.material_icon); if (attrs != null) { @@ -58,6 +60,12 @@ public class IconTextEntry extends LinearLayout { textView.setText(entryText); } + String subtitle = typedArray.getString(R.styleable.IconTextEntry_subtitle); + if (subtitle != null) { + subtitleView.setText(subtitle); + subtitleView.setVisibility(VISIBLE); + } + Drawable drawable = typedArray.getDrawable(R.styleable.IconTextEntry_icon); if (drawable != null) { iconView.setImageDrawable(drawable); -- cgit v1.2.3 From 67a2040e96048e164b592fab4b0e8f123a7dd68f Mon Sep 17 00:00:00 2001 From: cyBerta Date: Fri, 20 Sep 2019 00:07:44 +0200 Subject: add supportsPluggableTransports() to Provider class including Tests --- .../main/java/se/leap/bitmaskclient/Provider.java | 24 ++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) (limited to 'app/src/main/java/se') diff --git a/app/src/main/java/se/leap/bitmaskclient/Provider.java b/app/src/main/java/se/leap/bitmaskclient/Provider.java index 561453af..067f9b2e 100644 --- a/app/src/main/java/se/leap/bitmaskclient/Provider.java +++ b/app/src/main/java/se/leap/bitmaskclient/Provider.java @@ -21,6 +21,7 @@ import android.os.Parcelable; import com.google.gson.Gson; +import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -28,8 +29,13 @@ import java.net.MalformedURLException; import java.net.URL; import java.util.Locale; +import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4; +import static se.leap.bitmaskclient.Constants.CAPABILITIES; +import static se.leap.bitmaskclient.Constants.GATEWAYS; import static se.leap.bitmaskclient.Constants.PROVIDER_ALLOWED_REGISTERED; import static se.leap.bitmaskclient.Constants.PROVIDER_ALLOW_ANONYMOUS; +import static se.leap.bitmaskclient.Constants.TRANSPORT; +import static se.leap.bitmaskclient.Constants.TYPE; import static se.leap.bitmaskclient.ProviderAPI.ERRORS; /** @@ -119,9 +125,23 @@ public final class Provider implements Parcelable { hasPrivateKey(); } - //TODO: implement me! public boolean supportsPluggableTransports() { - return true; + try { + JSONArray gatewayJsons = eipServiceJson.getJSONArray(GATEWAYS); + for (int i = 0; i < gatewayJsons.length(); i++) { + JSONArray transports = gatewayJsons.getJSONObject(i). + getJSONObject(CAPABILITIES). + getJSONArray(TRANSPORT); + for (int j = 0; j < transports.length(); j++) { + if (OBFS4.toString().equals(transports.getJSONObject(j).getString(TYPE))) { + return true; + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return false; } public void setMainUrl(URL url) { -- cgit v1.2.3 From e52d759c635a25969079503991e825b3f373cbcc Mon Sep 17 00:00:00 2001 From: cyBerta Date: Fri, 20 Sep 2019 00:26:27 +0200 Subject: restart vpn after pluggableTransports was selected or deselected --- .../leap/bitmaskclient/drawer/NavigationDrawerFragment.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'app/src/main/java/se') diff --git a/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java b/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java index 380b765e..7d97dfbb 100644 --- a/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java @@ -44,6 +44,7 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import de.blinkt.openvpn.core.VpnStatus; import se.leap.bitmaskclient.EipFragment; import se.leap.bitmaskclient.FragmentManagerEnhanced; import se.leap.bitmaskclient.MainActivity; @@ -51,6 +52,7 @@ import se.leap.bitmaskclient.Provider; import se.leap.bitmaskclient.ProviderListActivity; import se.leap.bitmaskclient.ProviderObservable; import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.eip.EipCommand; import se.leap.bitmaskclient.fragments.AboutFragment; import se.leap.bitmaskclient.fragments.AlwaysOnDialog; import se.leap.bitmaskclient.fragments.LogFragment; @@ -263,8 +265,15 @@ public class NavigationDrawerFragment extends Fragment { if (ProviderObservable.getInstance().getCurrentProvider().supportsPluggableTransports()) { useBridges.setVisibility(VISIBLE); useBridges.setChecked(getUsePluggableTransports(getContext())); - useBridges.setOnCheckedChangeListener((buttonView, isChecked) -> - usePluggableTransports(getContext(), isChecked)); + useBridges.setOnCheckedChangeListener((buttonView, isChecked) -> { + usePluggableTransports(getContext(), isChecked); + if (VpnStatus.isVPNActive()) { + EipCommand.startVPN(getContext(), false); + closeDrawerWithDelay(); + } + }); + + } else { useBridges.setVisibility(GONE); } -- cgit v1.2.3 From 91877ac8ed64927d14c0762cd4402e5ed736fab9 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Fri, 27 Sep 2019 12:17:52 +0200 Subject: fix flickering when restarting the vpn --- .../java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app/src/main/java/se') diff --git a/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java b/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java index 7d97dfbb..3db86205 100644 --- a/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java @@ -268,8 +268,8 @@ public class NavigationDrawerFragment extends Fragment { useBridges.setOnCheckedChangeListener((buttonView, isChecked) -> { usePluggableTransports(getContext(), isChecked); if (VpnStatus.isVPNActive()) { - EipCommand.startVPN(getContext(), false); - closeDrawerWithDelay(); + EipCommand.startVPN(getContext(), true); + closeDrawer(); } }); -- cgit v1.2.3 From eebba26668588d5e644441b0dbcc04eb9f9f3eac Mon Sep 17 00:00:00 2001 From: cyBerta Date: Fri, 27 Sep 2019 16:25:13 +0200 Subject: fix error handling for failing fetch of eip-service.json in production flavor and minor refactorings --- .../main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'app/src/main/java/se') diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java b/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java index 37adbe93..26f1691c 100644 --- a/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java +++ b/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java @@ -750,6 +750,13 @@ public abstract class ProviderApiManagerBase { return result; } + protected Bundle setErrorResult(Bundle result, String stringJsonErrorMessage) { + String reasonToFail = pickErrorMessage(stringJsonErrorMessage); + result.putString(ERRORS, reasonToFail); + result.putBoolean(BROADCAST_RESULT_KEY, false); + return result; + } + Bundle setErrorResult(Bundle result, int errorMessageId, String errorId) { JSONObject errorJson = new JSONObject(); String errorMessage = getProviderFormattedString(resources, errorMessageId); -- cgit v1.2.3 From bc48c48f1fefd1cb95caa7dcf0643ef3cff1c399 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Mon, 30 Sep 2019 17:02:22 +0200 Subject: handle SSLPeerUnverifiedException as certificate error --- app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'app/src/main/java/se') diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java b/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java index 26f1691c..46782802 100644 --- a/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java +++ b/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java @@ -49,6 +49,7 @@ import java.util.List; import java.util.NoSuchElementException; import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLPeerUnverifiedException; import okhttp3.OkHttpClient; import se.leap.bitmaskclient.Constants.CREDENTIAL_ERRORS; @@ -578,7 +579,7 @@ public abstract class ProviderApiManagerBase { plainResponseBody = formatErrorMessage(server_unreachable_message); } catch (MalformedURLException e) { plainResponseBody = formatErrorMessage(malformed_url); - } catch (SSLHandshakeException e) { + } catch (SSLHandshakeException | SSLPeerUnverifiedException e) { plainResponseBody = formatErrorMessage(certificate_error); } catch (ConnectException e) { plainResponseBody = formatErrorMessage(service_is_down_error); -- cgit v1.2.3 From 685da193ea29f3e7a8a42d55747dfb2f956f23b6 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Mon, 30 Sep 2019 23:10:01 +0200 Subject: remove hard coded demo setup, use real pt.demo.bitmask.net demo provider --- .../java/se/leap/bitmaskclient/StartActivity.java | 82 ---------------------- 1 file changed, 82 deletions(-) (limited to 'app/src/main/java/se') diff --git a/app/src/main/java/se/leap/bitmaskclient/StartActivity.java b/app/src/main/java/se/leap/bitmaskclient/StartActivity.java index 945429fd..b89363b2 100644 --- a/app/src/main/java/se/leap/bitmaskclient/StartActivity.java +++ b/app/src/main/java/se/leap/bitmaskclient/StartActivity.java @@ -25,36 +25,20 @@ import android.support.annotation.IntDef; import android.support.annotation.Nullable; import android.util.Log; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import de.blinkt.openvpn.core.Preferences; import de.blinkt.openvpn.core.VpnStatus; import se.leap.bitmaskclient.eip.EipCommand; -import se.leap.bitmaskclient.utils.PreferenceHelper; -import static se.leap.bitmaskclient.BuildConfig.useDemoConfig; import static se.leap.bitmaskclient.Constants.APP_ACTION_CONFIGURE_ALWAYS_ON_PROFILE; import static se.leap.bitmaskclient.Constants.EIP_RESTART_ON_BOOT; import static se.leap.bitmaskclient.Constants.PREFERENCES_APP_VERSION; -import static se.leap.bitmaskclient.Constants.PROVIDER_CONFIGURED; import static se.leap.bitmaskclient.Constants.PROVIDER_EIP_DEFINITION; import static se.leap.bitmaskclient.Constants.PROVIDER_KEY; -import static se.leap.bitmaskclient.Constants.PROVIDER_PRIVATE_KEY; -import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE; import static se.leap.bitmaskclient.Constants.REQUEST_CODE_CONFIGURE_LEAP; import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; import static se.leap.bitmaskclient.MainActivity.ACTION_SHOW_VPN_FRAGMENT; -import static se.leap.bitmaskclient.Provider.CA_CERT; -import static se.leap.bitmaskclient.Provider.MAIN_URL; import static se.leap.bitmaskclient.utils.ConfigHelper.isDefaultBitmask; import static se.leap.bitmaskclient.utils.PreferenceHelper.getSavedProviderFromSharedPreferences; import static se.leap.bitmaskclient.utils.PreferenceHelper.providerInSharedPreferences; @@ -106,10 +90,6 @@ public class StartActivity extends Activity{ // initialize app necessities VpnStatus.initLogCache(getApplicationContext().getCacheDir()); - if (useDemoConfig) { - demoSetup(); - } - prepareEIP(); } @@ -235,66 +215,4 @@ public class StartActivity extends Activity{ startActivity(intent); finish(); } - - private String getInputAsString(InputStream fileAsInputStream) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(fileAsInputStream)); - StringBuilder sb = new StringBuilder(); - String line = br.readLine(); - while (line != null) { - sb.append(line); - line = br.readLine(); - } - - return sb.toString(); - } - - private void demoSetup() { - try { - //set demo data - String demoEipServiceJson = getInputAsString(getAssets().open("ptdemo.bitmask.eip-service.json")); - String secrets = getInputAsString(getAssets().open("ptdemo.bitmask.secrets.json")); - String provider = getInputAsString(getAssets().open("ptdemo.bitmask.net.json")); - - Log.d(TAG, "setup provider: " + provider); - Log.d(TAG, "setup eip json: " + demoEipServiceJson); - JSONObject secretsJson = new JSONObject(secrets); - - preferences.edit().putString(PROVIDER_EIP_DEFINITION+".demo.bitmask.net", demoEipServiceJson). - putString(PROVIDER_EIP_DEFINITION, demoEipServiceJson). - putString(CA_CERT, secretsJson.getString(CA_CERT)). - putString(PROVIDER_PRIVATE_KEY, secretsJson.getString(PROVIDER_PRIVATE_KEY)). - putString(PROVIDER_VPN_CERTIFICATE, secretsJson.getString(PROVIDER_VPN_CERTIFICATE)). - putString(Provider.KEY, provider). - putString(MAIN_URL, "https://demo.bitmask.net"). - putBoolean(PROVIDER_CONFIGURED, true).commit(); - - PreferenceHelper.getSavedProviderFromSharedPreferences(preferences); - ProviderObservable.getInstance().updateProvider(PreferenceHelper.getSavedProviderFromSharedPreferences(preferences)); - - // remove last used profiles - SharedPreferences prefs = Preferences.getDefaultSharedPreferences(this); - SharedPreferences.Editor prefsedit = prefs.edit(); - prefsedit.remove("lastConnectedProfile").commit(); - File f = new File(this.getCacheDir().getAbsolutePath() + "/android.conf"); - if (f.exists()) { - Log.d(TAG, "android.conf exists -> delete:" + f.delete()); - } - - File filesDirectory = new File(this.getFilesDir().getAbsolutePath()); - if (filesDirectory.exists() && filesDirectory.isDirectory()) { - File[] filesInDirectory = filesDirectory.listFiles(); - for (File file : filesInDirectory) { - Log.d(TAG, "delete profile: " + file.getName() + ": "+ file.delete()); - - } - } else Log.d(TAG, "file folder doesn't exist"); - - } catch (IOException e) { - e.printStackTrace(); - } catch (JSONException e) { - e.printStackTrace(); - } - - } - } -- cgit v1.2.3