/** * Copyright (c) 2013 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.eip; 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.LOCATION; import static se.leap.bitmaskclient.base.models.Constants.LOCATIONS; import static se.leap.bitmaskclient.base.models.Constants.NAME; 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.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 com.google.gson.Gson; import org.json.JSONException; import org.json.JSONObject; import java.io.IOException; import java.util.HashSet; 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 se.leap.bitmaskclient.base.utils.ConfigHelper; /** * Gateway provides objects defining gateways and their metadata. * Each instance contains a VpnProfile for OpenVPN specific data and member * variables describing capabilities and location (name) * * @author Sean Leonard * @author Parménides GV * @author cyberta */ public class Gateway { public final static String TAG = Gateway.class.getSimpleName(); private JSONObject generalConfiguration; private JSONObject secrets; private JSONObject gateway; private JSONObject load; // the location of a gateway is its name private String name; private int timezone; private int apiVersion; private Vector vpnProfiles; /** * Build a gateway object from a JSON OpenVPN gateway definition in eip-service.json * and create a VpnProfile belonging to it. */ public Gateway(JSONObject eipDefinition, JSONObject secrets, JSONObject gateway) throws ConfigParser.ConfigParseError, JSONException, IOException { this(eipDefinition, secrets, gateway, null); } public Gateway(JSONObject eipDefinition, JSONObject secrets, JSONObject gateway, JSONObject load) throws ConfigParser.ConfigParseError, JSONException, IOException { this.gateway = gateway; this.secrets = secrets; this.load = load; apiVersion = getApiVersion(eipDefinition); VpnConfigGenerator.Configuration configuration = getProfileConfig(eipDefinition, apiVersion); generalConfiguration = getGeneralConfiguration(eipDefinition); timezone = getTimezone(eipDefinition); 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; } public void updateLoad(JSONObject load) { this.load = load; } private JSONObject getGeneralConfiguration(JSONObject eipDefinition) { try { return eipDefinition.getJSONObject(OPENVPN_CONFIGURATION); } catch (JSONException e) { return new JSONObject(); } } private int getTimezone(JSONObject eipDefinition) { JSONObject location = getLocationInfo(eipDefinition); return location.optInt(TIMEZONE); } private int getApiVersion(JSONObject eipDefinition) { return eipDefinition.optInt(VERSION); } public String getRemoteIP() { return gateway.optString(IP_ADDRESS); } public String getHost() { return gateway.optString(HOST); } private String locationAsName(JSONObject eipDefinition) { JSONObject location = getLocationInfo(eipDefinition); return location.optString(NAME); } private JSONObject getLocationInfo(JSONObject eipDefinition) { try { JSONObject locations = eipDefinition.getJSONObject(LOCATIONS); return locations.getJSONObject(gateway.getString(LOCATION)); } catch (JSONException e) { return new JSONObject(); } } public boolean hasLoadInfo() { return load != null; } public double getFullness() { try { return load.getDouble(FULLNESS); } catch (JSONException | NullPointerException e) { return ConfigHelper.getConnectionQualityFromTimezoneDistance(timezone); } } public boolean isOverloaded() { try { return load.getBoolean(OVERLOAD); } catch (JSONException | NullPointerException e) { return false; } } /** * Create and attach the VpnProfile to our gateway object */ private @NonNull Vector createVPNProfiles(VpnConfigGenerator.Configuration profileConfig) throws ConfigParser.ConfigParseError, IOException, JSONException { VpnConfigGenerator vpnConfigurationGenerator = new VpnConfigGenerator(generalConfiguration, secrets, gateway, profileConfig); Vector profiles = vpnConfigurationGenerator.generateVpnProfiles(); return profiles; } public String getName() { return name; } public Vector getProfiles() { return vpnProfiles; } /** * Returns a VpnProfile that supports a given transport type and any of the given transport * layer protocols (e.g. TCP, KCP). If multiple VpnProfiles fulfill these requirements, a random * profile will be chosen. This can currently only occur for obfuscation protocols. * @param transportType transport type, e.g. openvpn or obfs4 * @param obfuscationTransportLayerProtocols Vector of transport layer protocols PTs can be based on * @return */ public @Nullable VpnProfile getProfile(Connection.TransportType transportType, @Nullable Set obfuscationTransportLayerProtocols) { Vector results = new Vector<>(); for (VpnProfile vpnProfile : vpnProfiles) { if (vpnProfile.getTransportType() == transportType) { if (!vpnProfile.usePluggableTransports() || obfuscationTransportLayerProtocols == null || obfuscationTransportLayerProtocols.contains(vpnProfile.getObfuscationTransportLayerProtocol())) { results.add(vpnProfile); } } } if (results.size() == 0) { return null; } int randomIndex = (int) (Math.random() * (results.size())); return results.get(randomIndex); } public boolean hasProfile(VpnProfile profile) { return vpnProfiles.contains(profile); } /** * Checks if a transport type is supported by the gateway. * In case the transport type is an obfuscation transport, you can pass a Vector of required transport layer protocols. * This way you can filter for TCP based obfs4 traffic versus KCP based obfs4 traffic. * @param transportType transport type, e.g. openvpn or obfs4 * @param obfuscationTransportLayerProtocols filters for _any_ of these transport layer protocols (e.g. TCP or KCP) of a given obfuscation transportType, can be omitted if transportType is OPENVPN. * * @return */ public boolean supportsTransport(Connection.TransportType transportType, @Nullable Set obfuscationTransportLayerProtocols) { if (transportType == PT) { return supportsPluggableTransports(); } return getProfile(transportType, obfuscationTransportLayerProtocols) != null; } public HashSet getSupportedTransports() { HashSet transportTypes = new HashSet<>(); for (VpnProfile p : vpnProfiles) { transportTypes.add(p.getTransportType()); } return transportTypes; } public boolean supportsPluggableTransports() { for (VpnProfile profile : vpnProfiles) { Connection.TransportType transportType = profile.getTransportType(); if (transportType.isPluggableTransport()) { return true; } } return false; } public int getTimezone() { return timezone; } @Override public String toString() { return new Gson().toJson(this, Gateway.class); } }