diff options
10 files changed, 427 insertions, 11 deletions
diff --git a/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java b/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java index 5b4ab361..4a8bcf99 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java +++ b/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java @@ -714,6 +714,10 @@ public class ConfigParser { Vector<Vector<String>> connectionBlocks = getAllOption("connection", 1, 1); + if (connectionBlocks == null && np.mConnections.length == 0) { + throw new ConfigParseError("No --remote or <connection> block found."); + } + if (np.mConnections.length > 0 && connectionBlocks != null) { throw new ConfigParseError("Using a <connection> block and --remote is not allowed."); } diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java index 813daac5..18e97411 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java @@ -27,6 +27,7 @@ import android.graphics.ColorMatrixColorFilter; import android.os.Bundle; import android.os.IBinder; import android.os.Vibrator; +import android.text.TextUtils; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; @@ -37,6 +38,7 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.widget.AppCompatButton; import androidx.appcompat.widget.AppCompatImageView; import androidx.appcompat.widget.AppCompatTextView; import androidx.fragment.app.DialogFragment; @@ -267,13 +269,8 @@ public class EipFragment extends Fragment implements Observer { preferences.edit().putBoolean(EIP_RESTART_ON_BOOT, restartOnBoot).apply(); } - @OnClick(R.id.gateway_location_button) - void onButtonClick() { - handleIcon(); - } - @OnClick(R.id.main_button) - void onVpnStateImageClick() { + void onButtonClick() { handleIcon(); } diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/ConfigHelper.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/ConfigHelper.java index 4248072a..64b51960 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/utils/ConfigHelper.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/ConfigHelper.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.res.Resources; import android.os.Build; import android.os.Looper; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; @@ -44,10 +45,12 @@ import java.security.interfaces.RSAPrivateKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.util.Calendar; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import se.leap.bitmaskclient.BuildConfig; -import se.leap.bitmaskclient.providersetup.ProviderAPI; import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.providersetup.ProviderAPI; import static se.leap.bitmaskclient.base.models.Constants.DEFAULT_BITMASK; @@ -62,6 +65,7 @@ public class ConfigHelper { final public static String NG_1024 = "eeaf0ab9adb38dd69c33f80afa8fc5e86072618775ff3c0b9ea2314c9c256576d674df7496ea81d3383b4813d692c6e0e0d5d8e250b98be48e495c1d6089dad15dc7d7b46154d6b6ce8ef4ad69b15d4982559b297bcf1885c529f566660e57ec68edbc3c05726cc02fd4cbf4976eaa9afd5138fe8376435b9fc61d2fc0eb06e3"; final public static BigInteger G = new BigInteger("2"); + final public static Pattern IPv4_PATTERN = Pattern.compile("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$"); public static boolean checkErroneousDownload(String downloadedString) { try { @@ -203,6 +207,8 @@ public class ConfigHelper { (string1 != null && string1.equals(string2)); } + @SuppressWarnings("unused") + // FatWeb Flavor uses that for auto-update public static String getApkFileName() { try { return BuildConfig.update_apk_url.substring(BuildConfig.update_apk_url.lastIndexOf("/")); @@ -211,6 +217,8 @@ public class ConfigHelper { } } + @SuppressWarnings("unused") + // FatWeb Flavor uses that for auto-update public static String getVersionFileName() { try { return BuildConfig.version_file_url.substring(BuildConfig.version_file_url.lastIndexOf("/")); @@ -219,6 +227,8 @@ public class ConfigHelper { } } + @SuppressWarnings("unused") + // FatWeb Flavor uses that for auto-update public static String getSignatureFileName() { try { return BuildConfig.signature_url.substring(BuildConfig.signature_url.lastIndexOf("/")); @@ -227,4 +237,12 @@ public class ConfigHelper { } } + public static boolean isIPv4(String ipv4) { + if (ipv4 == null) { + return false; + } + Matcher matcher = IPv4_PATTERN.matcher(ipv4); + return matcher.matches(); + } + } 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 6fffb403..d72f0936 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java @@ -29,8 +29,10 @@ import java.util.Iterator; import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.core.ConfigParser; +import de.blinkt.openvpn.core.VpnStatus; import de.blinkt.openvpn.core.connection.Connection; import se.leap.bitmaskclient.base.models.Provider; +import se.leap.bitmaskclient.base.utils.ConfigHelper; import se.leap.bitmaskclient.pluggableTransports.Obfs4Options; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4; @@ -95,7 +97,11 @@ public class VpnConfigGenerator { HashMap<Connection.TransportType, VpnProfile> profiles = new HashMap<>(); profiles.put(OPENVPN, createProfile(OPENVPN)); if (supportsObfs4()) { - profiles.put(OBFS4, createProfile(OBFS4)); + try { + profiles.put(OBFS4, createProfile(OBFS4)); + } catch (ConfigParser.ConfigParseError | NumberFormatException | JSONException | IOException e) { + e.printStackTrace(); + } } return profiles; } @@ -162,16 +168,18 @@ public class VpnConfigGenerator { StringBuilder stringBuilder = new StringBuilder(); try { - String ipAddress = gateway.getString(IP_ADDRESS); + String ipAddress = null; JSONObject capabilities = gateway.getJSONObject(CAPABILITIES); switch (apiVersion) { default: case 1: case 2: + ipAddress = gateway.getString(IP_ADDRESS); gatewayConfigApiv1(stringBuilder, ipAddress, capabilities); break; case 3: case 4: + ipAddress = gateway.optString(IP_ADDRESS); String ipAddress6 = gateway.optString(IP_ADDRESS6); String[] ipAddresses = ipAddress6.isEmpty() ? new String[]{ipAddress} : @@ -189,6 +197,7 @@ public class VpnConfigGenerator { if (remotes.endsWith(newLine)) { remotes = remotes.substring(0, remotes.lastIndexOf(newLine)); } + return remotes; } @@ -247,6 +256,7 @@ public class VpnConfigGenerator { private void obfs4GatewayConfigMinApiv3(StringBuilder stringBuilder, String[] ipAddresses, JSONArray transports) throws JSONException { JSONObject obfs4Transport = getTransport(transports, OBFS4); + JSONArray protocols = obfs4Transport.getJSONArray(PROTOCOLS); //for now only use ipv4 gateway the syntax route remote_host 255.255.255.255 net_gateway is not yet working // https://community.openvpn.net/openvpn/ticket/1161 /*for (String ipAddress : ipAddresses) { @@ -258,10 +268,38 @@ public class VpnConfigGenerator { return; } - String ipAddress = ipAddresses[ipAddresses.length - 1]; + // check if at least one address is IPv4, IPv6 is currently not supported for obfs4 + String ipAddress = null; + for (String address : ipAddresses) { + if (ConfigHelper.isIPv4(address)) { + ipAddress = address; + break; + } + VpnStatus.logWarning("Skipping IP address " + address + " while configuring obfs4."); + } + + if (ipAddress == null) { + VpnStatus.logError("No matching IPv4 address found to configure obfs4."); + return; + } + + // check if at least one protocol is TCP, UDP is currently not supported for obfs4 + boolean hasTcp = false; + for (int i = 0; i < protocols.length(); i++) { + String protocol = protocols.getString(i); + if (protocol.contains("tcp")) { + hasTcp = true; + } + } + + if (!hasTcp) { + VpnStatus.logError("obfs4 currently only allows TCP! Skipping obfs4 config for ip " + ipAddress); + return; + } + 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; + String remote = REMOTE + " " + DISPATCHER_IP + " " + DISPATCHER_PORT + " tcp" + newLine; stringBuilder.append(remote); } diff --git a/app/src/test/java/se/leap/bitmaskclient/base/utils/ConfigHelperTest.java b/app/src/test/java/se/leap/bitmaskclient/base/utils/ConfigHelperTest.java new file mode 100644 index 00000000..75552226 --- /dev/null +++ b/app/src/test/java/se/leap/bitmaskclient/base/utils/ConfigHelperTest.java @@ -0,0 +1,48 @@ +package se.leap.bitmaskclient.base.utils; + +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.modules.junit4.PowerMockRunnerDelegate; + +import static org.junit.Assert.assertEquals; + +@RunWith(PowerMockRunner.class) +@PowerMockRunnerDelegate(DataProviderRunner.class) +public class ConfigHelperTest { + + @DataProvider + public static Object[][] dataProviderIPs() { + // @formatter:off + return new Object[][] { + { "0.0.0.0", true }, + { "1.1.1.1", true }, + { "8.8.8.8", true }, + { "127.0.0.1", true }, + { "255.255.255.255", true }, + { "200.50.20.10", true }, + { "97.72.15.12", true }, + {"02.0.0.0", false}, + {"10.000.1.1", false}, + {"256.256.256.256", false}, + {"127..0.1", false}, + {"127.0..1", false}, + {"127.0.0.", false}, + {"127.0.0", false}, + {"255.255.255.255.255", false}, + {"", false}, + {null, false}, + }; + } + + + @Test + @UseDataProvider("dataProviderIPs") + public void testisIPv4_validIPs_returnsTrue(String ip, boolean isValidExpected) { + assertEquals(isValidExpected, ConfigHelper.isIPv4(ip)); + } +}
\ No newline at end of file diff --git a/app/src/test/java/se/leap/bitmaskclient/eip/VpnConfigGeneratorTest.java b/app/src/test/java/se/leap/bitmaskclient/eip/VpnConfigGeneratorTest.java index ad6f5d7b..56575556 100644 --- a/app/src/test/java/se/leap/bitmaskclient/eip/VpnConfigGeneratorTest.java +++ b/app/src/test/java/se/leap/bitmaskclient/eip/VpnConfigGeneratorTest.java @@ -1140,5 +1140,56 @@ public class VpnConfigGeneratorTest { assertTrue(vpnProfiles.get(OPENVPN).getConfigFile(context, false).trim().equals(expectedVPNConfig_v4_ovpn_udp_tcp.trim())); } + @Test + public void testGenerateVpnProfile_v3_ipv6only_allowOpenvpnIPv6Only() throws Exception { + gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_misconfigured_ipv6.json"))).getJSONArray("gateways").getJSONObject(0); + generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_misconfigured_ipv6.json"))).getJSONObject(OPENVPN_CONFIGURATION); + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, 3); + HashMap<Connection.TransportType, VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); + assertTrue(vpnProfiles.containsKey(OPENVPN)); + } + + @Test + public void testGenerateVpnProfile_v3_obfs4IPv6_skip() throws Exception { + gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_misconfigured_ipv6.json"))).getJSONArray("gateways").getJSONObject(0); + generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_misconfigured_ipv6.json"))).getJSONObject(OPENVPN_CONFIGURATION); + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, 3); + HashMap<Connection.TransportType, VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); + assertFalse(vpnProfiles.containsKey(OBFS4)); + } + + @Test + public void testGenerateVpnProfile_v3_obfs4IPv4AndIPv6_skipIPv6() throws Exception { + gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_misconfigured_ipv4ipv6.json"))).getJSONArray("gateways").getJSONObject(0); + generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_misconfigured_ipv4ipv6.json"))).getJSONObject(OPENVPN_CONFIGURATION); + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, 3); + HashMap<Connection.TransportType, VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); + assertTrue(vpnProfiles.containsKey(OBFS4)); + assertTrue(vpnProfiles.containsKey(OPENVPN)); + assertEquals(1, vpnProfiles.get(OBFS4).mConnections.length); + assertEquals("37.218.247.60/32", vpnProfiles.get(OBFS4).mExcludedRoutes.trim()); + } + + @Test + public void testGenerateVpnProfile_v3_obfs4udp_skip() throws Exception { + gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_misconfigured_udp.json"))).getJSONArray("gateways").getJSONObject(0); + generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_misconfigured_udp.json"))).getJSONObject(OPENVPN_CONFIGURATION); + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, 3); + HashMap<Connection.TransportType, VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); + assertFalse(vpnProfiles.containsKey(OBFS4)); + assertTrue(vpnProfiles.containsKey(OPENVPN)); + } + + @Test + public void testGenerateVpnProfile_v3_obfs4UDPAndTCP_skipUDP() throws Exception { + gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_misconfigured_udptcp.json"))).getJSONArray("gateways").getJSONObject(0); + generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_misconfigured_udptcp.json"))).getJSONObject(OPENVPN_CONFIGURATION); + vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, 3); + HashMap<Connection.TransportType, VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles(); + assertTrue(vpnProfiles.containsKey(OBFS4)); + assertTrue(vpnProfiles.containsKey(OPENVPN)); + assertEquals(1, vpnProfiles.get(OBFS4).mConnections.length); + assertFalse(vpnProfiles.get(OBFS4).mConnections[0].isUseUdp()); + } }
\ No newline at end of file diff --git a/app/src/test/resources/ptdemo_misconfigured_ipv4ipv6.json b/app/src/test/resources/ptdemo_misconfigured_ipv4ipv6.json new file mode 100644 index 00000000..5c913c14 --- /dev/null +++ b/app/src/test/resources/ptdemo_misconfigured_ipv4ipv6.json @@ -0,0 +1,65 @@ +{ + "gateways":[ + { + "capabilities":{ + "adblock":false, + "filter_dns":false, + "limited":false, + "transport":[ + { + "type":"obfs4", + "protocols":[ + "tcp" + ], + "ports":[ + "23049" + ], + "options": { + "cert": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "iatMode": "0" + } + }, + { + "type":"openvpn", + "protocols":[ + "tcp" + ], + "ports":[ + "1195" + ] + } + ], + "user_ips":false + }, + "host":"pt.demo.bitmask.net", + "ip_address6":"2001:db8:123::1058", + "ip_address":"37.218.247.60", + "location":"Amsterdam" + } + ], + "locations":{ + "Amsterdam":{ + "country_code":"NL", + "hemisphere":"N", + "name":"Amsterdam", + "timezone":"-1" + } + }, + "openvpn_configuration":{ + "auth":"SHA1", + "cipher":"AES-256-CBC", + "keepalive":"10 30", + "tls-cipher":"DHE-RSA-AES128-SHA", + "tun-ipv6":true, + "dev" : "tun", + "sndbuf" : "0", + "rcvbuf" : "0", + "nobind" : true, + "persist-key" : true, + "comp-lzo" : true, + "key-direction" : "1", + "verb" : "3" + }, + "serial":2, + "version":3 +}
\ No newline at end of file diff --git a/app/src/test/resources/ptdemo_misconfigured_ipv6.json b/app/src/test/resources/ptdemo_misconfigured_ipv6.json new file mode 100644 index 00000000..736dbb8f --- /dev/null +++ b/app/src/test/resources/ptdemo_misconfigured_ipv6.json @@ -0,0 +1,64 @@ +{ + "gateways":[ + { + "capabilities":{ + "adblock":false, + "filter_dns":false, + "limited":false, + "transport":[ + { + "type":"obfs4", + "protocols":[ + "tcp" + ], + "ports":[ + "23049" + ], + "options": { + "cert": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "iatMode": "0" + } + }, + { + "type":"openvpn", + "protocols":[ + "tcp" + ], + "ports":[ + "1195" + ] + } + ], + "user_ips":false + }, + "host":"pt.demo.bitmask.net", + "ip_address6":"2001:db8:123::1058", + "location":"Amsterdam" + } + ], + "locations":{ + "Amsterdam":{ + "country_code":"NL", + "hemisphere":"N", + "name":"Amsterdam", + "timezone":"-1" + } + }, + "openvpn_configuration":{ + "auth":"SHA1", + "cipher":"AES-256-CBC", + "keepalive":"10 30", + "tls-cipher":"DHE-RSA-AES128-SHA", + "tun-ipv6":true, + "dev" : "tun", + "sndbuf" : "0", + "rcvbuf" : "0", + "nobind" : true, + "persist-key" : true, + "comp-lzo" : true, + "key-direction" : "1", + "verb" : "3" + }, + "serial":2, + "version":3 +}
\ No newline at end of file diff --git a/app/src/test/resources/ptdemo_misconfigured_udp.json b/app/src/test/resources/ptdemo_misconfigured_udp.json new file mode 100644 index 00000000..aa9a0d33 --- /dev/null +++ b/app/src/test/resources/ptdemo_misconfigured_udp.json @@ -0,0 +1,65 @@ +{ + "gateways":[ + { + "capabilities":{ + "adblock":false, + "filter_dns":false, + "limited":false, + "transport":[ + { + "type":"obfs4", + "protocols":[ + "udp" + ], + "ports":[ + "23049" + ], + "options": { + "cert": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "iatMode": "0" + } + }, + { + "type":"openvpn", + "protocols":[ + "tcp" + ], + "ports":[ + "1195" + ] + } + ], + "user_ips":false + }, + "host":"pt.demo.bitmask.net", + "ip_address6":"2001:db8:123::1058", + "ip_address":"37.218.247.60", + "location":"Amsterdam" + } + ], + "locations":{ + "Amsterdam":{ + "country_code":"NL", + "hemisphere":"N", + "name":"Amsterdam", + "timezone":"-1" + } + }, + "openvpn_configuration":{ + "auth":"SHA1", + "cipher":"AES-256-CBC", + "keepalive":"10 30", + "tls-cipher":"DHE-RSA-AES128-SHA", + "tun-ipv6":true, + "dev" : "tun", + "sndbuf" : "0", + "rcvbuf" : "0", + "nobind" : true, + "persist-key" : true, + "comp-lzo" : true, + "key-direction" : "1", + "verb" : "3" + }, + "serial":2, + "version":3 +}
\ No newline at end of file diff --git a/app/src/test/resources/ptdemo_misconfigured_udptcp.json b/app/src/test/resources/ptdemo_misconfigured_udptcp.json new file mode 100644 index 00000000..42d55de9 --- /dev/null +++ b/app/src/test/resources/ptdemo_misconfigured_udptcp.json @@ -0,0 +1,66 @@ +{ + "gateways":[ + { + "capabilities":{ + "adblock":false, + "filter_dns":false, + "limited":false, + "transport":[ + { + "type":"obfs4", + "protocols":[ + "udp", + "tcp" + ], + "ports":[ + "23049" + ], + "options": { + "cert": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "iatMode": "0" + } + }, + { + "type":"openvpn", + "protocols":[ + "tcp" + ], + "ports":[ + "1195" + ] + } + ], + "user_ips":false + }, + "host":"pt.demo.bitmask.net", + "ip_address6":"2001:db8:123::1058", + "ip_address":"37.218.247.60", + "location":"Amsterdam" + } + ], + "locations":{ + "Amsterdam":{ + "country_code":"NL", + "hemisphere":"N", + "name":"Amsterdam", + "timezone":"-1" + } + }, + "openvpn_configuration":{ + "auth":"SHA1", + "cipher":"AES-256-CBC", + "keepalive":"10 30", + "tls-cipher":"DHE-RSA-AES128-SHA", + "tun-ipv6":true, + "dev" : "tun", + "sndbuf" : "0", + "rcvbuf" : "0", + "nobind" : true, + "persist-key" : true, + "comp-lzo" : true, + "key-direction" : "1", + "verb" : "3" + }, + "serial":2, + "version":3 +}
\ No newline at end of file |