diff options
Diffstat (limited to 'app/src/main/java/se/leap')
14 files changed, 364 insertions, 172 deletions
diff --git a/app/src/main/java/se/leap/bitmaskclient/Constants.java b/app/src/main/java/se/leap/bitmaskclient/Constants.java index 6ec3076c..6462b663 100644 --- a/app/src/main/java/se/leap/bitmaskclient/Constants.java +++ b/app/src/main/java/se/leap/bitmaskclient/Constants.java @@ -142,4 +142,5 @@ public interface Constants { String LOCATION = "location"; String OPENVPN_CONFIGURATION = "openvpn_configuration"; String GATEWAYS = "gateways"; + String HOST = "host"; } diff --git a/app/src/main/java/se/leap/bitmaskclient/EipFragment.java b/app/src/main/java/se/leap/bitmaskclient/EipFragment.java index b8883f77..fceadd88 100644 --- a/app/src/main/java/se/leap/bitmaskclient/EipFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/EipFragment.java @@ -27,14 +27,6 @@ import android.graphics.ColorMatrixColorFilter; import android.os.Bundle; import android.os.IBinder; import android.os.Vibrator; -import androidx.annotation.NonNull; -import androidx.fragment.app.DialogFragment; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentTransaction; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.widget.AppCompatButton; -import androidx.appcompat.widget.AppCompatImageView; -import androidx.appcompat.widget.AppCompatTextView; import android.text.TextUtils; import android.util.Log; import android.view.Gravity; @@ -44,6 +36,15 @@ import android.view.ViewGroup; import android.widget.TextView; 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; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentTransaction; + import java.util.Observable; import java.util.Observer; @@ -62,6 +63,8 @@ import static android.view.View.GONE; import static android.view.View.VISIBLE; import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_NONETWORK; import static se.leap.bitmaskclient.Constants.ASK_TO_CANCEL_VPN; +import static se.leap.bitmaskclient.Constants.EIP_ACTION_START; +import static se.leap.bitmaskclient.Constants.EIP_EARLY_ROUTES; import static se.leap.bitmaskclient.Constants.EIP_RESTART_ON_BOOT; import static se.leap.bitmaskclient.Constants.PROVIDER_KEY; import static se.leap.bitmaskclient.Constants.REQUEST_CODE_CONFIGURE_LEAP; @@ -71,6 +74,7 @@ import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; import static se.leap.bitmaskclient.EipSetupObserver.connectionRetry; import static se.leap.bitmaskclient.EipSetupObserver.gatewayOrder; import static se.leap.bitmaskclient.EipSetupObserver.reconnectingWithDifferentGateway; +import static se.leap.bitmaskclient.ProviderAPI.DOWNLOAD_GEOIP_JSON; import static se.leap.bitmaskclient.ProviderAPI.UPDATE_INVALID_VPN_CERTIFICATE; import static se.leap.bitmaskclient.ProviderAPI.USER_MESSAGE; import static se.leap.bitmaskclient.R.string.vpn_certificate_user_message; @@ -301,7 +305,14 @@ public class EipFragment extends Fragment implements Observer { Log.e(TAG, "context is null when trying to start VPN"); return; } - EipCommand.startVPN(context.getApplicationContext(), false); + if (!provider.getGeoipUrl().isDefault() && provider.shouldUpdateGeoIpJson()) { + Bundle bundle = new Bundle(); + bundle.putBoolean(EIP_ACTION_START, true); + bundle.putBoolean(EIP_EARLY_ROUTES, false); + ProviderAPICommand.execute(getContext().getApplicationContext(), DOWNLOAD_GEOIP_JSON, bundle, provider); + } else { + EipCommand.startVPN(context.getApplicationContext(), false); + } vpnStateImage.showProgress(); routedText.setVisibility(GONE); vpnRoute.setVisibility(GONE); diff --git a/app/src/main/java/se/leap/bitmaskclient/EipSetupObserver.java b/app/src/main/java/se/leap/bitmaskclient/EipSetupObserver.java index ee7e7ef5..7504e0c0 100644 --- a/app/src/main/java/se/leap/bitmaskclient/EipSetupObserver.java +++ b/app/src/main/java/se/leap/bitmaskclient/EipSetupObserver.java @@ -6,9 +6,10 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.os.Bundle; -import androidx.localbroadcastmanager.content.LocalBroadcastManager; import android.util.Log; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; + import org.json.JSONObject; import java.util.Vector; @@ -39,11 +40,14 @@ import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_KEY; import static se.leap.bitmaskclient.Constants.EIP_ACTION_PREPARE_VPN; import static se.leap.bitmaskclient.Constants.EIP_ACTION_START; import static se.leap.bitmaskclient.Constants.EIP_ACTION_START_ALWAYS_ON_VPN; +import static se.leap.bitmaskclient.Constants.EIP_EARLY_ROUTES; import static se.leap.bitmaskclient.Constants.EIP_REQUEST; import static se.leap.bitmaskclient.Constants.PROVIDER_KEY; import static se.leap.bitmaskclient.Constants.PROVIDER_PROFILE; import static se.leap.bitmaskclient.ProviderAPI.CORRECTLY_DOWNLOADED_EIP_SERVICE; +import static se.leap.bitmaskclient.ProviderAPI.CORRECTLY_DOWNLOADED_GEOIP_JSON; import static se.leap.bitmaskclient.ProviderAPI.CORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.ProviderAPI.INCORRECTLY_DOWNLOADED_GEOIP_JSON; /** * Created by cyberta on 05.12.18. @@ -151,6 +155,15 @@ class EipSetupObserver extends BroadcastReceiver implements VpnStatus.StateListe PreferenceHelper.storeProviderInPreferences(preferences, provider); EipCommand.startVPN(context.getApplicationContext(), true); break; + case CORRECTLY_DOWNLOADED_GEOIP_JSON: + provider = resultData.getParcelable(PROVIDER_KEY); + ProviderObservable.getInstance().updateProvider(provider); + PreferenceHelper.storeProviderInPreferences(preferences, provider); + maybeStartEipService(resultData); + break; + case INCORRECTLY_DOWNLOADED_GEOIP_JSON: + maybeStartEipService(resultData); + break; default: break; } @@ -160,6 +173,13 @@ class EipSetupObserver extends BroadcastReceiver implements VpnStatus.StateListe } } + private void maybeStartEipService(Bundle resultData) { + if (resultData.getBoolean(EIP_ACTION_START)) { + boolean earlyRoutes = resultData.getBoolean(EIP_EARLY_ROUTES); + EipCommand.startVPN(context.getApplicationContext(), earlyRoutes); + } + } + private void handleEipEvent(Intent intent) { int resultCode = intent.getIntExtra(BROADCAST_RESULT_CODE, RESULT_CANCELED); @@ -184,6 +204,7 @@ class EipSetupObserver extends BroadcastReceiver implements VpnStatus.StateListe finishGatewaySetup(false); EipCommand.startBlockingVPN(context.getApplicationContext()); } else { + //FIXME: finishGatewaySetup(false); EipCommand.stopVPN(context); EipStatus.refresh(); diff --git a/app/src/main/java/se/leap/bitmaskclient/Provider.java b/app/src/main/java/se/leap/bitmaskclient/Provider.java index 4dd7c76e..186ce11e 100644 --- a/app/src/main/java/se/leap/bitmaskclient/Provider.java +++ b/app/src/main/java/se/leap/bitmaskclient/Provider.java @@ -18,7 +18,6 @@ package se.leap.bitmaskclient; import android.os.Parcel; import android.os.Parcelable; -import androidx.annotation.NonNull; import com.google.gson.Gson; @@ -46,10 +45,13 @@ import static se.leap.bitmaskclient.ProviderAPI.ERRORS; public final class Provider implements Parcelable { private static long EIP_SERVICE_TIMEOUT = 1000 * 60 * 60 * 24 * 3; + private static long GEOIP_SERVICE_TIMEOUT = 1000 * 60 * 60; private JSONObject definition = new JSONObject(); // Represents our Provider's provider.json private JSONObject eipServiceJson = new JSONObject(); + private JSONObject geoIpJson = new JSONObject(); private DefaultedURL mainUrl = new DefaultedURL(); private DefaultedURL apiUrl = new DefaultedURL(); + private DefaultedURL geoipUrl = new DefaultedURL(); private String providerIp = ""; private String providerApiIp = ""; private String certificatePin = ""; @@ -59,6 +61,7 @@ public final class Provider implements Parcelable { private String privateKey = ""; private String vpnCertificate = ""; private long lastEipServiceUpdate = 0L; + private long lastGeoIpUpdate = 0L; private boolean allowAnonymous; private boolean allowRegistered; @@ -78,21 +81,31 @@ public final class Provider implements Parcelable { DOMAIN = "domain", MAIN_URL = "main_url", PROVIDER_IP = "provider_ip", - PROVIDER_API_IP = "provider_api_ip"; + PROVIDER_API_IP = "provider_api_ip", + GEOIP_URL = "geoip_url"; private static final String API_TERM_NAME = "name"; public Provider() { } public Provider(String mainUrl) { + this(mainUrl, null); + } + + public Provider(String mainUrl, String geoipUrl) { try { this.mainUrl.setUrl(new URL(mainUrl)); } catch (MalformedURLException e) { this.mainUrl = new DefaultedURL(); } + setGeoipUrl(geoipUrl); } public Provider(String mainUrl, String providerIp, String providerApiIp) { + this(mainUrl, null, providerIp, providerApiIp); + } + + public Provider(String mainUrl, String geoipUrl, String providerIp, String providerApiIp) { try { this.mainUrl.setUrl(new URL(mainUrl)); if (providerIp != null) { @@ -103,21 +116,14 @@ public final class Provider implements Parcelable { } } catch (MalformedURLException e) { e.printStackTrace(); + return; } + setGeoipUrl(geoipUrl); } - public Provider(String mainUrl, String providerIp, String providerApiIp, String caCert, String definition) { - try { - this.mainUrl.setUrl(new URL(mainUrl)); - } catch (MalformedURLException e) { - e.printStackTrace(); - } - if (this.providerIp != null) { - this.providerIp = providerIp; - } - if (this.providerApiIp != null) { - this.providerApiIp = providerApiIp; - } + + public Provider(String mainUrl, String geoipUrl, String providerIp, String providerApiIp, String caCert, String definition) { + this(mainUrl, geoipUrl, providerIp, providerApiIp); if (caCert != null) { this.caCert = caCert; } @@ -240,6 +246,18 @@ public final class Provider implements Parcelable { protected DefaultedURL getApiUrl() { return apiUrl; } + + public DefaultedURL getGeoipUrl() { + return geoipUrl; + } + + public void setGeoipUrl(String url) { + try { + this.geoipUrl.setUrl(new URL(url)); + } catch (MalformedURLException e) { + this.geoipUrl = new DefaultedURL(); + } + } protected String getApiUrlWithVersion() { return getApiUrlString() + "/" + getApiVersion(); @@ -262,6 +280,10 @@ public final class Provider implements Parcelable { return definition != null && definition.length() > 0; } + public boolean hasGeoIpJson() { + return geoIpJson != null && geoIpJson.length() > 0; + } + public String getCaCert() { return caCert; @@ -321,56 +343,17 @@ public final class Provider implements Parcelable { parcel.writeString(getMainUrlString()); parcel.writeString(getProviderIp()); parcel.writeString(getProviderApiIp()); + parcel.writeString(getGeoipUrl().toString()); parcel.writeString(getDefinitionString()); parcel.writeString(getCaCert()); parcel.writeString(getEipServiceJsonString()); + parcel.writeString(getGeoIpJsonString()); parcel.writeString(getPrivateKey()); parcel.writeString(getVpnCertificate()); parcel.writeLong(lastEipServiceUpdate); + parcel.writeLong(lastGeoIpUpdate); } - @Override - public boolean equals(Object o) { - if (o instanceof Provider) { - Provider p = (Provider) o; - return p.getDomain().equals(getDomain()) && - definition.toString().equals(p.getDefinition().toString()) && - eipServiceJson.toString().equals(p.getEipServiceJson().toString())&& - mainUrl.equals(p.getMainUrl()) && - providerIp.equals(p.getProviderIp()) && - providerApiIp.equals(p.getProviderApiIp()) && - apiUrl.equals(p.getApiUrl()) && - certificatePin.equals(p.getCertificatePin()) && - certificatePinEncoding.equals(p.getCertificatePinEncoding()) && - caCert.equals(p.getCaCert()) && - apiVersion.equals(p.getApiVersion()) && - privateKey.equals(p.getPrivateKey()) && - vpnCertificate.equals(p.getVpnCertificate()) && - allowAnonymous == p.allowsAnonymous() && - allowRegistered == p.allowsRegistered() && - lastEipServiceUpdate == p.getLastEipServiceUpdate(); - } else return false; - } - - public JSONObject toJson() { - JSONObject json = new JSONObject(); - try { - json.put(Provider.MAIN_URL, mainUrl); - } catch (JSONException e) { - e.printStackTrace(); - } - return json; - } - - @Override - public int hashCode() { - return getDomain().hashCode(); - } - - @Override - public String toString() { - return new Gson().toJson(this); - } //TODO: write a test for marshalling! private Provider(Parcel in) { @@ -386,6 +369,10 @@ public final class Provider implements Parcelable { } tmpString = in.readString(); if (!tmpString.isEmpty()) { + geoipUrl.setUrl(new URL(tmpString)); + } + tmpString = in.readString(); + if (!tmpString.isEmpty()) { definition = new JSONObject((tmpString)); parseDefinition(definition); } @@ -399,6 +386,10 @@ public final class Provider implements Parcelable { } tmpString = in.readString(); if (!tmpString.isEmpty()) { + this.setGeoIpJson(new JSONObject(tmpString)); + } + tmpString = in.readString(); + if (!tmpString.isEmpty()) { this.setPrivateKey(tmpString); } tmpString = in.readString(); @@ -406,11 +397,57 @@ public final class Provider implements Parcelable { this.setVpnCertificate(tmpString); } this.lastEipServiceUpdate = in.readLong(); + this.lastGeoIpUpdate = in.readLong(); } catch (MalformedURLException | JSONException e) { e.printStackTrace(); } } + + @Override + public boolean equals(Object o) { + if (o instanceof Provider) { + Provider p = (Provider) o; + return p.getDomain().equals(getDomain()) && + definition.toString().equals(p.getDefinition().toString()) && + eipServiceJson.toString().equals(p.getEipServiceJsonString()) && + geoIpJson.toString().equals(p.getGeoIpJsonString()) && + providerIp.equals(p.getProviderIp()) && + providerApiIp.equals(p.getProviderApiIp()) && + apiUrl.equals(p.getApiUrl()) && + geoipUrl.equals(p.getGeoipUrl()) && + certificatePin.equals(p.getCertificatePin()) && + certificatePinEncoding.equals(p.getCertificatePinEncoding()) && + caCert.equals(p.getCaCert()) && + apiVersion.equals(p.getApiVersion()) && + privateKey.equals(p.getPrivateKey()) && + vpnCertificate.equals(p.getVpnCertificate()) && + allowAnonymous == p.allowsAnonymous() && + allowRegistered == p.allowsRegistered(); + } else return false; + } + + + public JSONObject toJson() { + JSONObject json = new JSONObject(); + try { + json.put(Provider.MAIN_URL, mainUrl); + } catch (JSONException e) { + e.printStackTrace(); + } + return json; + } + + @Override + public int hashCode() { + return getDomain().hashCode(); + } + + @Override + public String toString() { + return new Gson().toJson(this); + } + private boolean parseDefinition(JSONObject definition) { try { String pin = definition.getString(CA_CERT_FINGERPRINT); @@ -442,14 +479,20 @@ public final class Provider implements Parcelable { lastEipServiceUpdate = timestamp; } - public long getLastEipServiceUpdate() { - return lastEipServiceUpdate; - } - public boolean shouldUpdateEipServiceJson() { return System.currentTimeMillis() - lastEipServiceUpdate >= EIP_SERVICE_TIMEOUT; } + + public void setLastGeoIpUpdate(long timestamp) { + lastGeoIpUpdate = timestamp; + } + + public boolean shouldUpdateGeoIpJson() { + return System.currentTimeMillis() - lastGeoIpUpdate >= GEOIP_SERVICE_TIMEOUT; + } + + public boolean setEipServiceJson(JSONObject eipServiceJson) { if (eipServiceJson.has(ERRORS)) { return false; @@ -458,16 +501,34 @@ public final class Provider implements Parcelable { return true; } + public boolean setGeoIpJson(JSONObject geoIpJson) { + if (geoIpJson.has(ERRORS)) { + return false; + } + this.geoIpJson = geoIpJson; + return true; + } + public JSONObject getEipServiceJson() { return eipServiceJson; } + public JSONObject getGeoIpJson() { + return geoIpJson; + } + + public String getGeoIpJsonString() { + return geoIpJson.toString(); + } + public String getEipServiceJsonString() { return getEipServiceJson().toString(); } + public boolean isDefault() { return getMainUrl().isDefault() && getApiUrl().isDefault() && + getGeoipUrl().isDefault() && certificatePin.isEmpty() && certificatePinEncoding.isEmpty() && caCert.isEmpty(); @@ -515,6 +576,7 @@ public final class Provider implements Parcelable { public void reset() { definition = new JSONObject(); eipServiceJson = new JSONObject(); + geoIpJson = new JSONObject(); apiUrl = new DefaultedURL(); certificatePin = ""; certificatePinEncoding = ""; @@ -524,5 +586,7 @@ public final class Provider implements Parcelable { vpnCertificate = ""; allowRegistered = false; allowAnonymous = false; + lastGeoIpUpdate = 0L; + lastEipServiceUpdate = 0L; } } diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderAPI.java b/app/src/main/java/se/leap/bitmaskclient/ProviderAPI.java index 9d34b38f..767e6a78 100644 --- a/app/src/main/java/se/leap/bitmaskclient/ProviderAPI.java +++ b/app/src/main/java/se/leap/bitmaskclient/ProviderAPI.java @@ -24,6 +24,7 @@ import android.content.SharedPreferences; import androidx.annotation.NonNull; import androidx.core.app.JobIntentService; import androidx.localbroadcastmanager.content.LocalBroadcastManager; + import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; /** @@ -48,6 +49,7 @@ public class ProviderAPI extends JobIntentService implements ProviderApiManagerB TAG = ProviderAPI.class.getSimpleName(), SET_UP_PROVIDER = "setUpProvider", UPDATE_PROVIDER_DETAILS = "updateProviderDetails", + DOWNLOAD_GEOIP_JSON = "downloadGeoIpJson", SIGN_UP = "srpRegister", LOG_IN = "srpAuth", LOG_OUT = "logOut", @@ -77,7 +79,9 @@ public class ProviderAPI extends JobIntentService implements ProviderApiManagerB CORRECTLY_DOWNLOADED_EIP_SERVICE = 13, INCORRECTLY_DOWNLOADED_EIP_SERVICE = 14, CORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE = 15, - INCORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE = 16; + INCORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE = 16, + CORRECTLY_DOWNLOADED_GEOIP_JSON = 17, + INCORRECTLY_DOWNLOADED_GEOIP_JSON = 18; ProviderApiManager providerApiManager; @@ -99,11 +103,12 @@ public class ProviderAPI extends JobIntentService implements ProviderApiManagerB */ static void enqueueWork(Context context, Intent work) { try { - enqueueWork(context, ProviderAPI.class, JOB_ID, work); + ProviderAPI.enqueueWork(context, ProviderAPI.class, JOB_ID, work); } catch (IllegalStateException e) { e.printStackTrace(); } } + @Override protected void onHandleWork(@NonNull Intent command) { providerApiManager.handleIntent(command); diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderAPICommand.java b/app/src/main/java/se/leap/bitmaskclient/ProviderAPICommand.java index f3122376..9a0731cd 100644 --- a/app/src/main/java/se/leap/bitmaskclient/ProviderAPICommand.java +++ b/app/src/main/java/se/leap/bitmaskclient/ProviderAPICommand.java @@ -4,11 +4,13 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.ResultReceiver; +import android.util.Log; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public class ProviderAPICommand { + private static final String TAG = ProviderAPICommand.class.getSimpleName(); private Context context; private String action; @@ -60,22 +62,22 @@ public class ProviderAPICommand { return command; } - public static void execute(Context context, String action, Provider provider) { + public static void execute(Context context, String action, @NotNull Provider provider) { ProviderAPICommand command = new ProviderAPICommand(context, action, provider); command.execute(); } - public static void execute(Context context, String action, Bundle parameters, Provider provider) { + public static void execute(Context context, String action, Bundle parameters, @NotNull Provider provider) { ProviderAPICommand command = new ProviderAPICommand(context, action, parameters, provider); command.execute(); } - public static void execute(Context context, String action, Bundle parameters, Provider provider, ResultReceiver resultReceiver) { + public static void execute(Context context, String action, Bundle parameters, @NotNull Provider provider, ResultReceiver resultReceiver) { ProviderAPICommand command = new ProviderAPICommand(context, action, parameters, provider, resultReceiver); command.execute(); } - public static void execute(Context context, String action, Provider provider, ResultReceiver resultReceiver) { + public static void execute(Context context, String action, @NotNull Provider provider, ResultReceiver resultReceiver) { ProviderAPICommand command = new ProviderAPICommand(context, action, provider, resultReceiver); command.execute(); } diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java b/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java index 1fb6bf48..e5ca184f 100644 --- a/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java +++ b/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java @@ -22,11 +22,12 @@ import android.content.SharedPreferences; import android.content.res.Resources; import android.os.Bundle; import android.os.ResultReceiver; -import androidx.annotation.NonNull; import android.util.Base64; import android.util.Log; import android.util.Pair; +import androidx.annotation.NonNull; + import org.json.JSONException; import org.json.JSONObject; @@ -60,17 +61,21 @@ import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_CODE; import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_KEY; import static se.leap.bitmaskclient.Constants.CREDENTIALS_PASSWORD; import static se.leap.bitmaskclient.Constants.CREDENTIALS_USERNAME; +import static se.leap.bitmaskclient.Constants.EIP_ACTION_START; 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.Provider.CA_CERT; +import static se.leap.bitmaskclient.Provider.GEOIP_URL; import static se.leap.bitmaskclient.Provider.PROVIDER_API_IP; import static se.leap.bitmaskclient.Provider.PROVIDER_IP; import static se.leap.bitmaskclient.ProviderAPI.BACKEND_ERROR_KEY; import static se.leap.bitmaskclient.ProviderAPI.BACKEND_ERROR_MESSAGE; import static se.leap.bitmaskclient.ProviderAPI.CORRECTLY_DOWNLOADED_EIP_SERVICE; +import static se.leap.bitmaskclient.ProviderAPI.CORRECTLY_DOWNLOADED_GEOIP_JSON; import static se.leap.bitmaskclient.ProviderAPI.CORRECTLY_DOWNLOADED_VPN_CERTIFICATE; import static se.leap.bitmaskclient.ProviderAPI.CORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.ProviderAPI.DOWNLOAD_GEOIP_JSON; import static se.leap.bitmaskclient.ProviderAPI.DOWNLOAD_SERVICE_JSON; import static se.leap.bitmaskclient.ProviderAPI.DOWNLOAD_VPN_CERTIFICATE; import static se.leap.bitmaskclient.ProviderAPI.ERRORID; @@ -78,6 +83,7 @@ import static se.leap.bitmaskclient.ProviderAPI.ERRORS; import static se.leap.bitmaskclient.ProviderAPI.FAILED_LOGIN; import static se.leap.bitmaskclient.ProviderAPI.FAILED_SIGNUP; import static se.leap.bitmaskclient.ProviderAPI.INCORRECTLY_DOWNLOADED_EIP_SERVICE; +import static se.leap.bitmaskclient.ProviderAPI.INCORRECTLY_DOWNLOADED_GEOIP_JSON; import static se.leap.bitmaskclient.ProviderAPI.INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE; import static se.leap.bitmaskclient.ProviderAPI.INCORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE; import static se.leap.bitmaskclient.ProviderAPI.LOGOUT_FAILED; @@ -143,7 +149,11 @@ public abstract class ProviderApiManagerBase { } public void handleIntent(Intent command) { - final ResultReceiver receiver = command.getParcelableExtra(RECEIVER_KEY); +// Log.d(TAG, "handleIntent was called!"); + ResultReceiver receiver = null; + if (command.getParcelableExtra(RECEIVER_KEY) != null) { + receiver = command.getParcelableExtra(RECEIVER_KEY); + } String action = command.getAction(); Bundle parameters = command.getBundleExtra(PARAMETERS); @@ -167,6 +177,7 @@ public abstract class ProviderApiManagerBase { Bundle task = new Bundle(); result = setUpProvider(provider, task); if (result.getBoolean(BROADCAST_RESULT_KEY)) { + getGeoIPJson(provider); sendToReceiverOrBroadcast(receiver, PROVIDER_OK, result, provider); } else { sendToReceiverOrBroadcast(receiver, PROVIDER_NOK, result, provider); @@ -177,6 +188,7 @@ public abstract class ProviderApiManagerBase { ProviderObservable.getInstance().setProviderForDns(provider); result = setUpProvider(provider, parameters); if (result.getBoolean(BROADCAST_RESULT_KEY)) { + getGeoIPJson(provider); sendToReceiverOrBroadcast(receiver, PROVIDER_OK, result, provider); } else { sendToReceiverOrBroadcast(receiver, PROVIDER_NOK, result, provider); @@ -245,6 +257,20 @@ public abstract class ProviderApiManagerBase { } } break; + + case DOWNLOAD_GEOIP_JSON: + if (!provider.getGeoipUrl().isDefault()) { + boolean startEIP = parameters.getBoolean(EIP_ACTION_START); + ProviderObservable.getInstance().setProviderForDns(provider); + result = getGeoIPJson(provider); + result.putBoolean(EIP_ACTION_START, startEIP); + if (result.getBoolean(BROADCAST_RESULT_KEY)) { + sendToReceiverOrBroadcast(receiver, CORRECTLY_DOWNLOADED_GEOIP_JSON, result, provider); + } else { + sendToReceiverOrBroadcast(receiver, INCORRECTLY_DOWNLOADED_GEOIP_JSON, result, provider); + } + ProviderObservable.getInstance().setProviderForDns(null); + } } } @@ -664,6 +690,15 @@ public abstract class ProviderApiManagerBase { protected abstract Bundle updateVpnCertificate(Provider provider); + /** + * Fetches the Geo ip Json, containing a list of gateways sorted by distance from the users current location + * + * @param provider + * @return + */ + protected abstract Bundle getGeoIPJson(Provider provider); + + protected boolean isValidJson(String jsonString) { try { new JSONObject(jsonString); @@ -708,6 +743,7 @@ public abstract class ProviderApiManagerBase { provider.setVpnCertificate(getPersistedVPNCertificate(providerDomain)); provider.setProviderApiIp(getPersistedProviderApiIp(providerDomain)); provider.setProviderIp(getPersistedProviderIp(providerDomain)); + provider.setGeoipUrl(getPersistedGeoIp(providerDomain)); } } @@ -816,6 +852,10 @@ public abstract class ProviderApiManagerBase { return getFromPersistedProvider(PROVIDER_IP, providerDomain, preferences); } + protected String getPersistedGeoIp(String providerDomain) { + return getFromPersistedProvider(GEOIP_URL, providerDomain, preferences); + } + protected boolean hasUpdatedProviderDetails(String domain) { return preferences.contains(Provider.KEY + "." + domain) && preferences.contains(CA_CERT + "." + domain); } diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderListBaseActivity.java b/app/src/main/java/se/leap/bitmaskclient/ProviderListBaseActivity.java index 7c45c921..a7d0f916 100644 --- a/app/src/main/java/se/leap/bitmaskclient/ProviderListBaseActivity.java +++ b/app/src/main/java/se/leap/bitmaskclient/ProviderListBaseActivity.java @@ -106,7 +106,7 @@ public abstract class ProviderListBaseActivity extends ProviderSetupBaseActivity } public void showAndSelectProvider(String newURL) { - provider = new Provider(newURL, "", ""); + provider = new Provider(newURL, null, null); autoSelectProvider(); } diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderManager.java b/app/src/main/java/se/leap/bitmaskclient/ProviderManager.java index 6d074a7a..c23ad270 100644 --- a/app/src/main/java/se/leap/bitmaskclient/ProviderManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/ProviderManager.java @@ -20,6 +20,7 @@ import java.util.Iterator; import java.util.List; import java.util.Set; +import static se.leap.bitmaskclient.Provider.GEOIP_URL; import static se.leap.bitmaskclient.Provider.MAIN_URL; import static se.leap.bitmaskclient.Provider.PROVIDER_API_IP; import static se.leap.bitmaskclient.Provider.PROVIDER_IP; @@ -90,18 +91,20 @@ public class ProviderManager implements AdapteeCollection<Provider> { String providerApiIp = null; String certificate = null; String providerDefinition = null; + String geoipUrl = null; try { String provider = file.substring(0, file.length() - ".url".length()); InputStream providerFile = assetsManager.open(directory + "/" + file); mainUrl = extractKeyFromInputStream(providerFile, MAIN_URL); providerIp = extractKeyFromInputStream(providerFile, PROVIDER_IP); providerApiIp = extractKeyFromInputStream(providerFile, PROVIDER_API_IP); + geoipUrl = extractKeyFromInputStream(providerFile, GEOIP_URL); certificate = loadInputStreamAsString(assetsManager.open(provider + EXT_PEM)); providerDefinition = loadInputStreamAsString(assetsManager.open(provider + EXT_JSON)); } catch (IOException e) { e.printStackTrace(); } - providers.add(new Provider(mainUrl, providerIp, providerApiIp, certificate, providerDefinition)); + providers.add(new Provider(mainUrl, geoipUrl, providerIp, providerApiIp, certificate, providerDefinition)); } return providers; diff --git a/app/src/main/java/se/leap/bitmaskclient/StartActivity.java b/app/src/main/java/se/leap/bitmaskclient/StartActivity.java index b6ace6db..dd4878f3 100644 --- a/app/src/main/java/se/leap/bitmaskclient/StartActivity.java +++ b/app/src/main/java/se/leap/bitmaskclient/StartActivity.java @@ -21,9 +21,10 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.os.Bundle; +import android.util.Log; + import androidx.annotation.IntDef; import androidx.annotation.Nullable; -import android.util.Log; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -40,8 +41,6 @@ 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.utils.ConfigHelper.isDefaultBitmask; -import static se.leap.bitmaskclient.utils.PreferenceHelper.getSavedProviderFromSharedPreferences; -import static se.leap.bitmaskclient.utils.PreferenceHelper.providerInSharedPreferences; import static se.leap.bitmaskclient.utils.PreferenceHelper.storeProviderInPreferences; /** @@ -162,9 +161,9 @@ public class StartActivity extends Activity{ } private void prepareEIP() { - boolean providerExists = providerInSharedPreferences(preferences); + boolean providerExists = ProviderObservable.getInstance().getCurrentProvider() != null; if (providerExists) { - Provider provider = getSavedProviderFromSharedPreferences(preferences); + Provider provider = ProviderObservable.getInstance().getCurrentProvider(); if(!provider.isConfigured()) { configureLeapProvider(); } else { 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 34c88ef9..e0c96ebb 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java @@ -226,7 +226,7 @@ public final class EIP extends JobIntentService implements Observer { return; } - GatewaysManager gatewaysManager = gatewaysFromPreferences(); + GatewaysManager gatewaysManager = new GatewaysManager(getApplicationContext()); if (gatewaysManager.isEmpty()) { setErrorResult(result, warning_client_parsing_error_gateways, null); tellToReceiverOrBroadcast(this, EIP_ACTION_START, RESULT_CANCELED, result); @@ -248,7 +248,7 @@ public final class EIP extends JobIntentService implements Observer { * The {@link OnBootReceiver} will care if there is no profile. */ private void startEIPAlwaysOnVpn() { - GatewaysManager gatewaysManager = gatewaysFromPreferences(); + GatewaysManager gatewaysManager = new GatewaysManager(getApplicationContext()); Gateway gateway = gatewaysManager.select(0); if (!launchActiveGateway(gateway, 0)) { @@ -310,15 +310,6 @@ public final class EIP extends JobIntentService implements Observer { } /** - * read eipServiceJson from preferences and parse Gateways - * - * @return GatewaysManager - */ - private GatewaysManager gatewaysFromPreferences() { - return new GatewaysManager(getApplicationContext(), preferences); - } - - /** * read VPN certificate from preferences and check it * broadcast result */ 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 66c9fe84..f3eea415 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java @@ -34,6 +34,7 @@ import de.blinkt.openvpn.core.ConfigParser; import de.blinkt.openvpn.core.connection.Connection; import se.leap.bitmaskclient.utils.PreferenceHelper; +import static se.leap.bitmaskclient.Constants.HOST; import static se.leap.bitmaskclient.Constants.IP_ADDRESS; import static se.leap.bitmaskclient.Constants.LOCATION; import static se.leap.bitmaskclient.Constants.LOCATIONS; @@ -114,6 +115,10 @@ public class Gateway { 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); @@ -152,6 +157,10 @@ public class Gateway { return vpnProfiles.get(transportType); } + public boolean suppoortsTransport(Connection.TransportType transportType) { + return vpnProfiles.get(transportType) != null; + } + public int getTimezone() { return timezone; } 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 fbe1861a..0515a35e 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java @@ -17,7 +17,6 @@ package se.leap.bitmaskclient.eip; import android.content.Context; -import android.content.SharedPreferences; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; @@ -30,6 +29,7 @@ import java.io.IOException; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.LinkedHashMap; +import java.util.List; import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.core.ConfigParser; @@ -37,7 +37,6 @@ import de.blinkt.openvpn.core.VpnStatus; import de.blinkt.openvpn.core.connection.Connection; import se.leap.bitmaskclient.Provider; import se.leap.bitmaskclient.ProviderObservable; -import se.leap.bitmaskclient.utils.PreferenceHelper; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OPENVPN; @@ -54,15 +53,9 @@ public class GatewaysManager { private static final String TAG = GatewaysManager.class.getSimpleName(); private Context context; - private SharedPreferences preferences; private LinkedHashMap<String, Gateway> gateways = new LinkedHashMap<>(); private Type listType = new TypeToken<ArrayList<Gateway>>() {}.getType(); - - public GatewaysManager(Context context, SharedPreferences preferences) { - this.preferences = preferences; - this.context = context; - configureFromPreferences(); - } + private ArrayList<Gateway> presortedList = new ArrayList<>(); public GatewaysManager(Context context) { configureFromCurrentProvider(); @@ -75,13 +68,42 @@ public class GatewaysManager { */ public Gateway select(int nClosest) { Connection.TransportType transportType = getUsePluggableTransports(context) ? OBFS4 : OPENVPN; - GatewaySelector gatewaySelector = new GatewaySelector(new ArrayList<>(gateways.values())); + + if (presortedList.size() > 0) { + return getGatewayFromPresortedList(nClosest, transportType); + } + + return getGatewayFromTimezoneCalculation(nClosest, transportType); + } + + + private Gateway getGatewayFromTimezoneCalculation(int nClosest, Connection.TransportType transportType) { + List<Gateway> list = new ArrayList<>(gateways.values()); + GatewaySelector gatewaySelector = new GatewaySelector(list); Gateway gateway; - while ((gateway = gatewaySelector.select(nClosest)) != null) { - if (gateway.getProfile(transportType) != null) { - return gateway; + int found = 0; + int i = 0; + while ((gateway = gatewaySelector.select(i)) != null) { + if (gateway.suppoortsTransport(transportType)) { + if (found == nClosest) { + return gateway; + } + found++; + } + i++; + } + return null; + } + + private Gateway getGatewayFromPresortedList(int nClosest, Connection.TransportType transportType) { + int found = 0; + for (Gateway gateway : presortedList) { + if (gateway.suppoortsTransport(transportType)) { + if (found == nClosest) { + return gateway; + } + found++; } - nClosest++; } return null; } @@ -92,15 +114,41 @@ public class GatewaysManager { * @return position of the gateway owning to the profile */ public int getPosition(VpnProfile profile) { - Connection.TransportType transportType = getUsePluggableTransports(context) ? OBFS4 : OPENVPN; + if (presortedList.size() > 0) { + return getPositionFromPresortedList(profile); + } + + return getPositionFromTimezoneCalculatedList(profile); + } + + private int getPositionFromPresortedList(VpnProfile profile) { + Connection.TransportType transportType = profile.mUsePluggableTransports ? OBFS4 : OPENVPN; + int nClosest = 0; + for (Gateway gateway : presortedList) { + if (gateway.suppoortsTransport(transportType)) { + if (profile.equals(gateway.getProfile(transportType))) { + return nClosest; + } + nClosest++; + } + } + return -1; + } + + private int getPositionFromTimezoneCalculatedList(VpnProfile profile) { + Connection.TransportType transportType = profile.mUsePluggableTransports ? OBFS4 : OPENVPN; GatewaySelector gatewaySelector = new GatewaySelector(new ArrayList<>(gateways.values())); Gateway gateway; int nClosest = 0; - while ((gateway = gatewaySelector.select(nClosest)) != null) { - if (profile.equals(gateway.getProfile(transportType))) { - return nClosest; + int i = 0; + while ((gateway = gatewaySelector.select(i)) != null) { + if (gateway.suppoortsTransport(transportType)) { + if (profile.equals(gateway.getProfile(transportType))) { + return nClosest; + } + nClosest++; } - nClosest++; + i++; } return -1; } @@ -126,41 +174,56 @@ public class GatewaysManager { } /** - * parse gateways from eipDefinition - * @param eipDefinition eipServiceJson + * parse gateways from Provider's eip service + * @param provider */ - private void fromEipServiceJson(JSONObject eipDefinition, JSONObject secrets) { - JSONArray gatewaysDefined = new JSONArray(); - try { - gatewaysDefined = eipDefinition.getJSONArray(GATEWAYS); - } catch (Exception e) { - e.printStackTrace(); - } - - for (int i = 0; i < gatewaysDefined.length(); i++) { - try { - JSONObject gw = gatewaysDefined.getJSONObject(i); - Gateway aux = new Gateway(eipDefinition, secrets, gw, this.context); - if (gateways.get(aux.getRemoteIP()) == null) { - addGateway(aux); - } - } catch (JSONException | ConfigParser.ConfigParseError | IOException e) { - e.printStackTrace(); - VpnStatus.logError("Unable to parse gateway config!"); - } - } + 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(); + } + + for (int i = 0; i < gatewaysDefined.length(); i++) { + try { + JSONObject gw = gatewaysDefined.getJSONObject(i); + Gateway aux = new Gateway(eipDefinition, secrets, gw, this.context); + if (gateways.get(aux.getHost()) == null) { + addGateway(aux); + } + } catch (JSONException | ConfigParser.ConfigParseError | IOException e) { + e.printStackTrace(); + VpnStatus.logError("Unable to parse gateway config!"); + } + } + } catch (NullPointerException npe) { + npe.printStackTrace(); + } } - private JSONObject secretsConfigurationFromPreferences() { - JSONObject result = new JSONObject(); - try { - result.put(Provider.CA_CERT, preferences.getString(Provider.CA_CERT, "")); - result.put(PROVIDER_PRIVATE_KEY, preferences.getString(PROVIDER_PRIVATE_KEY, "")); - result.put(PROVIDER_VPN_CERTIFICATE, preferences.getString(PROVIDER_VPN_CERTIFICATE, "")); - } catch (JSONException e) { - e.printStackTrace(); - } - return result; + private void parseGatewaysFromGeoIpServiceJson(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) { + npe.printStackTrace(); + } } private JSONObject secretsConfigurationFromCurrentProvider() { @@ -177,31 +240,13 @@ public class GatewaysManager { return result; } - - void clearGateways() { - gateways.clear(); - } - private void addGateway(Gateway gateway) { - gateways.put(gateway.getRemoteIP(), gateway); - } - - /** - * read EipServiceJson from preferences and set gateways - */ - private void configureFromPreferences() { - fromEipServiceJson( - PreferenceHelper.getEipDefinitionFromPreferences(preferences), secretsConfigurationFromPreferences() - ); + gateways.put(gateway.getHost(), gateway); } private void configureFromCurrentProvider() { - try { - JSONObject json = ProviderObservable.getInstance().getCurrentProvider().getEipServiceJson(); - fromEipServiceJson(json, secretsConfigurationFromCurrentProvider()); - } catch (NullPointerException npe) { - npe.printStackTrace(); - } - + Provider provider = ProviderObservable.getInstance().getCurrentProvider(); + parseDefaultGateways(provider); + parseGatewaysFromGeoIpServiceJson(provider); } } 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 669b7f8e..cb2aeb26 100644 --- a/app/src/main/java/se/leap/bitmaskclient/utils/PreferenceHelper.java +++ b/app/src/main/java/se/leap/bitmaskclient/utils/PreferenceHelper.java @@ -37,9 +37,6 @@ import static se.leap.bitmaskclient.Constants.USE_PLUGGABLE_TRANSPORTS; */ public class PreferenceHelper { - public static boolean providerInSharedPreferences(@NonNull SharedPreferences preferences) { - return preferences.getBoolean(PROVIDER_CONFIGURED, false); - } public static Provider getSavedProviderFromSharedPreferences(@NonNull SharedPreferences preferences) { Provider provider = new Provider(); @@ -47,6 +44,7 @@ public class PreferenceHelper { provider.setMainUrl(new URL(preferences.getString(Provider.MAIN_URL, ""))); provider.setProviderIp(preferences.getString(Provider.PROVIDER_IP, "")); provider.setProviderApiIp(preferences.getString(Provider.PROVIDER_API_IP, "")); + provider.setGeoipUrl(preferences.getString(Provider.GEOIP_URL, "")); provider.define(new JSONObject(preferences.getString(Provider.KEY, ""))); provider.setCaCert(preferences.getString(Provider.CA_CERT, "")); provider.setVpnCertificate(preferences.getString(PROVIDER_VPN_CERTIFICATE, "")); @@ -68,6 +66,7 @@ public class PreferenceHelper { public static void storeProviderInPreferences(SharedPreferences preferences, Provider provider) { preferences.edit().putBoolean(PROVIDER_CONFIGURED, true). putString(Provider.PROVIDER_IP, provider.getProviderIp()). + putString(Provider.GEOIP_URL, provider.getGeoipUrl().toString()). putString(Provider.PROVIDER_API_IP, provider.getProviderApiIp()). putString(Provider.MAIN_URL, provider.getMainUrlString()). putString(Provider.KEY, provider.getDefinitionString()). @@ -82,6 +81,7 @@ public class PreferenceHelper { putString(Provider.PROVIDER_IP + "." + providerDomain, provider.getProviderIp()). putString(Provider.PROVIDER_API_IP + "." + providerDomain, provider.getProviderApiIp()). putString(Provider.MAIN_URL + "." + providerDomain, provider.getMainUrlString()). + putString(Provider.GEOIP_URL + "." + providerDomain, provider.getGeoipUrl().toString()). putString(Provider.KEY + "." + providerDomain, provider.getDefinitionString()). putString(Provider.CA_CERT + "." + providerDomain, provider.getCaCert()). putString(PROVIDER_EIP_DEFINITION + "." + providerDomain, provider.getEipServiceJsonString()). @@ -114,6 +114,7 @@ public class PreferenceHelper { remove(Provider.PROVIDER_IP + "." + providerDomain). remove(Provider.PROVIDER_API_IP + "." + providerDomain). remove(Provider.MAIN_URL + "." + providerDomain). + remove(Provider.GEOIP_URL + "." + providerDomain). remove(PROVIDER_EIP_DEFINITION + "." + providerDomain). remove(PROVIDER_PRIVATE_KEY + "." + providerDomain). remove(PROVIDER_VPN_CERTIFICATE + "." + providerDomain). |