diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/ObfuscationProxyDialog.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/ObfuscationProxyDialog.java index 9e4f5d51d10e15e55dce2db7785d4d612f8784ac..f441e340c32834ae83eb122dec25b3c62afa3cbf 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/ObfuscationProxyDialog.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/ObfuscationProxyDialog.java @@ -23,6 +23,7 @@ import org.json.JSONObject; import java.util.ArrayList; +import de.blinkt.openvpn.core.connection.Connection; import se.leap.bitmaskclient.base.models.Transport; import se.leap.bitmaskclient.base.utils.ConfigHelper; import se.leap.bitmaskclient.base.utils.ConfigHelper.ObfsVpnHelper; @@ -77,23 +78,36 @@ public class ObfuscationProxyDialog extends AppCompatDialogFragment { Transport transport = Transport.fromJson(jsonObject); if (transport.getOptions() == null) { errors.add("Missing Bridge Options"); - } - if (transport.getOptions() != null && - (transport.getOptions().getEndpoints() == null || transport.getOptions().getEndpoints().length == 0)) { - errors.add("Cert and IP is missing"); - } - if (transport.getOptions() != null && transport.getOptions().getEndpoints() != null) { - for (Transport.Endpoint endpoint : transport.getOptions().getEndpoints()) { - if (endpoint.getIp() == null || endpoint.getIp().isEmpty()) { - errors.add("IP is missing"); - } else if (!ConfigHelper.isIPv4(endpoint.getIp())) { - errors.add("Invalid IPv4 address"); + } else { + if (transport.getOptions().getEndpoints() == null || + transport.getOptions().getEndpoints().length == 0) { + errors.add("Cert and IP is missing"); + } + int iatMode; + try { + if (transport.getOptions().getIatMode() == null || + transport.getOptions().getIatMode().isEmpty()) { + errors.add("iat mode is missing"); + } else if ((iatMode = Integer.parseInt(transport.getOptions().getIatMode())) < 0 || (iatMode > 1)) { + errors.add("invalid iat mode (0 or 1)"); } - if (endpoint.getCert() == null || endpoint.getCert().isEmpty()) { - errors.add("Cert is missing"); + } catch (NumberFormatException nfe) { + errors.add("invalid iat mode (0 or 1)"); + } + if (transport.getOptions().getEndpoints() != null) { + for (Transport.Endpoint endpoint : transport.getOptions().getEndpoints()) { + if (endpoint.getIp() == null || endpoint.getIp().isEmpty()) { + errors.add("IP is missing"); + } else if (!ConfigHelper.isIPv4(endpoint.getIp())) { + errors.add("Invalid IPv4 address"); + } + if (endpoint.getCert() == null || endpoint.getCert().isEmpty()) { + errors.add("Cert is missing"); + } } } } + if (transport.getProtocols() == null || transport.getProtocols().length == 0) { errors.add("missing protocols"); } @@ -105,10 +119,31 @@ public class ObfuscationProxyDialog extends AppCompatDialogFragment { } catch (NullPointerException | IllegalArgumentException e) {} if (!hasValidTransportType) { errors.add("invalid bridge transport type"); - } else if (!hasPTAllowedProtocol(transport)) { - errors.add("invalid protocol for transport " + transport.getType()); + } else { + if (!hasPTAllowedProtocol(transport)) { + errors.add("invalid protocol for transport " + transport.getType()); + } + if (transport.getTransportType() != Connection.TransportType.OBFS4_HOP && + (transport.getPorts() == null || transport.getPorts().length == 0)) { + errors.add("ports are missing"); + } else { + for (String port: transport.getPorts()) { + try { + int portNumber = Integer.parseInt(port); + if (portNumber < 1 || portNumber > 65535) { + errors.add("invalid port number (1-65535)"); + } + } catch (NumberFormatException e) { + if (port.isEmpty()) { + errors.add("bridge port value cannot be empty"); + } else { + errors.add(port + " is an invalid value for bridge ports"); + } + } + } + } } - } catch (IllegalStateException | JSONException e) { + } catch (Exception e) { errors.add("invalid json format"); } StringBuilder stringBuilder = new StringBuilder(); @@ -117,6 +152,7 @@ public class ObfuscationProxyDialog extends AppCompatDialogFragment { int diff = 0; if (i == 2 && (diff = errors.size() - 3) > 0) { stringBuilder.append(diff + " more..."); + break; } } validityCheck.setText(stringBuilder.toString()); @@ -124,20 +160,18 @@ public class ObfuscationProxyDialog extends AppCompatDialogFragment { } }); - - try { - Transport transport = Transport.fromJson(new JSONObject(PreferenceHelper.getObfuscationPinningTransport(getContext()))); + Transport transport = PreferenceHelper.getObfuscationPinningTransport(getContext(), false); + if (transport != null) { bridgeConfig.setText(transport.toPrettyPrint()); - } catch (Exception e) { - // eat me } + saveButton.setOnClickListener(v -> { JSONObject jsonObject = null; try { jsonObject = new JSONObject(bridgeConfig.getText().toString()); - Transport transport = Transport.fromJson(jsonObject); - PreferenceHelper.setObfuscationPinningGatewayLocation(v.getContext(), gatewaysManager.getLocationNameForIP(transport.getOptions().getEndpoints()[0].getIp(), v.getContext())); - PreferenceHelper.setObufscationPinningTransport(v.getContext(), transport); + Transport t = Transport.fromJson(jsonObject); + PreferenceHelper.setObfuscationPinningGatewayLocation(v.getContext(), gatewaysManager.getLocationNameForIP(t.getIPFromEndpoints(), v.getContext())); + PreferenceHelper.setObfuscationPinningTransport(v.getContext(), t); } catch (JSONException | NullPointerException | ArrayIndexOutOfBoundsException e) {} dismiss(); diff --git a/app/src/main/java/se/leap/bitmaskclient/base/models/Transport.java b/app/src/main/java/se/leap/bitmaskclient/base/models/Transport.java index ddd75ddadfc48897c4de02769c3fab13c6a01135..1256277a32668310ce9a9c9200813bd209176691 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/models/Transport.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/models/Transport.java @@ -64,6 +64,20 @@ public class Transport implements Serializable { return gson.toJson(this); } + public @Nullable String getIPFromEndpoints() { + if (options == null || options.endpoints == null || options.endpoints.length == 0) { + return null; + } + return options.endpoints[0].ip; + } + + public @Nullable String getCertFromEndpoints() { + if (options == null || options.endpoints == null || options.endpoints.length == 0) { + return null; + } + return options.endpoints[0].cert; + } + public static Transport fromJson(JSONObject json) { GsonBuilder builder = new GsonBuilder(); return builder. diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java index f08a98e0a7446288d12754ba5cbbc6dd3c30694f..0b278ed418b169780e9bace0fc197bfea75a2fd1 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java @@ -37,7 +37,6 @@ import static se.leap.bitmaskclient.base.models.Constants.USE_SNOWFLAKE; import android.content.Context; import android.content.SharedPreferences; -import android.text.TextUtils; import androidx.annotation.NonNull; import androidx.annotation.WorkerThread; @@ -51,6 +50,7 @@ import java.util.HashSet; import java.util.Set; import de.blinkt.openvpn.VpnProfile; +import de.blinkt.openvpn.core.connection.Connection; import se.leap.bitmaskclient.BuildConfig; import se.leap.bitmaskclient.base.models.Provider; import se.leap.bitmaskclient.base.models.Transport; @@ -332,12 +332,29 @@ public class PreferenceHelper { getObfuscationPinningTransport(context) != null; } - public static void setObufscationPinningTransport(Context context, Transport transport) { + public static void setObfuscationPinningTransport(Context context, Transport transport) { putString(context, OBFUSCATION_PINNING_TRANSPORT, transport.toString()); } - public static String getObfuscationPinningTransport(Context context) { - return getString(context, OBFUSCATION_PINNING_TRANSPORT, null); + public static Transport getObfuscationPinningTransport(Context context) { + return getObfuscationPinningTransport(context, true); + } + + public static Transport getObfuscationPinningTransport(Context context, boolean compatFix) { + try { + String transportString = getString(context, OBFUSCATION_PINNING_TRANSPORT, null); + if (transportString != null) { + Transport transport = Transport.fromJson(new JSONObject(getString(context, OBFUSCATION_PINNING_TRANSPORT, null))); + // compatibility hack... + if (compatFix && transport.getTransportType() == Connection.TransportType.OBFS4) { + transport.getOptions().setCert(transport.getCertFromEndpoints()); + } + return transport; + } + } catch (JSONException | NullPointerException e) { + e.printStackTrace(); + } + return null; } public static void setObfuscationPinningIP(Context context, String ip) { 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 0ee1695cc51eef954fbe86fec2436de099e60362..92323d13387afd228a5f47915ca06da3d830eeac 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java @@ -16,7 +16,6 @@ */ package se.leap.bitmaskclient.eip; -import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4; import static de.blinkt.openvpn.core.connection.Connection.TransportType.PT; import static se.leap.bitmaskclient.base.models.Constants.FULLNESS; import static se.leap.bitmaskclient.base.models.Constants.HOST; @@ -30,11 +29,7 @@ import static se.leap.bitmaskclient.base.models.Constants.TIMEZONE; import static se.leap.bitmaskclient.base.models.Constants.VERSION; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.allowExperimentalTransports; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getExcludedApps; -import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getObfuscationPinningCert; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getObfuscationPinningGatewayLocation; -import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getObfuscationPinningIP; -import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getObfuscationPinningKCP; -import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getObfuscationPinningPort; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getObfuscationPinningTransport; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getPreferUDP; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.useObfuscationPinning; @@ -116,10 +111,10 @@ public class Gateway { config.experimentalTransports = allowExperimentalTransports(context); config.excludedApps = getExcludedApps(context); config.useObfuscationPinning = useObfuscationPinning(context); - if (config.useObfuscationPinning) { + if (config.useObfuscationPinning) { try { - Transport transport = Transport.fromJson(new JSONObject(getObfuscationPinningTransport(context))); - config.remoteGatewayIP = transport.getOptions().getEndpoints()[0].getIp(); + Transport transport = getObfuscationPinningTransport(context); + config.remoteGatewayIP = transport.getIPFromEndpoints(); config.obfuscationProxyTransport = transport; config.profileName = getObfuscationPinningGatewayLocation(context); } catch (Exception e) { 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 d114665b87006da79adf6ad619c0bdc6cdc548be..a867805931a15d812ed901c3e5d16b224c25f4b7 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java @@ -25,10 +25,7 @@ import static se.leap.bitmaskclient.base.models.Constants.HOST; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PRIVATE_KEY; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_VPN_CERTIFICATE; import static se.leap.bitmaskclient.base.models.Constants.SORTED_GATEWAYS; -import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getObfuscationPinningCert; -import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getObfuscationPinningIP; -import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getObfuscationPinningKCP; -import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getObfuscationPinningPort; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getObfuscationPinningTransport; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getPreferredCity; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getUseBridges; @@ -402,13 +399,10 @@ public class GatewaysManager { if (PreferenceHelper.useObfuscationPinning(context)) { try { - Transport[] transports = new Transport[]{ - new Transport(OBFS4.toString(), - new String[]{getObfuscationPinningKCP(context) ? "kcp" : "tcp"}, - new String[]{getObfuscationPinningPort(context)}, - getObfuscationPinningCert(context))}; + Transport transport = getObfuscationPinningTransport(context); + Transport[] transports = new Transport[]{transport}; GatewayJson.Capabilities capabilities = new GatewayJson.Capabilities(false, false, false, transports, false); - GatewayJson gatewayJson = new GatewayJson(context.getString(R.string.unknown_location), getObfuscationPinningIP(context), null, PINNED_OBFUSCATION_PROXY, capabilities); + GatewayJson gatewayJson = new GatewayJson(context.getString(R.string.unknown_location), transport.getIPFromEndpoints(), null, PINNED_OBFUSCATION_PROXY, capabilities); Gateway gateway = new Gateway(eipDefinition, secrets, new JSONObject(gatewayJson.toString()), this.context); addGateway(gateway); } catch (JSONException | ConfigParser.ConfigParseError | IOException 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 a5d9224ff0a22272c4ec3bac25dd8235a139e989..710980a80bd4c8bd4f302c995ed3cf5869d273db 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java @@ -22,7 +22,6 @@ import static de.blinkt.openvpn.core.connection.Connection.TransportType.OPENVPN import static se.leap.bitmaskclient.base.models.Constants.CAPABILITIES; import static se.leap.bitmaskclient.base.models.Constants.IP_ADDRESS; import static se.leap.bitmaskclient.base.models.Constants.IP_ADDRESS6; -import static se.leap.bitmaskclient.base.models.Constants.KCP; import static se.leap.bitmaskclient.base.models.Constants.PORTS; import static se.leap.bitmaskclient.base.models.Constants.PROTOCOLS; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PRIVATE_KEY; @@ -69,12 +68,6 @@ public class VpnConfigGenerator { private final boolean preferUDP; private final boolean experimentalTransports; private final boolean useObfuscationPinning; - - private final Transport obfuscationPinningTransport; - // private final String obfuscationPinningIP; - // private final String obfuscationPinningPort; - // private final String obfuscationPinningCert; - // private final boolean obfuscationPinningKCP; private final String remoteGatewayIP; private final String profileName; private final Set<String> excludedApps; @@ -92,11 +85,6 @@ public class VpnConfigGenerator { Set<String> excludedApps = null; boolean useObfuscationPinning; - // boolean obfuscationProxyKCP; - // String obfuscationProxyIP = ""; - // String obfuscationProxyPort = ""; - // String obfuscationProxyCert = ""; - Transport obfuscationProxyTransport; } @@ -108,11 +96,14 @@ public class VpnConfigGenerator { this.preferUDP = config.preferUDP; this.experimentalTransports = config.experimentalTransports; this.useObfuscationPinning = config.useObfuscationPinning; - this.obfuscationPinningTransport = useObfuscationPinning ? config.obfuscationProxyTransport : null; this.remoteGatewayIP = config.remoteGatewayIP; this.profileName = config.profileName; this.excludedApps = config.excludedApps; - checkCapabilities(); + if (useObfuscationPinning) { + transports.put(config.obfuscationProxyTransport.getTransportType(), config.obfuscationProxyTransport); + } else { + checkCapabilities(); + } } public void checkCapabilities() throws ConfigParser.ConfigParseError { @@ -197,18 +188,8 @@ public class VpnConfigGenerator { } private Obfs4Options getObfs4Options(TransportType transportType) throws JSONException { - String ip = gateway.getString(IP_ADDRESS); - Transport transport; - if (useObfuscationPinning) { - transport = obfuscationPinningTransport; - ip = obfuscationPinningTransport.getOptions().getEndpoints()[0].getIp(); - // hack just for compatibility reasons for now - if (obfuscationPinningTransport.getTransportType() == OBFS4) { - obfuscationPinningTransport.getOptions().setCert(obfuscationPinningTransport.getOptions().getEndpoints()[0].getCert()); - } - } else { - transport = transports.get(transportType); - } + Transport transport = transports.get(transportType); + String ip = useObfuscationPinning ? transport.getIPFromEndpoints() : gateway.getString(IP_ADDRESS); return new Obfs4Options(ip, transport); } @@ -390,9 +371,6 @@ public class VpnConfigGenerator { public String getRemoteString(String ipAddress, Transport transport) { if (useObfsVpn()) { - if (useObfuscationPinning) { - return REMOTE + " " + obfuscationPinningTransport.getOptions().getEndpoints()[0].getIp() + " " + obfuscationPinningTransport.getProtocols()[0] + " tcp" + newLine; - } switch (transport.getTransportType()) { case OBFS4: return REMOTE + " " + ipAddress + " " + transport.getPorts()[0] + " tcp" + newLine; @@ -416,9 +394,6 @@ public class VpnConfigGenerator { } public String getRouteString(String ipAddress, Transport transport) { - if (useObfuscationPinning) { - return "route " + remoteGatewayIP + " 255.255.255.255 net_gateway" + newLine; - } switch (transport.getTransportType()) { case OBFS4: return "route " + ipAddress + " 255.255.255.255 net_gateway" + newLine;