summaryrefslogtreecommitdiff
path: root/app/src
diff options
context:
space:
mode:
authorcyberta <cyberta@riseup.net>2022-05-20 11:19:12 +0000
committercyberta <cyberta@riseup.net>2022-05-20 11:19:12 +0000
commit39cf5b1c41af8060af836b93fa9616bbb9c6a60b (patch)
tree2ef16c29798b8712bfec1b923a7fb3e2422952bb /app/src
parent18d3cc0ccbaf3bb9e797fcd542d180669b92dbd8 (diff)
parent0ebc7e3a9e84f598a0221fe64f51d0e7906ac377 (diff)
Merge branch 'vpn_cert_update' into 'master'
improve VPN cert update Closes #9087 See merge request leap/bitmask_android!184
Diffstat (limited to 'app/src')
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java19
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java108
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java124
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java10
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderAPI.java3
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java123
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/tor/ClientTransportPlugin.java7
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/tor/TorServiceCommand.java10
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/tor/TorStatusObservable.java12
-rw-r--r--app/src/production/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java47
-rw-r--r--app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java100
-rw-r--r--app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/NoErrorBackendResponseAPIv4.java3
-rw-r--r--app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/TorFallbackBackendResponse.java8
-rw-r--r--app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java71
-rw-r--r--app/src/test/resources/v4/riseup.net.cert54
15 files changed, 512 insertions, 187 deletions
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java b/app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java
index d01edf5d..8cb12652 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java
@@ -49,6 +49,8 @@ import se.leap.bitmaskclient.eip.EIP;
import se.leap.bitmaskclient.eip.EipCommand;
import se.leap.bitmaskclient.eip.EipSetupListener;
import se.leap.bitmaskclient.eip.EipSetupObserver;
+import se.leap.bitmaskclient.eip.EipStatus;
+import se.leap.bitmaskclient.providersetup.ProviderAPI;
import se.leap.bitmaskclient.providersetup.activities.LoginActivity;
import se.leap.bitmaskclient.providersetup.models.LeapSRPSession;
@@ -74,6 +76,9 @@ import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORID;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORS;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.INCORRECTLY_DOWNLOADED_EIP_SERVICE;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.INCORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.TOR_EXCEPTION;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.TOR_TIMEOUT;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.UPDATE_INVALID_VPN_CERTIFICATE;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.USER_MESSAGE;
@@ -303,6 +308,19 @@ public class MainActivity extends AppCompatActivity implements EipSetupListener,
askUserToLogIn(getString(vpn_certificate_user_message));
}
break;
+ case TOR_TIMEOUT:
+ case TOR_EXCEPTION:
+ try {
+ Bundle resultData = intent.getParcelableExtra(BROADCAST_RESULT_KEY);
+ JSONObject jsonObject = new JSONObject(resultData.getString(ProviderAPI.ERRORS));
+ String initialAction = jsonObject.optString(ProviderAPI.INITIAL_ACTION);
+ if (UPDATE_INVALID_VPN_CERTIFICATE.equals(initialAction)) {
+ showMainActivityErrorDialog(getString(downloading_vpn_certificate_failed));
+ }
+ } catch (Exception e) {
+ //ignore
+ }
+ break;
}
}
@@ -327,7 +345,6 @@ public class MainActivity extends AppCompatActivity implements EipSetupListener,
JSONObject errorJson = new JSONObject(reasonToFail);
newFragment = MainActivityErrorDialog.newInstance(provider, errorJson);
} catch (JSONException e) {
- e.printStackTrace();
newFragment = MainActivityErrorDialog.newInstance(provider, reasonToFail);
}
newFragment.show(fragmentTransaction, MainActivityErrorDialog.TAG);
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java
index dfa45614..a61680a1 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java
@@ -16,6 +16,26 @@
*/
package se.leap.bitmaskclient.base.fragments;
+import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_NONETWORK;
+import static se.leap.bitmaskclient.base.models.Constants.ASK_TO_CANCEL_VPN;
+import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START;
+import static se.leap.bitmaskclient.base.models.Constants.EIP_EARLY_ROUTES;
+import static se.leap.bitmaskclient.base.models.Constants.EIP_RESTART_ON_BOOT;
+import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY;
+import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_CONFIGURE_LEAP;
+import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_LOG_IN;
+import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_SWITCH_PROVIDER;
+import static se.leap.bitmaskclient.base.models.Constants.SHARED_PREFERENCES;
+import static se.leap.bitmaskclient.base.utils.ConfigHelper.isDefaultBitmask;
+import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getPreferredCity;
+import static se.leap.bitmaskclient.base.utils.ViewHelper.convertDimensionToPx;
+import static se.leap.bitmaskclient.eip.EipSetupObserver.gatewayOrder;
+import static se.leap.bitmaskclient.eip.EipSetupObserver.reconnectingWithDifferentGateway;
+import static se.leap.bitmaskclient.eip.GatewaysManager.Load.UNKNOWN;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.DOWNLOAD_GEOIP_JSON;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.UPDATE_INVALID_VPN_CERTIFICATE;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.USER_MESSAGE;
+
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
@@ -27,18 +47,19 @@ import android.graphics.ColorMatrixColorFilter;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Vibrator;
+import android.text.Spannable;
+import android.text.SpannableString;
import android.text.TextUtils;
+import android.text.style.RelativeSizeSpan;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
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;
@@ -74,29 +95,8 @@ import se.leap.bitmaskclient.providersetup.ProviderListActivity;
import se.leap.bitmaskclient.providersetup.activities.CustomProviderSetupActivity;
import se.leap.bitmaskclient.providersetup.activities.LoginActivity;
import se.leap.bitmaskclient.providersetup.models.LeapSRPSession;
-
-import static android.view.View.INVISIBLE;
-import static android.view.View.VISIBLE;
-import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_NONETWORK;
-import static se.leap.bitmaskclient.R.string.vpn_certificate_user_message;
-import static se.leap.bitmaskclient.base.models.Constants.ASK_TO_CANCEL_VPN;
-import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START;
-import static se.leap.bitmaskclient.base.models.Constants.EIP_EARLY_ROUTES;
-import static se.leap.bitmaskclient.base.models.Constants.EIP_RESTART_ON_BOOT;
-import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY;
-import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_CONFIGURE_LEAP;
-import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_LOG_IN;
-import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_SWITCH_PROVIDER;
-import static se.leap.bitmaskclient.base.models.Constants.SHARED_PREFERENCES;
-import static se.leap.bitmaskclient.base.utils.ConfigHelper.isDefaultBitmask;
-import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getPreferredCity;
-import static se.leap.bitmaskclient.base.utils.ViewHelper.convertDimensionToPx;
-import static se.leap.bitmaskclient.eip.EipSetupObserver.gatewayOrder;
-import static se.leap.bitmaskclient.eip.EipSetupObserver.reconnectingWithDifferentGateway;
-import static se.leap.bitmaskclient.eip.GatewaysManager.Load.UNKNOWN;
-import static se.leap.bitmaskclient.providersetup.ProviderAPI.DOWNLOAD_GEOIP_JSON;
-import static se.leap.bitmaskclient.providersetup.ProviderAPI.UPDATE_INVALID_VPN_CERTIFICATE;
-import static se.leap.bitmaskclient.providersetup.ProviderAPI.USER_MESSAGE;
+import se.leap.bitmaskclient.tor.TorServiceCommand;
+import se.leap.bitmaskclient.tor.TorStatusObservable;
public class EipFragment extends Fragment implements Observer {
@@ -123,6 +123,8 @@ public class EipFragment extends Fragment implements Observer {
private Unbinder unbinder;
private EipStatus eipStatus;
+ private ProviderObservable providerObservable;
+ private TorStatusObservable torStatusObservable;
private GatewaysManager gatewaysManager;
@@ -172,6 +174,8 @@ public class EipFragment extends Fragment implements Observer {
super.onCreate(savedInstanceState);
openVpnConnection = new EipFragmentServiceConnection();
eipStatus = EipStatus.getInstance();
+ providerObservable = ProviderObservable.getInstance();
+ torStatusObservable = TorStatusObservable.getInstance();
Activity activity = getActivity();
if (activity != null) {
preferences = getActivity().getSharedPreferences(SHARED_PREFERENCES, Context.MODE_PRIVATE);
@@ -187,6 +191,8 @@ public class EipFragment extends Fragment implements Observer {
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
eipStatus.addObserver(this);
+ torStatusObservable.addObserver(this);
+ providerObservable.addObserver(this);
View view = inflater.inflate(R.layout.f_eip, container, false);
unbinder = ButterKnife.bind(this, view);
@@ -264,6 +270,8 @@ public class EipFragment extends Fragment implements Observer {
public void onDestroyView() {
super.onDestroyView();
eipStatus.deleteObserver(this);
+ providerObservable.deleteObserver(this);
+ torStatusObservable.deleteObserver(this);
unbinder.unbind();
}
@@ -277,7 +285,7 @@ public class EipFragment extends Fragment implements Observer {
}
void handleIcon() {
- if (isOpenVpnRunningWithoutNetwork() || eipStatus.isConnected() || eipStatus.isConnecting())
+ if (isOpenVpnRunningWithoutNetwork() || eipStatus.isConnected() || eipStatus.isConnecting() || eipStatus.isUpdatingVpnCert())
handleSwitchOff();
else
handleSwitchOn();
@@ -313,7 +321,7 @@ public class EipFragment extends Fragment implements Observer {
}
private void handleSwitchOff() {
- if (isOpenVpnRunningWithoutNetwork() || eipStatus.isConnecting()) {
+ if (isOpenVpnRunningWithoutNetwork() || eipStatus.isConnecting() || eipStatus.isUpdatingVpnCert()) {
askPendingStartCancellation();
} else if (eipStatus.isConnected()) {
askToStopEIP();
@@ -364,7 +372,14 @@ public class EipFragment extends Fragment implements Observer {
showPendingStartCancellation = true;
alertDialog = alertBuilder.setTitle(activity.getString(R.string.eip_cancel_connect_title))
.setMessage(activity.getString(R.string.eip_cancel_connect_text))
- .setPositiveButton((android.R.string.yes), (dialog, which) -> stopEipIfPossible())
+ .setPositiveButton((android.R.string.yes), (dialog, which) -> {
+ Context context = getContext();
+ if (context != null && eipStatus.isUpdatingVpnCert() &&
+ TorStatusObservable.isRunning()) {
+ TorServiceCommand.stopTorServiceAsync(context.getApplicationContext());
+ }
+ stopEipIfPossible();
+ })
.setNegativeButton(activity.getString(android.R.string.no), (dialog, which) -> {
}).setOnDismissListener(dialog -> showPendingStartCancellation = false).show();
} catch (IllegalStateException e) {
@@ -397,14 +412,20 @@ public class EipFragment extends Fragment implements Observer {
public void update(Observable observable, Object data) {
if (observable instanceof EipStatus) {
eipStatus = (EipStatus) observable;
- Activity activity = getActivity();
- if (activity != null) {
- activity.runOnUiThread(this::handleNewState);
- } else {
- Log.e("EipFragment", "activity is null");
- }
+ handleNewStateOnMain();
} else if (observable instanceof ProviderObservable) {
provider = ((ProviderObservable) observable).getCurrentProvider();
+ } else if (observable instanceof TorStatusObservable && EipStatus.getInstance().isUpdatingVpnCert()) {
+ handleNewStateOnMain();
+ }
+ }
+
+ private void handleNewStateOnMain() {
+ Activity activity = getActivity();
+ if (activity != null) {
+ activity.runOnUiThread(this::handleNewState);
+ } else {
+ Log.e("EipFragment", "activity is null");
}
}
@@ -416,7 +437,23 @@ public class EipFragment extends Fragment implements Observer {
}
Log.d(TAG, "eip fragment eipStatus state: " + eipStatus.getState() + " - level: " + eipStatus.getLevel() + " - is reconnecting: " + eipStatus.isReconnecting());
- if (eipStatus.isConnecting()) {
+ if (eipStatus.isUpdatingVpnCert()) {
+ setMainButtonEnabled(true);
+ showConnectionTransitionLayout(true);
+ locationButton.setText(getString(R.string.eip_status_start_pending));
+ locationButton.setLocationLoad(UNKNOWN);
+ locationButton.showBridgeIndicator(false);
+ locationButton.showRecommendedIndicator(false);
+ mainDescription.setText(null);
+ String torStatus = TorStatusObservable.getStringForCurrentStatus(getContext());
+ if (!TextUtils.isEmpty(torStatus)) {
+ Spannable spannable = new SpannableString(torStatus);
+ spannable.setSpan(new RelativeSizeSpan(0.75f), 0, spannable.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ subDescription.setText(TextUtils.concat(getString(R.string.updating_certificate_message) + "\n", spannable));
+ } else {
+ subDescription.setText(getString(R.string.updating_certificate_message));
+ }
+ } else if (eipStatus.isConnecting()) {
setMainButtonEnabled(true);
showConnectionTransitionLayout(true);
locationButton.setText(getString(R.string.eip_status_start_pending));
@@ -574,6 +611,7 @@ public class EipFragment extends Fragment implements Observer {
}
private void updateInvalidVpnCertificate() {
+ eipStatus.setUpdatingVpnCert(true);
ProviderAPICommand.execute(getContext(), UPDATE_INVALID_VPN_CERTIFICATE, provider);
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java b/app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java
index 813b8b62..9d67340e 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java
@@ -17,6 +17,38 @@
package se.leap.bitmaskclient.eip;
+import static android.app.Activity.RESULT_CANCELED;
+import static android.content.Intent.CATEGORY_DEFAULT;
+import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_CONNECTING_NO_SERVER_REPLY_YET;
+import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_NOTCONNECTED;
+import static se.leap.bitmaskclient.appUpdate.DownloadServiceCommand.CHECK_VERSION_FILE;
+import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_EIP_EVENT;
+import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_GATEWAY_SETUP_OBSERVER_EVENT;
+import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_PROVIDER_API_EVENT;
+import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_CODE;
+import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY;
+import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_LAUNCH_VPN;
+import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_PREPARE_VPN;
+import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START;
+import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START_ALWAYS_ON_VPN;
+import static se.leap.bitmaskclient.base.models.Constants.EIP_EARLY_ROUTES;
+import static se.leap.bitmaskclient.base.models.Constants.EIP_N_CLOSEST_GATEWAY;
+import static se.leap.bitmaskclient.base.models.Constants.EIP_REQUEST;
+import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY;
+import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PROFILE;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.CORRECTLY_DOWNLOADED_EIP_SERVICE;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.CORRECTLY_DOWNLOADED_GEOIP_JSON;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.CORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.INCORRECTLY_DOWNLOADED_EIP_SERVICE;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.INCORRECTLY_DOWNLOADED_GEOIP_JSON;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.INCORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.PROVIDER_NOK;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.PROVIDER_OK;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.TOR_EXCEPTION;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.TOR_TIMEOUT;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.UPDATE_INVALID_VPN_CERTIFICATE;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -45,39 +77,8 @@ import se.leap.bitmaskclient.base.utils.PreferenceHelper;
import se.leap.bitmaskclient.providersetup.ProviderAPI;
import se.leap.bitmaskclient.providersetup.ProviderAPICommand;
import se.leap.bitmaskclient.tor.TorServiceCommand;
-import se.leap.bitmaskclient.tor.TorServiceConnection;
import se.leap.bitmaskclient.tor.TorStatusObservable;
-import static android.app.Activity.RESULT_CANCELED;
-import static android.content.Intent.CATEGORY_DEFAULT;
-import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_CONNECTING_NO_SERVER_REPLY_YET;
-import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_NOTCONNECTED;
-import static se.leap.bitmaskclient.appUpdate.DownloadServiceCommand.CHECK_VERSION_FILE;
-import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_EIP_EVENT;
-import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_GATEWAY_SETUP_OBSERVER_EVENT;
-import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_PROVIDER_API_EVENT;
-import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_CODE;
-import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY;
-import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_LAUNCH_VPN;
-import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_PREPARE_VPN;
-import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START;
-import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START_ALWAYS_ON_VPN;
-import static se.leap.bitmaskclient.base.models.Constants.EIP_EARLY_ROUTES;
-import static se.leap.bitmaskclient.base.models.Constants.EIP_N_CLOSEST_GATEWAY;
-import static se.leap.bitmaskclient.base.models.Constants.EIP_REQUEST;
-import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY;
-import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PROFILE;
-import static se.leap.bitmaskclient.providersetup.ProviderAPI.CORRECTLY_DOWNLOADED_EIP_SERVICE;
-import static se.leap.bitmaskclient.providersetup.ProviderAPI.CORRECTLY_DOWNLOADED_GEOIP_JSON;
-import static se.leap.bitmaskclient.providersetup.ProviderAPI.CORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE;
-import static se.leap.bitmaskclient.providersetup.ProviderAPI.INCORRECTLY_DOWNLOADED_EIP_SERVICE;
-import static se.leap.bitmaskclient.providersetup.ProviderAPI.INCORRECTLY_DOWNLOADED_GEOIP_JSON;
-import static se.leap.bitmaskclient.providersetup.ProviderAPI.INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE;
-import static se.leap.bitmaskclient.providersetup.ProviderAPI.INCORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE;
-import static se.leap.bitmaskclient.providersetup.ProviderAPI.PROVIDER_NOK;
-import static se.leap.bitmaskclient.providersetup.ProviderAPI.PROVIDER_OK;
-import static se.leap.bitmaskclient.tor.TorStatusObservable.TorStatus.OFF;
-
/**
* Created by cyberta on 05.12.18.
*/
@@ -86,7 +87,7 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta
private static final String TAG = EipSetupObserver.class.getName();
private static final int UPDATE_CHECK_TIMEOUT = 1000*60*60*24*7;
- private Context context;
+ private final Context appContext;
private VpnProfile setupVpnProfile;
private String observedProfileFromVpnStatus;
AtomicInteger reconnectTry = new AtomicInteger();
@@ -97,7 +98,7 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta
private static EipSetupObserver instance;
private EipSetupObserver(Context context, SharedPreferences preferences) {
- this.context = context;
+ this.appContext = context.getApplicationContext();
this.preferences = preferences;
IntentFilter updateIntentFilter = new IntentFilter(BROADCAST_GATEWAY_SETUP_OBSERVER_EVENT);
updateIntentFilter.addAction(BROADCAST_EIP_EVENT);
@@ -105,7 +106,7 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta
updateIntentFilter.addAction(TorService.ACTION_STATUS);
updateIntentFilter.addAction(TorService.ACTION_ERROR);
updateIntentFilter.addCategory(CATEGORY_DEFAULT);
- LocalBroadcastManager.getInstance(context.getApplicationContext()).registerReceiver(this, updateIntentFilter);
+ LocalBroadcastManager.getInstance(context).registerReceiver(this, updateIntentFilter);
instance = this;
VpnStatus.addLogListener(this);
}
@@ -174,7 +175,7 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta
Log.d(TAG, "handle Tor status event: " + status);
Integer bootstrap = intent.getIntExtra(TorService.EXTRA_STATUS_DETAIL_BOOTSTRAP, -1);
String logKey = intent.getStringExtra(TorService.EXTRA_STATUS_DETAIL_LOGKEY);
- TorStatusObservable.updateState(context, status, bootstrap, logKey);
+ TorStatusObservable.updateState(appContext, status, bootstrap, logKey);
}
@@ -193,14 +194,18 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta
ProviderObservable.getInstance().updateProvider(provider);
PreferenceHelper.storeProviderInPreferences(preferences, provider);
if (EipStatus.getInstance().isDisconnected()) {
- EipCommand.startVPN(context, false);
+ EipCommand.startVPN(appContext, false);
}
break;
case CORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE:
provider = resultData.getParcelable(PROVIDER_KEY);
ProviderObservable.getInstance().updateProvider(provider);
PreferenceHelper.storeProviderInPreferences(preferences, provider);
- EipCommand.startVPN(context, false);
+ EipCommand.startVPN(appContext, false);
+ EipStatus.getInstance().setUpdatingVpnCert(false);
+ if (TorStatusObservable.isRunning()) {
+ TorServiceCommand.stopTorServiceAsync(appContext);
+ }
break;
case CORRECTLY_DOWNLOADED_GEOIP_JSON:
provider = resultData.getParcelable(PROVIDER_KEY);
@@ -211,18 +216,35 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta
case INCORRECTLY_DOWNLOADED_GEOIP_JSON:
maybeStartEipService(resultData);
break;
- case PROVIDER_NOK:
case INCORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE:
+ EipStatus.getInstance().setUpdatingVpnCert(false);
+ if (TorStatusObservable.isRunning()) {
+ TorServiceCommand.stopTorServiceAsync(appContext);
+ }
+ break;
+ case PROVIDER_NOK:
case INCORRECTLY_DOWNLOADED_EIP_SERVICE:
case INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE:
- if (TorStatusObservable.getStatus() != OFF) {
- TorServiceCommand.stopTorServiceAsync(context);
+ if (TorStatusObservable.isRunning()) {
+ TorServiceCommand.stopTorServiceAsync(appContext);
}
Log.d(TAG, "PROVIDER NOK - FETCH FAILED");
break;
case PROVIDER_OK:
Log.d(TAG, "PROVIDER OK - FETCH SUCCESSFUL");
break;
+ case TOR_TIMEOUT:
+ case TOR_EXCEPTION:
+ try {
+ JSONObject jsonObject = new JSONObject(resultData.getString(ProviderAPI.ERRORS));
+ String initialAction = jsonObject.optString(ProviderAPI.INITIAL_ACTION);
+ if (UPDATE_INVALID_VPN_CERTIFICATE.equals(initialAction)) {
+ EipStatus.getInstance().setUpdatingVpnCert(false);
+ }
+ } catch (Exception e) {
+ //ignore
+ }
+ break;
default:
break;
}
@@ -235,7 +257,7 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta
private void maybeStartEipService(Bundle resultData) {
if (resultData.getBoolean(EIP_ACTION_START)) {
boolean earlyRoutes = resultData.getBoolean(EIP_EARLY_ROUTES);
- EipCommand.startVPN(context, earlyRoutes);
+ EipCommand.startVPN(appContext, earlyRoutes);
}
}
@@ -262,14 +284,14 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta
switch (error) {
case NO_MORE_GATEWAYS:
finishGatewaySetup(false);
- EipCommand.startBlockingVPN(context);
+ EipCommand.startBlockingVPN(appContext);
break;
case ERROR_INVALID_PROFILE:
selectNextGateway();
break;
default:
finishGatewaySetup(false);
- EipCommand.stopVPN(context);
+ EipCommand.stopVPN(appContext);
EipStatus.refresh();
}
}
@@ -345,11 +367,11 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta
Provider provider = ProviderObservable.getInstance().getCurrentProvider();
if (setupNClosestGateway.get() > 0 || provider.shouldUpdateEipServiceJson()) {
//setupNClostestGateway > 0: at least one failed gateway -> did the provider change it's gateways?
- ProviderAPICommand.execute(context, ProviderAPI.DOWNLOAD_SERVICE_JSON, provider);
+ ProviderAPICommand.execute(appContext, ProviderAPI.DOWNLOAD_SERVICE_JSON, provider);
}
if (shouldCheckAppUpdate()) {
- DownloadServiceCommand.execute(context, CHECK_VERSION_FILE);
+ DownloadServiceCommand.execute(appContext, CHECK_VERSION_FILE);
}
finishGatewaySetup(false);
} else if ("TCP_CONNECT".equals(state)) {
@@ -358,13 +380,13 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta
}
private boolean shouldCheckAppUpdate() {
- return System.currentTimeMillis() - PreferenceHelper.getLastAppUpdateCheck(context) >= UPDATE_CHECK_TIMEOUT;
+ return System.currentTimeMillis() - PreferenceHelper.getLastAppUpdateCheck(appContext) >= UPDATE_CHECK_TIMEOUT;
}
private void selectNextGateway() {
changingGateway.set(true);
reconnectTry.set(0);
- EipCommand.startVPN(context, false, setupNClosestGateway.get() + 1);
+ EipCommand.startVPN(appContext, false, setupNClosestGateway.get() + 1);
}
private void finishGatewaySetup(boolean changingGateway) {
@@ -374,8 +396,8 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta
observedProfileFromVpnStatus = null;
this.changingGateway.set(changingGateway);
this.reconnectTry.set(0);
- if (TorStatusObservable.getStatus() != OFF) {
- TorServiceCommand.stopTorServiceAsync(context);
+ if (TorStatusObservable.isRunning()) {
+ TorServiceCommand.stopTorServiceAsync(appContext);
}
}
@@ -396,9 +418,9 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta
case SHAPESHIFTER:
VpnProfile profile = VpnStatus.getLastConnectedVpnProfile();
if (profile == null) {
- EipCommand.startVPN(context, false, 0);
+ EipCommand.startVPN(appContext, false, 0);
} else {
- GatewaysManager gatewaysManager = new GatewaysManager(context.getApplicationContext());
+ GatewaysManager gatewaysManager = new GatewaysManager(appContext);
int position = gatewaysManager.getPosition(profile);
setupNClosestGateway.set(position >= 0 ? position : 0);
selectNextGateway();
diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java b/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java
index bc123683..003e396f 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java
@@ -56,6 +56,7 @@ public class EipStatus extends Observable implements VpnStatus.StateListener {
private int lastErrorLine = 0;
private String state, logMessage;
private int localizedResId;
+ private boolean isUpdatingVPNCertificate;
public static EipStatus getInstance() {
if (currentStatus == null) {
@@ -178,6 +179,15 @@ public class EipStatus extends Observable implements VpnStatus.StateListener {
}
}
+ public void setUpdatingVpnCert(boolean isUpdating) {
+ isUpdatingVPNCertificate = isUpdating;
+ refresh();
+ }
+
+ public boolean isUpdatingVpnCert() {
+ return isUpdatingVPNCertificate;
+ }
+
public boolean isConnecting() {
return currentEipLevel == EipLevel.CONNECTING;
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderAPI.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderAPI.java
index 022ad040..da77af2f 100644
--- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderAPI.java
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderAPI.java
@@ -93,7 +93,8 @@ public class ProviderAPI extends JobIntentService implements ProviderApiManagerB
CORRECTLY_DOWNLOADED_GEOIP_JSON = 17,
INCORRECTLY_DOWNLOADED_GEOIP_JSON = 18,
TOR_TIMEOUT = 19,
- MISSING_NETWORK_CONNECTION = 20;
+ MISSING_NETWORK_CONNECTION = 20,
+ TOR_EXCEPTION = 21;
ProviderApiManager providerApiManager;
private volatile TorServiceConnection torServiceConnection;
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java
index 63cf03cf..ecddb9c7 100644
--- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java
@@ -17,57 +17,6 @@
package se.leap.bitmaskclient.providersetup;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.res.Resources;
-import android.os.Bundle;
-import android.os.ResultReceiver;
-import android.util.Base64;
-import android.util.Log;
-import android.util.Pair;
-
-import androidx.annotation.NonNull;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.io.IOException;
-import java.math.BigInteger;
-import java.net.ConnectException;
-import java.net.MalformedURLException;
-import java.net.SocketTimeoutException;
-import java.net.UnknownHostException;
-import java.net.UnknownServiceException;
-import java.security.NoSuchAlgorithmException;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateExpiredException;
-import java.security.cert.CertificateNotYetValidException;
-import java.security.cert.X509Certificate;
-import java.security.interfaces.RSAPrivateKey;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.NoSuchElementException;
-import java.util.concurrent.TimeoutException;
-
-import javax.net.ssl.SSLHandshakeException;
-import javax.net.ssl.SSLPeerUnverifiedException;
-
-import de.blinkt.openvpn.core.VpnStatus;
-import okhttp3.OkHttpClient;
-import se.leap.bitmaskclient.R;
-import se.leap.bitmaskclient.base.models.Constants.CREDENTIAL_ERRORS;
-import se.leap.bitmaskclient.base.models.Provider;
-import se.leap.bitmaskclient.base.models.ProviderObservable;
-import se.leap.bitmaskclient.base.utils.ConfigHelper;
-import se.leap.bitmaskclient.base.utils.PreferenceHelper;
-import se.leap.bitmaskclient.eip.EipStatus;
-import se.leap.bitmaskclient.providersetup.connectivity.OkHttpClientGenerator;
-import se.leap.bitmaskclient.providersetup.models.LeapSRPSession;
-import se.leap.bitmaskclient.providersetup.models.SrpCredentials;
-import se.leap.bitmaskclient.providersetup.models.SrpRegistrationData;
-import se.leap.bitmaskclient.tor.TorStatusObservable;
-
import static se.leap.bitmaskclient.R.string.certificate_error;
import static se.leap.bitmaskclient.R.string.error_io_exception_user_message;
import static se.leap.bitmaskclient.R.string.error_json_exception_user_message;
@@ -128,6 +77,7 @@ import static se.leap.bitmaskclient.providersetup.ProviderAPI.SIGN_UP;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.SUCCESSFUL_LOGIN;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.SUCCESSFUL_LOGOUT;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.SUCCESSFUL_SIGNUP;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.TOR_EXCEPTION;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.TOR_TIMEOUT;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.UPDATE_INVALID_VPN_CERTIFICATE;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.UPDATE_PROVIDER_DETAILS;
@@ -140,6 +90,57 @@ import static se.leap.bitmaskclient.tor.TorStatusObservable.TorStatus.OFF;
import static se.leap.bitmaskclient.tor.TorStatusObservable.TorStatus.ON;
import static se.leap.bitmaskclient.tor.TorStatusObservable.getProxyPort;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+import android.util.Base64;
+import android.util.Log;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.net.ConnectException;
+import java.net.MalformedURLException;
+import java.net.SocketTimeoutException;
+import java.net.UnknownHostException;
+import java.net.UnknownServiceException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.RSAPrivateKey;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.concurrent.TimeoutException;
+
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.SSLPeerUnverifiedException;
+
+import de.blinkt.openvpn.core.VpnStatus;
+import okhttp3.OkHttpClient;
+import se.leap.bitmaskclient.R;
+import se.leap.bitmaskclient.base.models.Constants.CREDENTIAL_ERRORS;
+import se.leap.bitmaskclient.base.models.Provider;
+import se.leap.bitmaskclient.base.models.ProviderObservable;
+import se.leap.bitmaskclient.base.utils.ConfigHelper;
+import se.leap.bitmaskclient.base.utils.PreferenceHelper;
+import se.leap.bitmaskclient.eip.EipStatus;
+import se.leap.bitmaskclient.providersetup.connectivity.OkHttpClientGenerator;
+import se.leap.bitmaskclient.providersetup.models.LeapSRPSession;
+import se.leap.bitmaskclient.providersetup.models.SrpCredentials;
+import se.leap.bitmaskclient.providersetup.models.SrpRegistrationData;
+import se.leap.bitmaskclient.tor.TorStatusObservable;
+
/**
* Implements the logic of the http api calls. The methods of this class needs to be called from
* a background thread.
@@ -200,11 +201,14 @@ public abstract class ProviderApiManagerBase {
}
try {
- if (PreferenceHelper.hasSnowflakePrefs(preferences)) {
+ if (PreferenceHelper.hasSnowflakePrefs(preferences) && !VpnStatus.isVPNActive()) {
startTorProxy();
}
} catch (InterruptedException | IllegalStateException e) {
e.printStackTrace();
+ Bundle result = new Bundle();
+ setErrorResultAction(result, action);
+ sendToReceiverOrBroadcast(receiver, TOR_EXCEPTION, result, provider);
return;
} catch (TimeoutException e) {
serviceCallback.stopTorService();
@@ -369,7 +373,7 @@ public abstract class ProviderApiManagerBase {
private void addErrorMessageToJson(JSONObject jsonObject, String errorMessage, String errorId, String initialAction) {
try {
- jsonObject.put(ERRORS, errorMessage);
+ jsonObject.putOpt(ERRORS, errorMessage);
jsonObject.putOpt(ERRORID, errorId);
jsonObject.putOpt(INITIAL_ACTION, initialAction);
} catch (JSONException e) {
@@ -599,6 +603,10 @@ public abstract class ProviderApiManagerBase {
case INCORRECTLY_DOWNLOADED_GEOIP_JSON:
event = "download menshen service json.";
break;
+ case TOR_TIMEOUT:
+ case TOR_EXCEPTION:
+ event = "start tor for censorship circumvention";
+ break;
default:
break;
}
@@ -959,6 +967,15 @@ public abstract class ProviderApiManagerBase {
return result;
}
+ Bundle setErrorResultAction(Bundle result, String initialAction) {
+ JSONObject errorJson = new JSONObject();
+ addErrorMessageToJson(errorJson, null, null, initialAction);
+ VpnStatus.logWarning("[API] error: " + initialAction + " failed.");
+ result.putString(ERRORS, errorJson.toString());
+ result.putBoolean(BROADCAST_RESULT_KEY, false);
+ return result;
+ }
+
Bundle setErrorResult(Bundle result, int errorMessageId, String errorId) {
return setErrorResult(result, errorMessageId, errorId, null);
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/tor/ClientTransportPlugin.java b/app/src/main/java/se/leap/bitmaskclient/tor/ClientTransportPlugin.java
index 0b481780..59c1290a 100644
--- a/app/src/main/java/se/leap/bitmaskclient/tor/ClientTransportPlugin.java
+++ b/app/src/main/java/se/leap/bitmaskclient/tor/ClientTransportPlugin.java
@@ -113,13 +113,16 @@ public class ClientTransportPlugin implements ClientTransportPluginInterface {
@Override
public void stop() {
IPtProxy.stopSnowflake();
- try {
+ try {
TorStatusObservable.waitUntil(this::isSnowflakeOff, 10);
} catch (InterruptedException | TimeoutException e) {
e.printStackTrace();
}
snowflakePort = -1;
- logFileObserver.stopWatching();
+ if (logFileObserver != null) {
+ logFileObserver.stopWatching();
+ logFileObserver = null;
+ }
}
private boolean isSnowflakeOff() {
diff --git a/app/src/main/java/se/leap/bitmaskclient/tor/TorServiceCommand.java b/app/src/main/java/se/leap/bitmaskclient/tor/TorServiceCommand.java
index 68988b67..b99abb3d 100644
--- a/app/src/main/java/se/leap/bitmaskclient/tor/TorServiceCommand.java
+++ b/app/src/main/java/se/leap/bitmaskclient/tor/TorServiceCommand.java
@@ -42,7 +42,9 @@ public class TorServiceCommand {
public static boolean startTorService(Context context, String action) throws InterruptedException {
Log.d(TAG, "startTorService");
try {
- waitUntil(TorServiceCommand::isNotCancelled, 30);
+ if (TorStatusObservable.isCancelled()) {
+ waitUntil(TorServiceCommand::isNotCancelled, 30);
+ }
} catch (TimeoutException e) {
e.printStackTrace();
}
@@ -79,7 +81,8 @@ public class TorServiceCommand {
@WorkerThread
public static void stopTorService(Context context) {
- if (TorStatusObservable.getStatus() == TorStatusObservable.TorStatus.OFF) {
+ if (TorStatusObservable.getStatus() == TorStatusObservable.TorStatus.STOPPING ||
+ TorStatusObservable.getStatus() == TorStatusObservable.TorStatus.OFF) {
return;
}
TorStatusObservable.markCancelled();
@@ -100,6 +103,9 @@ public class TorServiceCommand {
}
public static void stopTorServiceAsync(Context context) {
+ if (!TorStatusObservable.isRunning()) {
+ return;
+ }
TorStatusObservable.markCancelled();
new Thread(() -> stopTorService(context)).start();
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/tor/TorStatusObservable.java b/app/src/main/java/se/leap/bitmaskclient/tor/TorStatusObservable.java
index c924caee..7eee1a9d 100644
--- a/app/src/main/java/se/leap/bitmaskclient/tor/TorStatusObservable.java
+++ b/app/src/main/java/se/leap/bitmaskclient/tor/TorStatusObservable.java
@@ -178,7 +178,6 @@ public class TorStatusObservable extends Observable {
if (getInstance().status == TorStatus.OFF) {
getInstance().torNotificationManager.cancelNotifications(context);
getInstance().cancelled = false;
- getInstance().port = -1;
} else {
if (logKey != null) {
getInstance().lastTorLog = getStringFor(context, logKey);
@@ -264,6 +263,10 @@ public class TorStatusObservable extends Observable {
}
public static String getStringForCurrentStatus(Context context) {
+ if (context == null) {
+ return "";
+ }
+
switch (getInstance().status) {
case ON:
return context.getString(R.string.tor_started);
@@ -280,6 +283,8 @@ public class TorStatusObservable extends Observable {
public static void markCancelled() {
if (!getInstance().cancelled) {
getInstance().cancelled = true;
+ getInstance().port = -1;
+ getInstance().setChanged();
getInstance().notifyObservers();
}
}
@@ -287,4 +292,9 @@ public class TorStatusObservable extends Observable {
public static boolean isCancelled() {
return getInstance().cancelled;
}
+
+ public static boolean isRunning() {
+ return !TorStatusObservable.isCancelled() &&
+ TorStatusObservable.getStatus() != TorStatusObservable.TorStatus.OFF;
+ }
}
diff --git a/app/src/production/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java b/app/src/production/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java
index d1de62a0..3ec04f32 100644
--- a/app/src/production/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java
+++ b/app/src/production/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java
@@ -25,7 +25,6 @@ import android.util.Pair;
import org.json.JSONException;
import org.json.JSONObject;
-import java.io.IOException;
import java.net.URL;
import java.util.List;
import java.util.concurrent.TimeoutException;
@@ -199,29 +198,22 @@ public class ProviderApiManager extends ProviderApiManagerBase {
@Override
protected Bundle updateVpnCertificate(Provider provider) {
Bundle result = new Bundle();
- try {
- URL newCertStringUrl = new URL(provider.getApiUrlWithVersion() + "/" + PROVIDER_VPN_CERTIFICATE);
-
- String certString = downloadWithProviderCA(provider.getCaCert(), newCertStringUrl.toString());
- if (DEBUG_MODE) {
- VpnStatus.logDebug("[API] VPN CERT: " + certString);
- }
- if (ConfigHelper.checkErroneousDownload(certString)) {
- if (certString == null || certString.isEmpty()) {
- // probably 204
- setErrorResult(result, error_io_exception_user_message, null);
- } else {
- setErrorResult(result, certString);
- return result;
- }
+ String certString = downloadFromVersionedApiUrlWithProviderCA("/" + PROVIDER_VPN_CERTIFICATE, provider);
+ if (DEBUG_MODE) {
+ VpnStatus.logDebug("[API] VPN CERT: " + certString);
+ }
+ if (ConfigHelper.checkErroneousDownload(certString)) {
+ if (TorStatusObservable.isRunning()) {
+ setErrorResult(result, downloading_vpn_certificate_failed, null);
+ } else if (certString == null || certString.isEmpty() ){
+ // probably 204
+ setErrorResult(result, error_io_exception_user_message, null);
+ } else {
+ setErrorResult(result, certString);
}
- return loadCertificate(provider, certString);
- } catch (IOException e) {
- // TODO try to get Provider Json
- setErrorResult(result, downloading_vpn_certificate_failed, null);
- e.printStackTrace();
+ return result;
}
- return result;
+ return loadCertificate(provider, certString);
}
/**
@@ -352,6 +344,17 @@ public class ProviderApiManager extends ProviderApiManagerBase {
return downloadFromUrlWithProviderCA(urlString, provider);
}
+ /**
+ * Tries to download the contents of $base_url/$version/$path using not commercially validated CA certificate from chosen provider.
+ *
+ * @return an empty string if it fails, the response body if not.
+ */
+ private String downloadFromVersionedApiUrlWithProviderCA(String path, Provider provider) {
+ String baseUrl = provider.getApiUrlWithVersion();
+ String urlString = baseUrl + path;
+ return downloadFromUrlWithProviderCA(urlString, provider);
+ }
+
private String downloadFromUrlWithProviderCA(String urlString, Provider provider) {
return downloadFromUrlWithProviderCA(urlString, provider, true);
}
diff --git a/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java b/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java
index c3ee344f..cb1e1f73 100644
--- a/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java
+++ b/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java
@@ -54,6 +54,7 @@ import se.leap.bitmaskclient.testutils.MockSharedPreferences;
import se.leap.bitmaskclient.tor.TorStatusObservable;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import static org.mockito.Mockito.when;
import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY;
import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START;
@@ -61,12 +62,16 @@ import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY;
import static se.leap.bitmaskclient.base.models.Constants.USE_BRIDGES;
import static se.leap.bitmaskclient.base.models.Constants.USE_SNOWFLAKE;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.CORRECTLY_DOWNLOADED_GEOIP_JSON;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.CORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORS;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.INCORRECTLY_DOWNLOADED_GEOIP_JSON;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.INCORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.MISSING_NETWORK_CONNECTION;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.PARAMETERS;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.PROVIDER_NOK;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.PROVIDER_OK;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.TOR_EXCEPTION;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.TOR_TIMEOUT;
import static se.leap.bitmaskclient.testutils.BackendMockResponses.BackendMockProvider.TestBackendErrorCase.ERROR_CASE_FETCH_EIP_SERVICE_CERTIFICATE_INVALID;
import static se.leap.bitmaskclient.testutils.BackendMockResponses.BackendMockProvider.TestBackendErrorCase.ERROR_CASE_MICONFIGURED_PROVIDER;
@@ -76,6 +81,7 @@ import static se.leap.bitmaskclient.testutils.BackendMockResponses.BackendMockPr
import static se.leap.bitmaskclient.testutils.BackendMockResponses.BackendMockProvider.TestBackendErrorCase.ERROR_GEOIP_SERVICE_IS_DOWN_TOR_FALLBACK;
import static se.leap.bitmaskclient.testutils.BackendMockResponses.BackendMockProvider.TestBackendErrorCase.NO_ERROR;
import static se.leap.bitmaskclient.testutils.BackendMockResponses.BackendMockProvider.TestBackendErrorCase.NO_ERROR_API_V4;
+import static se.leap.bitmaskclient.testutils.MockHelper.mockBase64;
import static se.leap.bitmaskclient.testutils.MockHelper.mockBundle;
import static se.leap.bitmaskclient.testutils.MockHelper.mockClientGenerator;
import static se.leap.bitmaskclient.testutils.MockHelper.mockConfigHelper;
@@ -98,7 +104,7 @@ import static se.leap.bitmaskclient.testutils.TestSetupHelper.getProvider;
*/
@RunWith(PowerMockRunner.class)
-@PrepareForTest({ProviderApiManager.class, TextUtils.class, ConfigHelper.class, ProviderApiConnector.class, PreferenceHelper.class, TorStatusObservable.class})
+@PrepareForTest({ProviderApiManager.class, TextUtils.class, ConfigHelper.class, ProviderApiConnector.class, PreferenceHelper.class, TorStatusObservable.class, android.util.Base64.class})
public class ProviderApiManagerTest {
private SharedPreferences mockPreferences;
@@ -790,6 +796,98 @@ public class ProviderApiManagerTest {
}
@Test
+ public void test_handleIntentUpdateVPNCertificate_TorBridgesPreferencesNotConfigured_TorStartedAndSuccess() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, TimeoutException, InterruptedException {
+ Provider provider = getConfiguredProviderAPIv4();
+
+ mockConfigHelper(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
+ mockBase64();
+ mockProviderApiConnector(ERROR_DNS_RESUOLUTION_TOR_FALLBACK);
+
+ providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback());
+
+ Intent providerApiCommand = mockIntent();
+ providerApiCommand.putExtra(PROVIDER_KEY, provider);
+ providerApiCommand.setAction(ProviderAPI.UPDATE_INVALID_VPN_CERTIFICATE);
+ providerApiCommand.putExtra(ProviderAPI.RECEIVER_KEY, mockResultReceiver(CORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE));
+
+ mockTorStatusObservable(null);
+
+ providerApiManager.handleIntent(providerApiCommand);
+ assertNotEquals(-1, TorStatusObservable.getProxyPort());
+ }
+
+ @Test
+ public void test_handleIntentUpdateVPNCertificate_TorBridgesPreferencesFalse_TorNotStartedAndFailure() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, TimeoutException, InterruptedException {
+ Provider provider = getConfiguredProviderAPIv4();
+
+ mockConfigHelper(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
+ mockBase64();
+ mockProviderApiConnector(ERROR_DNS_RESUOLUTION_TOR_FALLBACK);
+ mockPreferences.edit().putBoolean(USE_BRIDGES, false).putBoolean(USE_SNOWFLAKE, false).commit();
+
+ providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback());
+
+ Intent providerApiCommand = mockIntent();
+ providerApiCommand.putExtra(PROVIDER_KEY, provider);
+ providerApiCommand.setAction(ProviderAPI.UPDATE_INVALID_VPN_CERTIFICATE);
+ providerApiCommand.putExtra(ProviderAPI.RECEIVER_KEY, mockResultReceiver(INCORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE));
+
+ mockTorStatusObservable(new TimeoutException("This timeout exception is never thrown"));
+
+ providerApiManager.handleIntent(providerApiCommand);
+ assertEquals(-1, TorStatusObservable.getProxyPort());
+ }
+
+ @Test
+ public void test_handleIntentUpdateVPNCertificate_TorBridgesPreferencesTrue_TorStartedAndSuccess() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, TimeoutException, InterruptedException {
+ Provider provider = getConfiguredProviderAPIv4();
+
+ mockConfigHelper(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
+ mockBase64();
+ mockProviderApiConnector(NO_ERROR_API_V4);
+ mockPreferences.edit().putBoolean(USE_BRIDGES, true).putBoolean(USE_SNOWFLAKE, true).commit();
+
+ providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback());
+
+ Intent providerApiCommand = mockIntent();
+ providerApiCommand.putExtra(PROVIDER_KEY, provider);
+ providerApiCommand.setAction(ProviderAPI.UPDATE_INVALID_VPN_CERTIFICATE);
+ providerApiCommand.putExtra(ProviderAPI.RECEIVER_KEY, mockResultReceiver(CORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE));
+
+ mockTorStatusObservable(null);
+
+ providerApiManager.handleIntent(providerApiCommand);
+ assertNotEquals(-1, TorStatusObservable.getProxyPort());
+ }
+
+ @Test
+ public void test_handleIntentUpdateVPNCertificate_TorBridgesPreferencesTrue_TorException_Failure() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, TimeoutException, InterruptedException {
+ Provider provider = getConfiguredProviderAPIv4();
+
+ mockConfigHelper(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
+ mockBase64();
+ mockProviderApiConnector(NO_ERROR_API_V4);
+ mockPreferences.edit().putBoolean(USE_BRIDGES, true).putBoolean(USE_SNOWFLAKE, true).commit();
+
+ providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback());
+
+ Bundle expectedResult = mockBundle();
+ expectedResult.putBoolean(BROADCAST_RESULT_KEY, false);
+ expectedResult.putString(ERRORS, "{\"initalAction\":\"ProviderAPI.UPDATE_INVALID_VPN_CERTIFICATE\"}");
+ expectedResult.putParcelable(PROVIDER_KEY, provider);
+
+ Intent providerApiCommand = mockIntent();
+ providerApiCommand.putExtra(PROVIDER_KEY, provider);
+ providerApiCommand.setAction(ProviderAPI.UPDATE_INVALID_VPN_CERTIFICATE);
+ providerApiCommand.putExtra(ProviderAPI.RECEIVER_KEY, mockResultReceiver(TOR_EXCEPTION, expectedResult));
+
+ mockTorStatusObservable(new InterruptedException("Tor thread was interrupted."));
+
+ providerApiManager.handleIntent(providerApiCommand);
+ assertEquals(-1, TorStatusObservable.getProxyPort());
+ }
+
+ @Test
public void test_handleIntentSetupProvider_TorBridgesPreferencesEnabledTimeout_TimeoutError() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, TimeoutException, InterruptedException {
Provider provider = getConfiguredProviderAPIv4();
diff --git a/app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/NoErrorBackendResponseAPIv4.java b/app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/NoErrorBackendResponseAPIv4.java
index 3b77834f..b9dc26b1 100644
--- a/app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/NoErrorBackendResponseAPIv4.java
+++ b/app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/NoErrorBackendResponseAPIv4.java
@@ -53,6 +53,9 @@ public class NoErrorBackendResponseAPIv4 extends BaseBackendResponse {
} else if (url.contains(":9001/json")) {
// download geoip json, containing a sorted list of gateways
return getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.geoip.json"));
+ } else if (url.contains("/cert")) {
+ // download vpn key and cert
+ return getInputAsString(getClass().getClassLoader().getResourceAsStream("v4/riseup.net.cert"));
} else if (url.contains("/users.json")) {
//create new user
//TODO: implement me
diff --git a/app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/TorFallbackBackendResponse.java b/app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/TorFallbackBackendResponse.java
index dc12ae89..c3779a21 100644
--- a/app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/TorFallbackBackendResponse.java
+++ b/app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/TorFallbackBackendResponse.java
@@ -49,6 +49,14 @@ public class TorFallbackBackendResponse extends BaseBackendResponse {
}
// download geoip json, containing a sorted list of gateways
return getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.geoip.json"));
+ } else if (url.contains("/cert")) {
+ if (requestAttempt == 0) {
+ requestAttempt++;
+ throw new UnknownHostException("DNS blocked by censor ;)");
+ }
+ // download vpn certificate for authentication
+ return getInputAsString(getClass().getClassLoader().getResourceAsStream("v4/riseup.net.cert"));
+
}
return null;
diff --git a/app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java b/app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java
index 8d76fd41..61d42f58 100644
--- a/app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java
+++ b/app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java
@@ -1,5 +1,23 @@
package se.leap.bitmaskclient.testutils;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.powermock.api.mockito.PowerMockito.mockStatic;
+import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PRIVATE_KEY;
+import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_VPN_CERTIFICATE;
+import static se.leap.bitmaskclient.base.utils.FileHelper.createFile;
+import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getEipDefinitionFromPreferences;
+import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getFromPersistedProvider;
+
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -18,16 +36,21 @@ import org.json.JSONException;
import org.json.JSONObject;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
+import org.powermock.api.mockito.PowerMockito;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
+import java.math.BigInteger;
import java.net.UnknownHostException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
+import java.security.interfaces.RSAPrivateKey;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Base64;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -51,24 +74,6 @@ import se.leap.bitmaskclient.testutils.BackendMockResponses.BackendMockProvider;
import se.leap.bitmaskclient.testutils.matchers.BundleMatcher;
import se.leap.bitmaskclient.tor.TorStatusObservable;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.powermock.api.mockito.PowerMockito.mockStatic;
-import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PRIVATE_KEY;
-import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_VPN_CERTIFICATE;
-import static se.leap.bitmaskclient.base.utils.FileHelper.createFile;
-import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getEipDefinitionFromPreferences;
-import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getFromPersistedProvider;
-
/**
* Created by cyberta on 29.01.18.
*/
@@ -408,6 +413,10 @@ public class MockHelper {
when(createFile(any(File.class), anyString())).thenReturn(mockedFile);
}
+ public static void mockBase64() {
+ mockStatic(android.util.Base64.class);
+ when(android.util.Base64.encodeToString(any(), anyInt())).thenAnswer(invocation -> Arrays.toString(Base64.getEncoder().encode((byte[]) invocation.getArguments()[0])));
+ }
public static void mockConfigHelper(String mockedFingerprint) throws CertificateEncodingException, NoSuchAlgorithmException {
mockStatic(ConfigHelper.class);
when(ConfigHelper.getFingerprintFromCertificate(any(X509Certificate.class), anyString())).thenReturn(mockedFingerprint);
@@ -417,6 +426,32 @@ public class MockHelper {
when(ConfigHelper.timezoneDistance(anyInt(), anyInt())).thenCallRealMethod();
when(ConfigHelper.isIPv4(anyString())).thenCallRealMethod();
when(ConfigHelper.isDefaultBitmask()).thenReturn(true);
+ when(ConfigHelper.parseRsaKeyFromString(anyString())).thenReturn(new RSAPrivateKey() {
+ @Override
+ public BigInteger getPrivateExponent() {
+ return BigInteger.TEN;
+ }
+
+ @Override
+ public String getAlgorithm() {
+ return "RSA";
+ }
+
+ @Override
+ public String getFormat() {
+ return null;
+ }
+
+ @Override
+ public byte[] getEncoded() {
+ return new byte[0];
+ }
+
+ @Override
+ public BigInteger getModulus() {
+ return BigInteger.ONE;
+ }
+ });
}
public static void mockPreferenceHelper(final Provider providerFromPrefs) {
diff --git a/app/src/test/resources/v4/riseup.net.cert b/app/src/test/resources/v4/riseup.net.cert
new file mode 100644
index 00000000..b689c033
--- /dev/null
+++ b/app/src/test/resources/v4/riseup.net.cert
@@ -0,0 +1,54 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAvXvOGBJeNZfzvkLgEbunA29j/zrxGtDxQwHQ2oAElpQyZfzF
+smHza6Q4o1audAH/hrLF4Z3I3jRbGsuh81pq7GCMsaL1/C2xWlOOHH6+zljdLHvr
+/COwVOuKIUR19yIIozcBaCp3mvDCMAH+cF0uLPw/cfs4Le0gaptN3n3f5jFnFxEs
+3fDXnAgUP7QPvxm5Wl5MM2HBNKcwPNLA29VrgRtJ6OJWtK3raB1S1D3Iv9OMAYtA
+5dCpRMYbV0gBcxc0YRsmlLy8s3ZKdKF3aqiZfN4R/dLzqmysIAVSgfvp8vZpRZDz
+uStE+fiTH4MEFpUTQ6bwbjV9hh6+M5UO1aPYUwIDAQABAoIBAGPgDhHCHMZDAccX
+mOO/9Zhp7ltpxgxMdd7L5jpFoCV+l9IKGmqcFqJ2PSRbXDjplLZ7JLJ3aJk3H45Q
+J10OG63cdkxriR0TOJhT0mRSqmA5ltsLtqeAaEFapcRDQaqx1buyEpvFRqX0oWaM
+poCznNM5YnfB4yrSAnQuyCyuTIYYO5n4baOWR97vbpfwPtznyt2vsVhsP6hj2+4u
+Jv4cgDtvap/yXpCcQdfoAKavDRKDd9Ig+6ZirfNZv8nMShQqQj3WWzDY9FxWb2kG
+BjKRURS5CD/4FRTY4CIQ9sGvhTvcY7WxgQ6uESuM2YBTEFygEtB9tyIMdPDTz9qg
+43eqA4ECgYEA6YbVqnZPxGK2TTqJw0yV/UPDhXy0gViOX+eMwQpfQySd1wqBabeh
+hS0ntMMcgZHNQ9IsBZDSkfVFA51+x97kQeoF5kvM3PYLBBvP6NBBaR/rlofkz/4z
+4CIEFoFMUFplF4tyJOm+QQx7A8njG8oIJ0MI8zEjglJk4//yHF8AfU8CgYEAz7fs
+uGKevL7Ha6n9qiQ08cLunWPP0CEUobf4afgXMVBl7R9hDiq80YImK0wuh8prOt0E
+TWtL9i8ToEvHaubuXT1kgoOZoeyIAEYpi1aF/a/+AZ562Ts8jeWwa6ZJVqLfG0lR
+gdrQvJgDgqsF4B6oynEjrvtanW48g+ROLmXwG70CgYBAF7yey1f7O2hza8SRsHxe
+BXItOdvEwExbMA7mkHUy1WLouT5piHexOIJ0TzSMrzqaCZ4BbQ0N+DYX1usL6jXV
+jWhPG7C/WFwPpZ57dGTveE5Ng0CegVM1icB7eMM8LoMeYixSy0BnVAiTMp69asaw
+F+rl7C+lvf1owj9t3/kfawKBgQCxvqFB5qIOwPHEn2IBBZqIhlXJOG/LmYMeH17i
+zviJqlKN5hwXE1sfrE8dHcNzTzMS262i0f3eW8pfkHjEcXfnMXGgfRwqA00dbux9
+3zwpKUAiAor8+EOI6NNeSpzXFef0YXjttWCJAUt/tPkCHzowgUAXq96OeJYwBl0g
+NvqPwQKBgDpV8An1JZAjcUNc9IP6GvubYpsTwyEjSHYteEdBtahLrh0G4gjLNgg7
+PvhHxni5UvjeRiNPwb3cWVYL7v7YVTRyDvRmZ7FrLWJ0q3P0S8Ww4ar+ZWw8sQhQ
+zaoY20NYypde+K9RRamnUw6GvoS+o7pF0c+hXaup/jGAwszcLJr3
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIEmzCCAoOgAwIBAgIQfhZuQF/HQWNj4bY/2E0R1zANBgkqhkiG9w0BAQsFADB1
+MRgwFgYDVQQKDA9SaXNldXAgTmV0d29ya3MxGzAZBgNVBAsMEmh0dHBzOi8vcmlz
+ZXVwLm5ldDE8MDoGA1UEAwwzUmlzZXVwIE5ldHdvcmtzIFJvb3QgQ0EgKGNsaWVu
+dCBjZXJ0aWZpY2F0ZXMgb25seSEpMB4XDTIyMDQxNzAwMDAwMFoXDTIyMDcxNzAw
+MDAwMFowLTErMCkGA1UEAwwiVU5MSU1JVEVEN2ZsNjdmcmZ6NnF3aWs2eG95emht
+bzV2eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL17zhgSXjWX875C
+4BG7pwNvY/868RrQ8UMB0NqABJaUMmX8xbJh82ukOKNWrnQB/4ayxeGdyN40WxrL
+ofNaauxgjLGi9fwtsVpTjhx+vs5Y3Sx76/wjsFTriiFEdfciCKM3AWgqd5rwwjAB
+/nBdLiz8P3H7OC3tIGqbTd593+YxZxcRLN3w15wIFD+0D78ZuVpeTDNhwTSnMDzS
+wNvVa4EbSejiVrSt62gdUtQ9yL/TjAGLQOXQqUTGG1dIAXMXNGEbJpS8vLN2SnSh
+d2qomXzeEf3S86psrCAFUoH76fL2aUWQ87krRPn4kx+DBBaVE0Om8G41fYYevjOV
+DtWj2FMCAwEAAaNvMG0wHQYDVR0OBBYEFASbEXiXvCseE07xGzDXVzZqRWYkMAsG
+A1UdDwQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAjAJBgNVHRMEAjAAMB8GA1Ud
+IwQYMBaAFBf0G9XlKgEBTWuiXTYKKQmWZYBGMA0GCSqGSIb3DQEBCwUAA4ICAQA6
+P4z8srFEo/LhaAdOTHjvi4t8OVR7WuD6Tit68UabZpbFwPmXVbzYATKmNepYjXqR
+TDW416M7QWja5nyFSXaLzk95osE1IxqcqgZ5vKHLPFE5J8eBtkVoFN6+9F6olx6s
+8UYbewU1Cm2rN2Y1xXNxAFz9wNqLjGdhuExdfxE+4/Y2uh40yC2rhiJLiSrsdfCU
+QsBQJy7Z76dAtbp2087aqikHmiqm/lkIKXE3jNgb8JCT3oJutedBA/CknDyM/7N/
+x83mS5Iu6vRQIBFN6wfsxxUWOh9oSnivcWBF5IO7a71nuiEEaZpG1tSsQTYk9ENW
+Sx5cP4NnF7uZyMhYASWpVRBsx0gz6cmGWTIO8v9CX4D8HMK7uKeJkMenmwiwnhkI
++H+9U8sHCX0UGUZqiJshM7ySf7lhHI+UgrE2qCR0GFoRfP9Yz6tsJ0bNKBUkl6UH
+BmXtH1Z7QVSfbkheNA3LNaFB2Gz20+s1kF6N0VGE1DQCsgM2gkciEhYAz13OBT70
+j6PPargWi51B/hsg9b3LE44dk052/vVnklnJ+rjdgrrmwIlN4xyEbc851knLOqxn
+7Xjnl1Qx4sJkm06E+AuKy0OXy2f/mIOiTmA0jlXtYPSV6rYxtUzJeOlqwsu3ImNg
+x3DRSmjZfW1BOCSqLvlJyKeFKxwJlWFGsKk7uBonwA==
+-----END CERTIFICATE----- \ No newline at end of file