diff options
author | cyBerta <cyberta@riseup.net> | 2024-11-14 11:22:37 +0100 |
---|---|---|
committer | cyberta <cyberta@riseup.net> | 2024-12-11 00:09:34 +0000 |
commit | 8d00d684997fe3fcd9f479394adea448242f74aa (patch) | |
tree | 455572967eed8d9ac2ecaf1630da615c2a33c921 /app/src/main | |
parent | 94667c775472c4060629d2d92b30c498ab466eb7 (diff) |
adapt gateway parsing and generation of VpnProfiles to apiv5
Diffstat (limited to 'app/src/main')
6 files changed, 433 insertions, 233 deletions
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java b/app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java index cdec9e7a..b826d338 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java @@ -545,11 +545,17 @@ public final class Provider implements Parcelable { } public boolean hasGatewaysInDifferentLocations() { - try { - return getEipServiceJson().getJSONObject(LOCATIONS).length() > 1; - } catch (NullPointerException | JSONException e) { - return false; + if (apiVersion >= 5) { + //FIXME: getService().getLocations() + return true; + } else { + try { + return getEipServiceJson().getJSONObject(LOCATIONS).length() > 1; + } catch (NullPointerException | JSONException e) { + return false; + } } + } @Override 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 c2590012..abd42812 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 @@ -1,5 +1,14 @@ package se.leap.bitmaskclient.base.models; +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.CERT; +import static se.leap.bitmaskclient.base.models.Constants.IAT_MODE; +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.TRANSPORT; + +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.google.gson.FieldNamingPolicy; @@ -7,11 +16,16 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.annotations.SerializedName; +import org.json.JSONArray; import org.json.JSONObject; import java.io.Serializable; +import java.util.Map; +import java.util.Vector; import de.blinkt.openvpn.core.connection.Connection; +import io.swagger.client.model.ModelsBridge; +import io.swagger.client.model.ModelsGateway; public class Transport implements Serializable { private final String type; @@ -32,6 +46,13 @@ public class Transport implements Serializable { this.options = options; } + public Transport(String type, String[] protocols, @Nullable String[] ports) { + this.type = type; + this.protocols = protocols; + this.ports = ports; + this.options = null; + } + public String getType() { return type; } @@ -67,6 +88,78 @@ public class Transport implements Serializable { fromJson(json.toString(), Transport.class); } + public static Transport createTransportFrom(ModelsBridge modelsBridge) { + if (modelsBridge == null) { + return null; + } + Map<String, Object> options = modelsBridge.getOptions(); + Transport.Options transportOptions = new Transport.Options((String) options.get(CERT), (String) options.get(IAT_MODE)); + Transport transport = new Transport( + modelsBridge.getType(), + new String[]{modelsBridge.getTransport()}, + new String[]{String.valueOf(modelsBridge.getPort())}, + transportOptions + ); + return transport; + } + + public static Transport createTransportFrom(ModelsGateway modelsGateway) { + if (modelsGateway == null) { + return null; + } + Transport transport = new Transport( + modelsGateway.getType(), + new String[]{modelsGateway.getTransport()}, + new String[]{String.valueOf(modelsGateway.getPort())} + ); + return transport; + } + + + @NonNull + public static Vector<Transport> createTransportsFrom(JSONObject gateway, int apiVersion) throws IllegalArgumentException { + Vector<Transport> transports = new Vector<>(); + try { + if (apiVersion >= 3) { + JSONArray supportedTransports = gateway.getJSONObject(CAPABILITIES).getJSONArray(TRANSPORT); + for (int i = 0; i < supportedTransports.length(); i++) { + Transport transport = Transport.fromJson(supportedTransports.getJSONObject(i)); + transports.add(transport); + } + } else { + JSONObject capabilities = gateway.getJSONObject(CAPABILITIES); + JSONArray ports = capabilities.getJSONArray(PORTS); + JSONArray protocols = capabilities.getJSONArray(PROTOCOLS); + String[] portArray = new String[ports.length()]; + String[] protocolArray = new String[protocols.length()]; + for (int i = 0; i < ports.length(); i++) { + portArray[i] = String.valueOf(ports.get(i)); + } + for (int i = 0; i < protocols.length(); i++) { + protocolArray[i] = protocols.optString(i); + } + Transport transport = new Transport(OPENVPN.toString(), protocolArray, portArray); + transports.add(transport); + } + } catch (Exception e) { + throw new IllegalArgumentException(); + //throw new ConfigParser.ConfigParseError("Api version ("+ apiVersion +") did not match required JSON fields"); + } + return transports; + } + + public static Vector<Transport> createTransportsFrom(ModelsBridge modelsBridge) { + Vector<Transport> transports = new Vector<>(); + transports.add(Transport.createTransportFrom(modelsBridge)); + return transports; + } + + public static Vector<Transport> createTransportsFrom(ModelsGateway modelsGateway) { + Vector<Transport> transports = new Vector<>(); + transports.add(Transport.createTransportFrom(modelsGateway)); + return transports; + } + public static class Options implements Serializable { @Nullable private final String cert; diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/BitmaskCoreProvider.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/BitmaskCoreProvider.java new file mode 100644 index 00000000..a9797142 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/BitmaskCoreProvider.java @@ -0,0 +1,2 @@ +package se.leap.bitmaskclient.base.utils;public class BitmaskCoreProvider { +} 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 16c92855..234eb9b0 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java @@ -20,6 +20,7 @@ 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; 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.LOCATION; import static se.leap.bitmaskclient.base.models.Constants.LOCATIONS; import static se.leap.bitmaskclient.base.models.Constants.NAME; @@ -27,6 +28,7 @@ import static se.leap.bitmaskclient.base.models.Constants.OPENVPN_CONFIGURATION; import static se.leap.bitmaskclient.base.models.Constants.OVERLOAD; import static se.leap.bitmaskclient.base.models.Constants.TIMEZONE; import static se.leap.bitmaskclient.base.models.Constants.VERSION; +import static se.leap.bitmaskclient.base.models.Transport.createTransportsFrom; 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; @@ -47,12 +49,18 @@ import org.json.JSONObject; import java.io.IOException; import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; import java.util.Set; import java.util.Vector; import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.core.ConfigParser; import de.blinkt.openvpn.core.connection.Connection; +import io.swagger.client.model.ModelsBridge; +import io.swagger.client.model.ModelsEIPService; +import io.swagger.client.model.ModelsGateway; +import se.leap.bitmaskclient.base.models.Transport; import se.leap.bitmaskclient.base.utils.ConfigHelper; /** @@ -71,6 +79,8 @@ public class Gateway { private JSONObject generalConfiguration; private JSONObject secrets; private JSONObject gateway; + private Vector<ModelsGateway> modelsGateways; + private Vector<ModelsBridge> modelsBridges; private JSONObject load; // the location of a gateway is its name @@ -78,6 +88,10 @@ public class Gateway { private int timezone; private int apiVersion; private Vector<VpnProfile> vpnProfiles; + private String remoteIpAddress; + private String remoteIpAddressV6; + private String host; + private String locationName; /** * Build a gateway object from a JSON OpenVPN gateway definition in eip-service.json @@ -94,32 +108,62 @@ public class Gateway { this.gateway = gateway; this.secrets = secrets; this.load = load; + this.apiVersion = eipDefinition.optInt(VERSION); + this.remoteIpAddress = gateway.optString(IP_ADDRESS); + this.remoteIpAddressV6 = gateway.optString(IP_ADDRESS6); + this.host = gateway.optString(HOST); + JSONObject location = getLocationInfo(gateway, eipDefinition); + this.locationName = location.optString(NAME); + this.timezone = location.optInt(TIMEZONE); + VpnConfigGenerator.Configuration configuration = getProfileConfig(Transport.createTransportsFrom(gateway, apiVersion)); + this.generalConfiguration = getGeneralConfiguration(eipDefinition); + this.name = configuration.profileName; + this.vpnProfiles = createVPNProfiles(configuration); + } + + + public Gateway(ModelsEIPService eipService, JSONObject secrets, ModelsGateway modelsGateway, int apiVersion) throws ConfigParser.ConfigParseError, JSONException, IOException { + this.apiVersion = apiVersion; + generalConfiguration = getGeneralConfiguration(eipService); + this.secrets = secrets; + this.modelsGateways = new Vector<>(); + this.modelsBridges = new Vector<>(); + this.modelsGateways.add(modelsGateway); + + this.remoteIpAddress = modelsGateway.getIpAddr(); + this.remoteIpAddressV6 = modelsGateway.getIp6Addr(); + this.host = modelsGateway.getHost(); + this.locationName = "UNKNOWN due to bug in menshen"; + // TODO eipService.getLocations().get + this.timezone = 2; // modelsGateway.getLocation()... + this.apiVersion = apiVersion; + VpnConfigGenerator.Configuration configuration = getProfileConfig(createTransportsFrom(modelsGateway)); + this.name = configuration.profileName; + this.vpnProfiles = createVPNProfiles(configuration); + } - apiVersion = getApiVersion(eipDefinition); - VpnConfigGenerator.Configuration configuration = getProfileConfig(eipDefinition, apiVersion); - generalConfiguration = getGeneralConfiguration(eipDefinition); - timezone = getTimezone(eipDefinition); + public Gateway(ModelsEIPService eipService, JSONObject secrets, ModelsBridge modelsBridge, int apiVersion) throws ConfigParser.ConfigParseError, JSONException, IOException { + this.apiVersion = apiVersion; + generalConfiguration = getGeneralConfiguration(eipService); + this.secrets = secrets; + this.modelsGateways = new Vector<>(); + this.modelsBridges = new Vector<>(); + this.modelsBridges.add(modelsBridge); + remoteIpAddress = modelsBridge.getIpAddr(); + host = modelsBridge.getHost(); + locationName = "UNKNOWN due to bug in menshen"; + // FIXME eipService.getLocations().get + timezone = 2; // modelsGateway.getLocation()... + this.apiVersion = apiVersion; + VpnConfigGenerator.Configuration configuration = getProfileConfig(Transport.createTransportsFrom(modelsBridge)); name = configuration.profileName; vpnProfiles = createVPNProfiles(configuration); } - private VpnConfigGenerator.Configuration getProfileConfig(JSONObject eipDefinition, int apiVersion) { - VpnConfigGenerator.Configuration config = new VpnConfigGenerator.Configuration(); - config.apiVersion = apiVersion; - config.preferUDP = getPreferUDP(); - config.experimentalTransports = allowExperimentalTransports(); - config.excludedApps = getExcludedApps(); - - config.remoteGatewayIP = config.useObfuscationPinning ? getObfuscationPinningIP() : gateway.optString(IP_ADDRESS); - config.useObfuscationPinning = useObfuscationPinning(); - config.profileName = config.useObfuscationPinning ? getObfuscationPinningGatewayLocation() : locationAsName(eipDefinition); - if (config.useObfuscationPinning) { - config.obfuscationProxyIP = getObfuscationPinningIP(); - config.obfuscationProxyPort = getObfuscationPinningPort(); - config.obfuscationProxyCert = getObfuscationPinningCert(); - config.obfuscationProxyKCP = getObfuscationPinningKCP(); - } - return config; + + + private VpnConfigGenerator.Configuration getProfileConfig(Vector<Transport> transports) { + return VpnConfigGenerator.Configuration.createProfileConfig(transports, apiVersion, remoteIpAddress, remoteIpAddressV6, locationName); } public void updateLoad(JSONObject load) { @@ -134,29 +178,33 @@ public class Gateway { } } - private int getTimezone(JSONObject eipDefinition) { - JSONObject location = getLocationInfo(eipDefinition); - return location.optInt(TIMEZONE); - } + private JSONObject getGeneralConfiguration(ModelsEIPService eipService) { + JSONObject config = new JSONObject(); + Map<String, Object> openvpnOptions = eipService.getOpenvpnConfiguration(); + Set<String> keys = openvpnOptions.keySet(); + Iterator<String> i = keys.iterator(); + while (i.hasNext()) { + try { + String key = i.next(); + Object o = openvpnOptions.get(key); + config.put(key, o); + } catch (JSONException e) { + e.printStackTrace(); + } + } - private int getApiVersion(JSONObject eipDefinition) { - return eipDefinition.optInt(VERSION); + return config; } public String getRemoteIP() { - return gateway.optString(IP_ADDRESS); + return remoteIpAddress; } public String getHost() { - return gateway.optString(HOST); + return host; } - private String locationAsName(JSONObject eipDefinition) { - JSONObject location = getLocationInfo(eipDefinition); - return location.optString(NAME); - } - - private JSONObject getLocationInfo(JSONObject eipDefinition) { + private JSONObject getLocationInfo(JSONObject gateway, JSONObject eipDefinition) { try { JSONObject locations = eipDefinition.getJSONObject(LOCATIONS); @@ -191,9 +239,8 @@ public class Gateway { */ private @NonNull Vector<VpnProfile> createVPNProfiles(VpnConfigGenerator.Configuration profileConfig) throws ConfigParser.ConfigParseError, IOException, JSONException { - VpnConfigGenerator vpnConfigurationGenerator = new VpnConfigGenerator(generalConfiguration, secrets, gateway, profileConfig); - Vector<VpnProfile> profiles = vpnConfigurationGenerator.generateVpnProfiles(); - return profiles; + VpnConfigGenerator vpnConfigurationGenerator = new VpnConfigGenerator(generalConfiguration, secrets, profileConfig); + return vpnConfigurationGenerator.generateVpnProfiles(); } public String getName() { @@ -250,8 +297,8 @@ public class Gateway { return getProfile(transportType, obfuscationTransportLayerProtocols) != null; } - public HashSet<Connection.TransportType> getSupportedTransports() { - HashSet<Connection.TransportType> transportTypes = new HashSet<>(); + public Set<Connection.TransportType> getSupportedTransports() { + Set<Connection.TransportType> transportTypes = new HashSet<>(); for (VpnProfile p : vpnProfiles) { transportTypes.add(p.getTransportType()); } @@ -277,4 +324,16 @@ public class Gateway { return new Gson().toJson(this, Gateway.class); } + public Gateway addTransport(Transport transport) { + Vector<Transport> transports = new Vector<>(); + transports.add(transport); + VpnConfigGenerator.Configuration profileConfig = getProfileConfig(transports); + try { + Vector<VpnProfile> profiles = createVPNProfiles(profileConfig); + vpnProfiles.addAll(profiles); + } catch (ConfigParser.ConfigParseError | IOException | JSONException e) { + e.printStackTrace(); + } + return this; + } } 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 37360f81..d7f3b42e 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java @@ -20,8 +20,10 @@ import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4_HOP; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OPENVPN; import static de.blinkt.openvpn.core.connection.Connection.TransportType.PT; +import static se.leap.bitmaskclient.base.models.Constants.CERT; import static se.leap.bitmaskclient.base.models.Constants.GATEWAYS; import static se.leap.bitmaskclient.base.models.Constants.HOST; +import static se.leap.bitmaskclient.base.models.Constants.IAT_MODE; import static se.leap.bitmaskclient.base.models.Constants.KCP; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_VPN_CERTIFICATE; import static se.leap.bitmaskclient.base.models.Constants.SORTED_GATEWAYS; @@ -65,6 +67,9 @@ import de.blinkt.openvpn.core.ConfigParser; import de.blinkt.openvpn.core.VpnStatus; import de.blinkt.openvpn.core.connection.Connection; import de.blinkt.openvpn.core.connection.Connection.TransportType; +import io.swagger.client.model.ModelsBridge; +import io.swagger.client.model.ModelsEIPService; +import io.swagger.client.model.ModelsGateway; import se.leap.bitmaskclient.BuildConfig; import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.base.models.GatewayJson; @@ -415,84 +420,150 @@ public class GatewaysManager { return new Gson().toJson(gateways, listType); } - /** - * parse gateways from Provider's eip service - * @param provider - */ - private void parseDefaultGateways(Provider provider) { - try { - JSONObject eipDefinition = provider.getEipServiceJson(); - JSONObject secrets = secretsConfigurationFromCurrentProvider(); - JSONArray gatewaysDefined = new JSONArray(); - try { - gatewaysDefined = eipDefinition.getJSONArray(GATEWAYS); - } catch (Exception e) { - e.printStackTrace(); - } - - if (PreferenceHelper.useObfuscationPinning()) { - try { - Transport[] transports = new Transport[]{ - new Transport(OBFS4.toString(), - new String[]{getObfuscationPinningKCP() ? "kcp" : "tcp"}, - new String[]{getObfuscationPinningPort()}, - getObfuscationPinningCert())}; - GatewayJson.Capabilities capabilities = new GatewayJson.Capabilities(false, false, false, transports, false); - GatewayJson gatewayJson = new GatewayJson(context.getString(R.string.unknown_location), getObfuscationPinningIP( - - ), null, PINNED_OBFUSCATION_PROXY, capabilities); - Gateway gateway = new Gateway(eipDefinition, secrets, new JSONObject(gatewayJson.toString())); - addGateway(gateway); - } catch (JSONException | ConfigParser.ConfigParseError | IOException e) { - e.printStackTrace(); - } - } else { - for (int i = 0; i < gatewaysDefined.length(); i++) { - try { - JSONObject gw = gatewaysDefined.getJSONObject(i); - Gateway aux = new Gateway(eipDefinition, secrets, gw); - if (gateways.get(aux.getHost()) == null) { - addGateway(aux); - } - } catch (JSONException | IOException e) { - e.printStackTrace(); - VpnStatus.logError("Unable to parse gateway config!"); - } catch (ConfigParser.ConfigParseError e) { - VpnStatus.logError("Unable to parse gateway config: " + e.getLocalizedMessage()); - } - } - } - } catch (NullPointerException npe) { - npe.printStackTrace(); - } + public void parseGatewaysV3(Provider provider) { + try { + JSONObject eipDefinition = provider.getEipServiceJson(); + JSONObject secrets = secretsConfigurationFromCurrentProvider(); + JSONArray gatewaysDefined = new JSONArray(); + try { + gatewaysDefined = eipDefinition.getJSONArray(GATEWAYS); + } catch (Exception e) { + e.printStackTrace(); + } + + if (PreferenceHelper.useObfuscationPinning()) { + try { + Transport[] transports = new Transport[]{ + new Transport(OBFS4.toString(), + new String[]{getObfuscationPinningKCP() ? "kcp" : "tcp"}, + new String[]{getObfuscationPinningPort()}, + getObfuscationPinningCert())}; + GatewayJson.Capabilities capabilities = new GatewayJson.Capabilities(false, false, false, transports, false); + GatewayJson gatewayJson = new GatewayJson(context.getString(R.string.unknown_location), getObfuscationPinningIP( + + ), null, PINNED_OBFUSCATION_PROXY, capabilities); + Gateway gateway = new Gateway(eipDefinition, secrets, new JSONObject(gatewayJson.toString())); + addGateway(gateway); + } catch (JSONException | ConfigParser.ConfigParseError | IOException e) { + e.printStackTrace(); + } + } else { + for (int i = 0; i < gatewaysDefined.length(); i++) { + try { + JSONObject gw = gatewaysDefined.getJSONObject(i); + Gateway aux = new Gateway(eipDefinition, secrets, gw); + if (gateways.get(aux.getHost()) == null) { + addGateway(aux); + } + } catch (JSONException | IOException e) { + e.printStackTrace(); + VpnStatus.logError("Unable to parse gateway config!"); + } catch (ConfigParser.ConfigParseError e) { + VpnStatus.logError("Unable to parse gateway config: " + e.getLocalizedMessage()); + } + } + } + } catch (NullPointerException npe) { + npe.printStackTrace(); + } + + if (BuildConfig.BUILD_TYPE.equals("debug") && handleGatewayPinning()) { + return; + } + + // parse v3 menshen geoIP json variants + if (hasSortedGatewaysWithLoad(provider)) { + parseGatewaysWithLoad(provider); + } else { + parseSimpleGatewayList(provider); + } + } + + public void parseGatewaysV5(Provider provider) { + ModelsGateway[] modelsGateways = provider.getGateways(); + ModelsBridge[] modelsBridges = provider.getBridges(); + ModelsEIPService modelsEIPService = provider.getService(); + JSONObject secrets = secretsConfigurationFromCurrentProvider(); + int apiVersion = provider.getApiVersion(); + + if (PreferenceHelper.useObfuscationPinning()) { + try { + ModelsBridge modelsBridge = new ModelsBridge(); + modelsBridge.ipAddr(getObfuscationPinningIP()); + modelsBridge.port(Integer.valueOf(getObfuscationPinningPort())); + HashMap<String, Object> options = new HashMap<>(); + options.put(CERT, getObfuscationPinningCert()); + options.put(IAT_MODE, "0"); + modelsBridge.options(options); + modelsBridge.transport(getObfuscationPinningKCP() ? "kcp" : "tcp"); + modelsBridge.type(OBFS4.toString()); + modelsBridge.host(PINNED_OBFUSCATION_PROXY); + Gateway gateway = new Gateway(modelsEIPService, secrets, modelsBridge, provider.getApiVersion()); + addGateway(gateway); + } catch (NumberFormatException | ConfigParser.ConfigParseError | JSONException | + IOException e) { + e.printStackTrace(); + } + } else { + for (ModelsGateway modelsGateway : modelsGateways) { + String host = modelsGateway.getHost(); + Gateway gateway = gateways.get(host); + if (gateway == null) { + try { + addGateway(new Gateway(modelsEIPService, secrets, modelsGateway, apiVersion)); + } catch (ConfigParser.ConfigParseError | JSONException | IOException e) { + e.printStackTrace(); + } + } else { + addGateway(gateway.addTransport(Transport.createTransportFrom(modelsGateway))); + } + } + for (ModelsBridge modelsBridge : modelsBridges) { + String host = modelsBridge.getHost(); + Gateway gateway = gateways.get(host); + if (gateway == null) { + try { + addGateway(new Gateway(modelsEIPService, secrets, modelsBridge, apiVersion)); + } catch (ConfigParser.ConfigParseError | JSONException | IOException e) { + e.printStackTrace(); + } + } else { + addGateway(gateway.addTransport(Transport.createTransportFrom(modelsBridge))); + } + } + } + + if (BuildConfig.BUILD_TYPE.equals("debug")) { + handleGatewayPinning(); + } } private void parseSimpleGatewayList(Provider provider) { - try { - JSONObject geoIpJson = provider.getGeoIpJson(); - JSONArray gatewaylist = geoIpJson.getJSONArray(GATEWAYS); - - for (int i = 0; i < gatewaylist.length(); i++) { - try { - String key = gatewaylist.getString(i); - if (gateways.containsKey(key)) { - presortedList.add(gateways.get(key)); - } - } catch (JSONException e) { - e.printStackTrace(); - } - } - } catch (NullPointerException | JSONException npe) { - Log.d(TAG, "No valid geoip json found: " + npe.getLocalizedMessage()); - } + try { + JSONObject geoIpJson = provider.getGeoIpJson(); + JSONArray gatewaylist = geoIpJson.getJSONArray(GATEWAYS); + + for (int i = 0; i < gatewaylist.length(); i++) { + try { + String key = gatewaylist.getString(i); + if (gateways.containsKey(key)) { + presortedList.add(gateways.get(key)); + } + } catch (JSONException e) { + e.printStackTrace(); + } + } + } catch (NullPointerException | JSONException npe) { + Log.d(TAG, "No valid geoip json found: " + npe.getLocalizedMessage()); + } } private boolean hasSortedGatewaysWithLoad(@Nullable Provider provider) { - if (provider == null) { - return false; - } - JSONObject geoIpJson = provider.getGeoIpJson(); - return geoIpJson.has(SORTED_GATEWAYS); + if (provider == null) { + return false; + } + JSONObject geoIpJson = provider.getGeoIpJson(); + return geoIpJson.has(SORTED_GATEWAYS); } private void parseGatewaysWithLoad(Provider provider) { @@ -534,30 +605,28 @@ public class GatewaysManager { } private void configureFromCurrentProvider() { - Provider provider = ProviderObservable.getInstance().getCurrentProvider(); - parseDefaultGateways(provider); - if (BuildConfig.BUILD_TYPE.equals("debug") && handleGatewayPinning()) { - return; - } - if (hasSortedGatewaysWithLoad(provider)) { - parseGatewaysWithLoad(provider); - } else { - parseSimpleGatewayList(provider); - } - + Provider provider = ProviderObservable.getInstance().getCurrentProvider(); + if (provider == null) { + return; + } + if (provider.getApiVersion() < 5) { + parseGatewaysV3(provider); + } else { + parseGatewaysV5(provider); + } } private boolean handleGatewayPinning() { - String host = PreferenceHelper.getPinnedGateway(); - if (host == null) { - return false; - } - Gateway gateway = gateways.get(host); - gateways.clear(); - if (gateway != null) { - gateways.put(host, gateway); - } - return true; + String host = PreferenceHelper.getPinnedGateway(); + if (host == null) { + return false; + } + Gateway gateway = gateways.get(host); + gateways.clear(); + if (gateway != null) { + gateways.put(host, gateway); + } + return true; } } 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 5defa7e6..f988dfa0 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java @@ -19,22 +19,25 @@ package se.leap.bitmaskclient.eip; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4_HOP; 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_VPN_CERTIFICATE; import static se.leap.bitmaskclient.base.models.Constants.REMOTE; import static se.leap.bitmaskclient.base.models.Constants.TCP; -import static se.leap.bitmaskclient.base.models.Constants.TRANSPORT; import static se.leap.bitmaskclient.base.models.Constants.UDP; - +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.getPreferUDP; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.useObfuscationPinning; + +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; -import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -57,7 +60,6 @@ import se.leap.bitmaskclient.pluggableTransports.models.Obfs4Options; public class VpnConfigGenerator { private final JSONObject generalConfiguration; - private final JSONObject gateway; private final JSONObject secrets; Vector<Transport> transports = new Vector<>(); private final int apiVersion; @@ -69,6 +71,7 @@ public class VpnConfigGenerator { private final String obfuscationPinningCert; private final boolean obfuscationPinningKCP; private final String remoteGatewayIP; + private final String remoteGatewayIPv6; private final String profileName; private final Set<String> excludedApps; @@ -81,6 +84,7 @@ public class VpnConfigGenerator { boolean preferUDP; boolean experimentalTransports; String remoteGatewayIP = ""; + String remoteGatewayIPv6 = ""; String profileName = ""; Set<String> excludedApps = null; @@ -89,11 +93,33 @@ public class VpnConfigGenerator { String obfuscationProxyIP = ""; String obfuscationProxyPort = ""; String obfuscationProxyCert = ""; + Vector<Transport> transports = new Vector<>(); + + public static VpnConfigGenerator.Configuration createProfileConfig(Vector<Transport> transports, int apiVersion, String remoteIpAddress, String remoteIpAddressV6, String locationName) { + VpnConfigGenerator.Configuration config = new VpnConfigGenerator.Configuration(); + config.apiVersion = apiVersion; + config.preferUDP = getPreferUDP(); + config.experimentalTransports = allowExperimentalTransports(); + config.excludedApps = getExcludedApps(); + + config.remoteGatewayIP = config.useObfuscationPinning ? getObfuscationPinningIP() : remoteIpAddress; + config.remoteGatewayIPv6 = config.useObfuscationPinning ? null : remoteIpAddressV6; + config.useObfuscationPinning = useObfuscationPinning(); + config.profileName = config.useObfuscationPinning ? getObfuscationPinningGatewayLocation() : locationName; + if (config.useObfuscationPinning) { + config.obfuscationProxyIP = getObfuscationPinningIP(); + config.obfuscationProxyPort = getObfuscationPinningPort(); + config.obfuscationProxyCert = getObfuscationPinningCert(); + config.obfuscationProxyKCP = getObfuscationPinningKCP(); + } + config.transports = transports; + return config; + } } - public VpnConfigGenerator(JSONObject generalConfiguration, JSONObject secrets, JSONObject gateway, Configuration config) throws ConfigParser.ConfigParseError { + + public VpnConfigGenerator(JSONObject generalConfiguration, JSONObject secrets, Configuration config) { this.generalConfiguration = generalConfiguration; - this.gateway = gateway; this.secrets = secrets; this.apiVersion = config.apiVersion; this.preferUDP = config.preferUDP; @@ -104,23 +130,10 @@ public class VpnConfigGenerator { this.obfuscationPinningCert = config.obfuscationProxyCert; this.obfuscationPinningKCP = config.obfuscationProxyKCP; this.remoteGatewayIP = config.remoteGatewayIP; + this.remoteGatewayIPv6 = config.remoteGatewayIPv6; + this.transports = config.transports; this.profileName = config.profileName; this.excludedApps = config.excludedApps; - checkCapabilities(); - } - - public void checkCapabilities() throws ConfigParser.ConfigParseError { - try { - if (apiVersion >= 3) { - JSONArray supportedTransports = gateway.getJSONObject(CAPABILITIES).getJSONArray(TRANSPORT); - for (int i = 0; i < supportedTransports.length(); i++) { - Transport transport = Transport.fromJson(supportedTransports.getJSONObject(i)); - transports.add(transport); - } - } - } catch (Exception e) { - throw new ConfigParser.ConfigParseError("Api version ("+ apiVersion +") did not match required JSON fields"); - } } public Vector<VpnProfile> generateVpnProfiles() throws @@ -128,42 +141,28 @@ public class VpnConfigGenerator { NumberFormatException { Vector<VpnProfile> profiles = new Vector<>(); - if (apiVersion >= 3) { - for (Transport transport : transports){ - if (transport.getTransportType().isPluggableTransport()) { - Transport.Options transportOptions = transport.getOptions(); - if (!experimentalTransports && transportOptions != null && transportOptions.isExperimental()) { - continue; - } - } else if (transport.getTransportType() == OPENVPN && useObfuscationPinning) { + for (Transport transport : transports){ + if (transport.getTransportType().isPluggableTransport()) { + Transport.Options transportOptions = transport.getOptions(); + if (!experimentalTransports && transportOptions != null && transportOptions.isExperimental()) { continue; } - try { - profiles.add(createProfile(transport)); - } catch (ConfigParser.ConfigParseError | NumberFormatException | JSONException | IOException e) { - e.printStackTrace(); - } + } else if (transport.getTransportType() == OPENVPN && useObfuscationPinning) { + continue; } - } else if (supportsOpenvpn()) { - // API v1 - TODO: let's remove support for API v1 soon try { - profiles.add(createApiv1OpenvpnProfile()); + profiles.add(createProfile(transport)); } catch (ConfigParser.ConfigParseError | NumberFormatException | JSONException | IOException e) { e.printStackTrace(); } } + if (profiles.isEmpty()) { throw new ConfigParser.ConfigParseError("No supported transports detected."); } return profiles; } - private boolean supportsOpenvpn() { - return !useObfuscationPinning && - ((apiVersion >= 3 && getTransport(OPENVPN) != null) || - (apiVersion < 3 && !gatewayConfiguration(null).isEmpty())); - } - private String getConfigurationString(Transport transport) { return generalConfiguration() + newLine @@ -193,23 +192,8 @@ public class VpnConfigGenerator { return profile; } - @VisibleForTesting - protected VpnProfile createApiv1OpenvpnProfile() throws IOException, ConfigParser.ConfigParseError, JSONException { - String configuration = getConfigurationString(null); - ConfigParser icsOpenvpnConfigParser = new ConfigParser(); - icsOpenvpnConfigParser.parseConfig(new StringReader(configuration)); - - VpnProfile profile = icsOpenvpnConfigParser.convertProfile(OPENVPN); - profile.mName = profileName; - profile.mGatewayIp = remoteGatewayIP; - if (excludedApps != null) { - profile.mAllowedAppsVpn = new HashSet<>(excludedApps); - } - return profile; - } - - private Obfs4Options getObfs4Options(Transport transport) throws JSONException { - String ip = gateway.getString(IP_ADDRESS); + private Obfs4Options getObfs4Options(Transport transport) throws JSONException, NullPointerException { + String ip = remoteGatewayIP; if (useObfuscationPinning) { transport = new Transport(OBFS4.toString(), new String[]{obfuscationPinningKCP ? KCP : TCP}, @@ -223,9 +207,9 @@ public class VpnConfigGenerator { private String generalConfiguration() { String commonOptions = ""; try { - Iterator keys = generalConfiguration.keys(); + Iterator<String> keys = generalConfiguration.keys(); while (keys.hasNext()) { - String key = keys.next().toString(); + String key = keys.next(); commonOptions += key + " "; for (String word : String.valueOf(generalConfiguration.get(key)).split(" ")) @@ -243,32 +227,19 @@ public class VpnConfigGenerator { return commonOptions; } - private String gatewayConfiguration(@Nullable Transport transport) { + private String gatewayConfiguration(@NonNull Transport transport) { String configs = ""; StringBuilder stringBuilder = new StringBuilder(); try { - 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} : - new String[]{ipAddress6, ipAddress}; - if (transport == null) { - throw new NullPointerException("Transport is not allowed to be null in APIv3+"); - } + case 1, 2 -> gatewayConfigApiv1(transport, stringBuilder, remoteGatewayIP); + case 3, 4, 5 -> { + String[] ipAddresses = (remoteGatewayIPv6 == null || remoteGatewayIPv6.isEmpty()) ? + new String[]{remoteGatewayIP} : + new String[]{remoteGatewayIPv6, remoteGatewayIP}; gatewayConfigMinApiv3(transport, stringBuilder, ipAddresses); - break; + } } } catch (JSONException | NullPointerException e) { // TODO Auto-generated catch block @@ -300,15 +271,15 @@ public class VpnConfigGenerator { return null; } - private void gatewayConfigApiv1(StringBuilder stringBuilder, String ipAddress, JSONObject capabilities) throws JSONException { - int port; - String protocol; - JSONArray ports = capabilities.getJSONArray(PORTS); - JSONArray protocols = capabilities.getJSONArray(PROTOCOLS); - for (int i = 0; i < ports.length(); i++) { - port = ports.getInt(i); - for (int j = 0; j < protocols.length(); j++) { - protocol = protocols.optString(j); + private void gatewayConfigApiv1(Transport transport, StringBuilder stringBuilder, String ipAddress) throws JSONException { + if (transport == null || transport.getProtocols() == null || transport.getPorts() == null) { + VpnStatus.logError("Misconfigured provider: missing details for transport openvpn on gateway " + ipAddress); + return; + } + String[] ports = transport.getPorts(); + String[] protocols = transport.getProtocols(); + for (String port : ports) { + for (String protocol : protocols) { String newRemote = REMOTE + " " + ipAddress + " " + port + " " + protocol + newLine; stringBuilder.append(newRemote); } |