From 6c1434addf1ee25a8c7ce30c240e2033d5075f35 Mon Sep 17 00:00:00 2001 From: Fup Duck Date: Thu, 15 Feb 2018 21:27:17 +0100 Subject: 8797 - pair programming effort --- .../se/leap/bitmaskclient/ProviderApiManager.java | 45 +-- .../java/se/leap/bitmaskclient/ConfigHelper.java | 9 +- .../main/java/se/leap/bitmaskclient/Constants.java | 2 - .../main/java/se/leap/bitmaskclient/Dashboard.java | 1 - .../leap/bitmaskclient/DownloadFailedDialog.java | 160 ----------- .../java/se/leap/bitmaskclient/EipFragment.java | 200 +------------- .../java/se/leap/bitmaskclient/MainActivity.java | 307 ++++++++++++++++++++- .../bitmaskclient/MainActivityErrorDialog.java | 148 ++++++++++ .../leap/bitmaskclient/OkHttpClientGenerator.java | 5 +- .../main/java/se/leap/bitmaskclient/Provider.java | 16 +- .../java/se/leap/bitmaskclient/ProviderAPI.java | 13 +- .../leap/bitmaskclient/ProviderApiManagerBase.java | 47 ++-- .../ProviderCredentialsBaseActivity.java | 19 +- .../bitmaskclient/ProviderListBaseActivity.java | 26 +- .../bitmaskclient/ProviderSetupFailedDialog.java | 160 +++++++++++ .../main/java/se/leap/bitmaskclient/eip/EIP.java | 134 ++++----- .../java/se/leap/bitmaskclient/eip/EipCommand.java | 9 - .../bitmaskclient/eip/VpnCertificateValidator.java | 8 +- app/src/main/res/values/strings.xml | 6 +- .../se/leap/bitmaskclient/ProviderApiManager.java | 33 ++- .../bitmaskclient/eip/ProviderApiManagerTest.java | 8 +- 21 files changed, 829 insertions(+), 527 deletions(-) delete mode 100644 app/src/main/java/se/leap/bitmaskclient/DownloadFailedDialog.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/MainActivityErrorDialog.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/ProviderSetupFailedDialog.java (limited to 'app') diff --git a/app/src/insecure/java/se/leap/bitmaskclient/ProviderApiManager.java b/app/src/insecure/java/se/leap/bitmaskclient/ProviderApiManager.java index 1c5247c0..42bd576a 100644 --- a/app/src/insecure/java/se/leap/bitmaskclient/ProviderApiManager.java +++ b/app/src/insecure/java/se/leap/bitmaskclient/ProviderApiManager.java @@ -51,10 +51,12 @@ import static android.text.TextUtils.isEmpty; import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_KEY; import static se.leap.bitmaskclient.Constants.PROVIDER_KEY; import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE; -import static se.leap.bitmaskclient.DownloadFailedDialog.DOWNLOAD_ERRORS.ERROR_CERTIFICATE_PINNING; -import static se.leap.bitmaskclient.DownloadFailedDialog.DOWNLOAD_ERRORS.ERROR_CORRUPTED_PROVIDER_JSON; +import static se.leap.bitmaskclient.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.ERROR_CERTIFICATE_PINNING; +import static se.leap.bitmaskclient.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.ERROR_CORRUPTED_PROVIDER_JSON; import static se.leap.bitmaskclient.ProviderAPI.ERRORS; import static se.leap.bitmaskclient.R.string.certificate_error; +import static se.leap.bitmaskclient.R.string.downloading_vpn_certificate_failed; +import static se.leap.bitmaskclient.R.string.error_io_exception_user_message; import static se.leap.bitmaskclient.R.string.malformed_url; import static se.leap.bitmaskclient.R.string.warning_corrupted_provider_cert; import static se.leap.bitmaskclient.R.string.warning_corrupted_provider_details; @@ -201,28 +203,33 @@ public class ProviderApiManager extends ProviderApiManagerBase { * @return true if certificate was downloaded correctly, false if provider.json is not present in SharedPreferences, or if the certificate url could not be parsed as a URI, or if there was an SSL error. */ @Override - protected boolean updateVpnCertificate(Provider provider) { + protected Bundle updateVpnCertificate(Provider provider) { + Bundle result = new Bundle(); try { - JSONObject providerDefinition = provider.getDefinition(); - - String providerMainUrl = providerDefinition.getString(Provider.API_URL); - URL newCertStringUrl = new URL(providerMainUrl + "/" + providerDefinition.getString(Provider.API_VERSION) + "/" + PROVIDER_VPN_CERTIFICATE); + JSONObject providerJson = provider.getDefinition(); + String providerMainUrl = providerJson.getString(Provider.API_URL); + URL newCertStringUrl = new URL(providerMainUrl + "/" + providerJson.getString(Provider.API_VERSION) + "/" + PROVIDER_VPN_CERTIFICATE); String certString = downloadWithProviderCA(provider.getCaCert(), newCertStringUrl.toString(), lastDangerOn); - - if (certString == null || certString.isEmpty() || ConfigHelper.checkErroneousDownload(certString)) - return false; - else - return loadCertificate(provider, certString); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - return false; - } catch (JSONException e) { - // TODO Auto-generated catch block + if (ConfigHelper.checkErroneousDownload(certString)) { + if (certString == null || certString.isEmpty()) { + // probably 204 + setErrorResult(result, error_io_exception_user_message, null); + } else { + String reasonToFail = pickErrorMessage(certString); + result.putString(ERRORS, reasonToFail); + result.putBoolean(BROADCAST_RESULT_KEY, false); + return result; + } + } + result = loadCertificate(provider, certString); + } catch (IOException | JSONException e) { + // TODO try to get Provider Json + setErrorResult(result, downloading_vpn_certificate_failed, null); e.printStackTrace(); - return false; } + result.putParcelable(PROVIDER_KEY, provider); + return result; } diff --git a/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java b/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java index 7b2accd6..a52df460 100644 --- a/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java +++ b/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java @@ -72,9 +72,9 @@ public class ConfigHelper { "eeaf0ab9adb38dd69c33f80afa8fc5e86072618775ff3c0b9ea2314c9c256576d674df7496ea81d3383b4813d692c6e0e0d5d8e250b98be48e495c1d6089dad15dc7d7b46154d6b6ce8ef4ad69b15d4982559b297bcf1885c529f566660e57ec68edbc3c05726cc02fd4cbf4976eaa9afd5138fe8376435b9fc61d2fc0eb06e3"; final public static BigInteger G = new BigInteger("2"); - public static boolean checkErroneousDownload(String downloaded_string) { + public static boolean checkErroneousDownload(String downloadedString) { try { - if (downloaded_string == null || downloaded_string.isEmpty() || new JSONObject(downloaded_string).has(ProviderAPI.ERRORS)) { + if (downloadedString == null || downloadedString.isEmpty() || new JSONObject(downloadedString).has(ProviderAPI.ERRORS)) { return true; } else { return false; @@ -158,7 +158,7 @@ public class ConfigHelper { } protected static RSAPrivateKey parseRsaKeyFromString(String rsaKeyString) { - RSAPrivateKey key = null; + RSAPrivateKey key; try { KeyFactory kf = KeyFactory.getInstance("RSA", "BC"); rsaKeyString = rsaKeyString.replaceFirst("-----BEGIN RSA PRIVATE KEY-----", "").replaceFirst("-----END RSA PRIVATE KEY-----", ""); @@ -282,6 +282,7 @@ public class ConfigHelper { provider.setCaCert(preferences.getString(Provider.CA_CERT, "")); provider.setVpnCertificate(preferences.getString(PROVIDER_VPN_CERTIFICATE, "")); provider.setPrivateKey(preferences.getString(PROVIDER_PRIVATE_KEY, "")); + provider.setEipServiceJson(new JSONObject(preferences.getString(PROVIDER_EIP_DEFINITION, ""))); } catch (MalformedURLException | JSONException e) { e.printStackTrace(); } @@ -374,8 +375,6 @@ public class ConfigHelper { putString(Provider.KEY + "." + providerDomain, provider.getDefinitionString()). putString(Provider.CA_CERT + "." + providerDomain, provider.getCaCert()). putString(PROVIDER_EIP_DEFINITION + "." + providerDomain, provider.getEipServiceJsonString()). - putString(PROVIDER_PRIVATE_KEY + "." + providerDomain, provider.getPrivateKey()). - putString(PROVIDER_VPN_CERTIFICATE + "." + providerDomain, provider.getVpnCertificate()). apply(); } diff --git a/app/src/main/java/se/leap/bitmaskclient/Constants.java b/app/src/main/java/se/leap/bitmaskclient/Constants.java index 680c10bf..12b56b93 100644 --- a/app/src/main/java/se/leap/bitmaskclient/Constants.java +++ b/app/src/main/java/se/leap/bitmaskclient/Constants.java @@ -35,13 +35,11 @@ public interface Constants { String EIP_ACTION_CHECK_CERT_VALIDITY = "EIP.CHECK_CERT_VALIDITY"; String EIP_ACTION_START = "se.leap.bitmaskclient.EIP.START"; String EIP_ACTION_STOP = "se.leap.bitmaskclient.EIP.STOP"; - String EIP_ACTION_UPDATE = "se.leap.bitmaskclient.EIP.UPDATE"; String EIP_ACTION_IS_RUNNING = "se.leap.bitmaskclient.EIP.IS_RUNNING"; String EIP_ACTION_START_ALWAYS_ON_VPN = "se.leap.bitmaskclient.START_ALWAYS_ON_VPN"; String EIP_ACTION_START_BLOCKING_VPN = "se.leap.bitmaskclient.EIP_ACTION_START_BLOCKING_VPN"; String EIP_ACTION_STOP_BLOCKING_VPN = "se.leap.bitmaskclient.EIP_ACTION_STOP_BLOCKING_VPN"; - String EIP_NOTIFICATION = "EIP.NOTIFICATION"; String EIP_RECEIVER = "EIP.RECEIVER"; String EIP_REQUEST = "EIP.REQUEST"; String EIP_RESTART_ON_BOOT = "EIP.RESTART_ON_BOOT"; diff --git a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java index 48dce1c2..41c308a2 100644 --- a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java +++ b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java @@ -162,7 +162,6 @@ public class Dashboard extends ButterKnifeActivity { case 91: // 0.6.0 without Bug #5999 case 101: // 0.8.0 if (!preferences.getString(PROVIDER_EIP_DEFINITION, "").isEmpty()) - EipCommand.updateEipService(this); break; } } catch (NameNotFoundException e) { diff --git a/app/src/main/java/se/leap/bitmaskclient/DownloadFailedDialog.java b/app/src/main/java/se/leap/bitmaskclient/DownloadFailedDialog.java deleted file mode 100644 index 8a6d981d..00000000 --- a/app/src/main/java/se/leap/bitmaskclient/DownloadFailedDialog.java +++ /dev/null @@ -1,160 +0,0 @@ -/** - * Copyright (c) 2013 LEAP Encryption Access Project and contributers - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package se.leap.bitmaskclient; - -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v4.app.DialogFragment; - -import org.json.JSONObject; - -import static se.leap.bitmaskclient.DownloadFailedDialog.DOWNLOAD_ERRORS.DEFAULT; -import static se.leap.bitmaskclient.DownloadFailedDialog.DOWNLOAD_ERRORS.valueOf; -import static se.leap.bitmaskclient.ProviderAPI.ERRORID; -import static se.leap.bitmaskclient.ProviderAPI.ERRORS; - -/** - * Implements a dialog to show why a download failed. - * - * @author parmegv - */ -public class DownloadFailedDialog extends DialogFragment { - - public static String TAG = "downloaded_failed_dialog"; - private String reasonToFail; - private DOWNLOAD_ERRORS downloadError = DEFAULT; - - private Provider provider; - - public enum DOWNLOAD_ERRORS { - DEFAULT, - ERROR_CORRUPTED_PROVIDER_JSON, - ERROR_INVALID_CERTIFICATE, - ERROR_CERTIFICATE_PINNING - } - - /** - * @return a new instance of this DialogFragment. - */ - public static DialogFragment newInstance(Provider provider, String reasonToFail) { - DownloadFailedDialog dialogFragment = new DownloadFailedDialog(); - dialogFragment.reasonToFail = reasonToFail; - dialogFragment.provider = provider; - return dialogFragment; - } - - /** - * @return a new instance of this DialogFragment. - */ - public static DialogFragment newInstance(Provider provider, JSONObject errorJson) { - DownloadFailedDialog dialogFragment = new DownloadFailedDialog(); - dialogFragment.provider = provider; - try { - if (errorJson.has(ERRORS)) { - dialogFragment.reasonToFail = errorJson.getString(ERRORS); - } else { - //default error msg - dialogFragment.reasonToFail = dialogFragment.getString(R.string.error_io_exception_user_message); - } - - if (errorJson.has(ERRORID)) { - dialogFragment.downloadError = valueOf(errorJson.getString(ERRORID)); - } - } catch (Exception e) { - e.printStackTrace(); - dialogFragment.reasonToFail = dialogFragment.getString(R.string.error_io_exception_user_message); - } - return dialogFragment; - } - - @Override - @NonNull - public Dialog onCreateDialog(Bundle savedInstanceState) { - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - builder.setMessage(reasonToFail) - .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - interfaceWithConfigurationWizard.cancelSettingUpProvider(); - dialog.dismiss(); - } - }); -switch (downloadError) { - case ERROR_CORRUPTED_PROVIDER_JSON: - builder.setPositiveButton(R.string.update_provider_details, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dismiss(); - interfaceWithConfigurationWizard.updateProviderDetails(); - } - }); - break; - case ERROR_CERTIFICATE_PINNING: - case ERROR_INVALID_CERTIFICATE: - builder.setPositiveButton(R.string.update_certificate, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dismiss(); - interfaceWithConfigurationWizard.updateProviderDetails(); - } - }); - break; - default: - builder.setPositiveButton(R.string.retry, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - dismiss(); - interfaceWithConfigurationWizard.retrySetUpProvider(provider); - } - }); - break; - } - - // Create the AlertDialog object and return it - return builder.create(); - } - - public interface DownloadFailedDialogInterface { - void retrySetUpProvider(@NonNull Provider provider); - - void cancelSettingUpProvider(); - - void updateProviderDetails(); - } - - DownloadFailedDialogInterface interfaceWithConfigurationWizard; - - @Override - public void onAttach(Context context) { - super.onAttach(context); - try { - interfaceWithConfigurationWizard = (DownloadFailedDialogInterface) context; - } catch (ClassCastException e) { - throw new ClassCastException(context.toString() - + " must implement NoticeDialogListener"); - } - } - - @Override - public void onCancel(DialogInterface dialog) { - interfaceWithConfigurationWizard.cancelSettingUpProvider(); - dialog.dismiss(); - } - -} diff --git a/app/src/main/java/se/leap/bitmaskclient/EipFragment.java b/app/src/main/java/se/leap/bitmaskclient/EipFragment.java index 41d9ff04..e1c61801 100644 --- a/app/src/main/java/se/leap/bitmaskclient/EipFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/EipFragment.java @@ -18,22 +18,18 @@ package se.leap.bitmaskclient; import android.app.Activity; import android.app.AlertDialog; -import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; -import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.SharedPreferences; import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; import android.os.Bundle; import android.os.IBinder; -import android.os.RemoteException; import android.support.annotation.NonNull; import android.support.v4.app.Fragment; -import android.support.v4.content.LocalBroadcastManager; import android.support.v7.widget.AppCompatImageView; import android.util.Log; import android.view.LayoutInflater; @@ -50,38 +46,20 @@ import butterknife.InjectView; import butterknife.OnClick; import de.blinkt.openvpn.core.IOpenVPNServiceInternal; import de.blinkt.openvpn.core.OpenVPNService; -import de.blinkt.openvpn.core.ProfileManager; -import de.blinkt.openvpn.core.VpnStatus; import se.leap.bitmaskclient.eip.EipCommand; import se.leap.bitmaskclient.eip.EipStatus; -import se.leap.bitmaskclient.eip.VoidVpnService; -import static android.app.Activity.RESULT_OK; -import static android.content.Intent.CATEGORY_DEFAULT; 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.BROADCAST_EIP_EVENT; -import static se.leap.bitmaskclient.Constants.BROADCAST_PROVIDER_API_EVENT; -import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_CODE; -import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_KEY; -import static se.leap.bitmaskclient.Constants.EIP_ACTION_CHECK_CERT_VALIDITY; -import static se.leap.bitmaskclient.Constants.EIP_ACTION_START; -import static se.leap.bitmaskclient.Constants.EIP_ACTION_STOP; -import static se.leap.bitmaskclient.Constants.EIP_ACTION_STOP_BLOCKING_VPN; -import static se.leap.bitmaskclient.Constants.EIP_ACTION_UPDATE; -import static se.leap.bitmaskclient.Constants.EIP_NOTIFICATION; -import static se.leap.bitmaskclient.Constants.EIP_REQUEST; 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_LOG_IN; import static se.leap.bitmaskclient.Constants.REQUEST_CODE_SWITCH_PROVIDER; import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; -import static se.leap.bitmaskclient.ProviderAPI.CORRECTLY_DOWNLOADED_CERTIFICATE; -import static se.leap.bitmaskclient.ProviderAPI.CORRECTLY_DOWNLOADED_EIP_SERVICE; -import static se.leap.bitmaskclient.ProviderAPI.DOWNLOAD_CERTIFICATE; -import static se.leap.bitmaskclient.ProviderAPI.INCORRECTLY_DOWNLOADED_CERTIFICATE; -import static se.leap.bitmaskclient.ProviderAPI.INCORRECTLY_DOWNLOADED_EIP_SERVICE; +import static se.leap.bitmaskclient.ProviderAPI.DOWNLOAD_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.ProviderCredentialsBaseActivity.USER_MESSAGE; +import static se.leap.bitmaskclient.R.string.vpn_certificate_user_message; public class EipFragment extends Fragment implements Observer { @@ -114,9 +92,6 @@ public class EipFragment extends Fragment implements Observer { TextView vpnRoute; private EipStatus eipStatus; - private boolean wantsToConnect; - - private EIPFragmentBroadcastReceiver eipFragmentBroadcastReceiver; private IOpenVPNServiceInternal mService; private ServiceConnection openVpnConnection = new ServiceConnection() { @@ -161,7 +136,6 @@ public class EipFragment extends Fragment implements Observer { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); eipStatus = EipStatus.getInstance(); - eipFragmentBroadcastReceiver = new EIPFragmentBroadcastReceiver(); Activity activity = getActivity(); if (activity != null) { preferences = getActivity().getSharedPreferences(SHARED_PREFERENCES, Context.MODE_PRIVATE); @@ -188,7 +162,6 @@ public class EipFragment extends Fragment implements Observer { super.onResume(); //FIXME: avoid race conditions while checking certificate an logging in at about the same time //eipCommand(Constants.EIP_ACTION_CHECK_CERT_VALIDITY); - setUpBroadcastReceiver(); handleNewState(); bindOpenVpnService(); } @@ -200,7 +173,6 @@ public class EipFragment extends Fragment implements Observer { Activity activity = getActivity(); if (activity != null) { getActivity().unbindService(openVpnConnection); - LocalBroadcastManager.getInstance(activity).unregisterReceiver(eipFragmentBroadcastReceiver); } Log.d(TAG, "broadcast unregistered"); } @@ -253,13 +225,7 @@ public class EipFragment extends Fragment implements Observer { if (canStartEIP()) { startEipFromScratch(); } else if (canLogInToStartEIP()) { - wantsToConnect = true; - Intent intent = new Intent(getContext(), LoginActivity.class); - intent.putExtra(PROVIDER_KEY, provider); - Activity activity = getActivity(); - if (activity != null) { - activity.startActivityForResult(intent, REQUEST_CODE_LOG_IN); - } + askUserToLogIn(getString(vpn_certificate_user_message)); } else { // provider has no VpnCertificate but user is logged in downloadVpnCertificate(); @@ -274,7 +240,7 @@ public class EipFragment extends Fragment implements Observer { private boolean canLogInToStartEIP() { boolean isAllowedRegistered = provider.allowsRegistered(); - boolean isLoggedIn = !LeapSRPSession.getToken().isEmpty(); + boolean isLoggedIn = LeapSRPSession.loggedIn(); return isAllowedRegistered && !isLoggedIn && !eipStatus.isConnecting() && !eipStatus.isConnected(); } @@ -310,7 +276,6 @@ public class EipFragment extends Fragment implements Observer { } public void startEipFromScratch() { - wantsToConnect = false; saveStatus(true); Context context = getContext(); if (context != null) { @@ -320,38 +285,6 @@ public class EipFragment extends Fragment implements Observer { } } - private void stop() { - saveStatus(false); - if (eipStatus.isBlockingVpnEstablished()) { - stopBlockingVpn(); - } - disconnect(); - } - - private void stopBlockingVpn() { - Log.d(TAG, "stop VoidVpn!"); - Activity activity = getActivity(); - if (activity != null) { - Intent stopVoidVpnIntent = new Intent(activity, VoidVpnService.class); - stopVoidVpnIntent.setAction(EIP_ACTION_STOP_BLOCKING_VPN); - activity.startService(stopVoidVpnIntent); - } else { - Log.e(TAG, "activity is null when trying to stop blocking vpn"); - // TODO what to do if not stopping void vpn? - } - } - - private void disconnect() { - ProfileManager.setConntectedVpnProfileDisconnected(getActivity()); - if (mService != null) { - try { - mService.stopVPN(false); - } catch (RemoteException e) { - VpnStatus.logException(e); - } - } - } - protected void stopEipIfPossible() { Context context = getContext(); if (context != null) { @@ -454,84 +387,6 @@ public class EipFragment extends Fragment implements Observer { } } - private class EIPFragmentBroadcastReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - Log.d(TAG, "received Broadcast"); - - String action = intent.getAction(); - if (action == null) { - return; - } - - int resultCode = intent.getIntExtra(BROADCAST_RESULT_CODE, -1); - Bundle resultData = intent.getParcelableExtra(BROADCAST_RESULT_KEY); - switch (action) { - case BROADCAST_EIP_EVENT: - handleEIPEvent(resultCode, resultData); - break; - case BROADCAST_PROVIDER_API_EVENT: - handleProviderApiEvent(resultCode, resultData); - break; - } - } - } - - private void handleEIPEvent(int resultCode, Bundle resultData) { - String request = resultData.getString(EIP_REQUEST); - - if (request == null) { - return; - } - - switch (request) { - case EIP_ACTION_START: - switch (resultCode) { - case RESULT_OK: - break; - case Activity.RESULT_CANCELED: - break; - } - break; - case EIP_ACTION_STOP: - switch (resultCode) { - case RESULT_OK: - stop(); - break; - case Activity.RESULT_CANCELED: - break; - } - break; - case EIP_NOTIFICATION: - switch (resultCode) { - case RESULT_OK: - break; - case Activity.RESULT_CANCELED: - break; - } - break; - case EIP_ACTION_CHECK_CERT_VALIDITY: - switch (resultCode) { - case RESULT_OK: - break; - case Activity.RESULT_CANCELED: - downloadVpnCertificate(); - break; - } - break; - case EIP_ACTION_UPDATE: - switch (resultCode) { - case RESULT_OK: - if (wantsToConnect) - startEipFromScratch(); - break; - case Activity.RESULT_CANCELED: - handleNewState(); - break; - } - } - } - private void greyscaleBackground() { ColorMatrix matrix = new ColorMatrix(); matrix.setSaturation(0); @@ -550,46 +405,21 @@ public class EipFragment extends Fragment implements Observer { background.setImageAlpha(255); } - public void handleProviderApiEvent(int resultCode, Bundle resultData) { - Context context = getContext(); - if (context == null) { - return; - } - - // TODO call DOWNLOAD_EIP_SERVICES ore remove respective cases - switch (resultCode) { - case CORRECTLY_DOWNLOADED_EIP_SERVICE: - provider = resultData.getParcelable(PROVIDER_KEY); - EipCommand.updateEipService(context); - break; - case INCORRECTLY_DOWNLOADED_EIP_SERVICE: - //dashboard.setResult(RESULT_CANCELED); - // TODO CATCH ME IF YOU CAN - WHAT DO WE WANT TO DO? - break; - case CORRECTLY_DOWNLOADED_CERTIFICATE: - startEipFromScratch(); - break; - case INCORRECTLY_DOWNLOADED_CERTIFICATE: - // TODO CATCH ME IF YOU CAN - LOGIN? - break; - } - } - private void downloadVpnCertificate() { - ProviderAPICommand.execute(getContext(), DOWNLOAD_CERTIFICATE, provider); + ProviderAPICommand.execute(getContext(), DOWNLOAD_VPN_CERTIFICATE, provider); } - private void setUpBroadcastReceiver() { + private void askUserToLogIn(String userMessage) { + Intent intent = new Intent(getContext(), LoginActivity.class); + intent.putExtra(PROVIDER_KEY, provider); + + if(userMessage != null) { + intent.putExtra(USER_MESSAGE, userMessage); + } + Activity activity = getActivity(); if (activity != null) { - IntentFilter updateIntentFilter = new IntentFilter(BROADCAST_EIP_EVENT); - updateIntentFilter.addAction(BROADCAST_PROVIDER_API_EVENT); - updateIntentFilter.addCategory(CATEGORY_DEFAULT); - LocalBroadcastManager.getInstance(activity).registerReceiver(eipFragmentBroadcastReceiver, updateIntentFilter); - Log.d(TAG, "broadcast registered"); - } else { - Log.e(TAG, "activity null when setting up broadcast receiver"); + activity.startActivityForResult(intent, REQUEST_CODE_LOG_IN); } } - } diff --git a/app/src/main/java/se/leap/bitmaskclient/MainActivity.java b/app/src/main/java/se/leap/bitmaskclient/MainActivity.java index 06a66e43..6d9d32b5 100644 --- a/app/src/main/java/se/leap/bitmaskclient/MainActivity.java +++ b/app/src/main/java/se/leap/bitmaskclient/MainActivity.java @@ -1,32 +1,100 @@ package se.leap.bitmaskclient; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; import android.content.SharedPreferences; import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.support.v4.app.DialogFragment; import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentTransaction; +import android.support.v4.content.LocalBroadcastManager; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; +import android.util.Log; +import org.jetbrains.annotations.NotNull; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.Observable; +import java.util.Observer; + +import de.blinkt.openvpn.core.IOpenVPNServiceInternal; +import de.blinkt.openvpn.core.OpenVPNService; +import de.blinkt.openvpn.core.ProfileManager; +import de.blinkt.openvpn.core.VpnStatus; import se.leap.bitmaskclient.drawer.NavigationDrawerFragment; import se.leap.bitmaskclient.eip.EipCommand; +import se.leap.bitmaskclient.eip.EipStatus; +import se.leap.bitmaskclient.eip.VoidVpnService; +import static android.content.Intent.CATEGORY_DEFAULT; +import static se.leap.bitmaskclient.Constants.BROADCAST_EIP_EVENT; +import static se.leap.bitmaskclient.Constants.BROADCAST_PROVIDER_API_EVENT; +import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_CODE; +import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_KEY; +import static se.leap.bitmaskclient.Constants.EIP_ACTION_START; +import static se.leap.bitmaskclient.Constants.EIP_ACTION_STOP; +import static se.leap.bitmaskclient.Constants.EIP_ACTION_STOP_BLOCKING_VPN; +import static se.leap.bitmaskclient.Constants.EIP_REQUEST; +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; import static se.leap.bitmaskclient.Constants.REQUEST_CODE_LOG_IN; import static se.leap.bitmaskclient.Constants.REQUEST_CODE_SWITCH_PROVIDER; import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; import static se.leap.bitmaskclient.EipFragment.ASK_TO_CANCEL_VPN; +import static se.leap.bitmaskclient.ProviderAPI.CORRECTLY_DOWNLOADED_EIP_SERVICE; +import static se.leap.bitmaskclient.ProviderAPI.CORRECTLY_DOWNLOADED_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.ProviderAPI.ERRORS; +import static se.leap.bitmaskclient.ProviderAPI.INCORRECTLY_DOWNLOADED_EIP_SERVICE; +import static se.leap.bitmaskclient.ProviderAPI.INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.ProviderCredentialsBaseActivity.USER_MESSAGE; +import static se.leap.bitmaskclient.R.string.downloading_vpn_certificate_failed; +import static se.leap.bitmaskclient.R.string.vpn_certificate_user_message; + + +public class MainActivity extends AppCompatActivity implements Observer, MainActivityErrorDialog.MainActivityErrorDialogInterface{ + public final static String TAG = MainActivity.class.getSimpleName(); -public class MainActivity extends AppCompatActivity { + private final String ACTIVITY_STATE = "state of activity"; + private final String DEFAULT_UI_STATE = "default state"; + private final String SHOW_DIALOG_STATE = "show dialog"; + private final String REASON_TO_FAIL = "reason to fail"; + protected Intent mConfigState = new Intent(DEFAULT_UI_STATE); private static Provider provider = new Provider(); - private static FragmentManagerEnhanced fragmentManager; private SharedPreferences preferences; + private String reasonToFail; + + private EipStatus eipStatus; private NavigationDrawerFragment navigationDrawerFragment; + private MainActivityBroadcastReceiver mainActivityBroadcastReceiver; + + private IOpenVPNServiceInternal mService; + private ServiceConnection openVpnConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName className, + IBinder service) { + mService = IOpenVPNServiceInternal.Stub.asInterface(service); + } + + @Override + public void onServiceDisconnected(ComponentName arg0) { + mService = null; + } + + }; public final static String ACTION_SHOW_VPN_FRAGMENT = "action_show_vpn_fragment"; @@ -40,19 +108,62 @@ public class MainActivity extends AppCompatActivity { setContentView(R.layout.activity_main); setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); + mainActivityBroadcastReceiver = new MainActivityBroadcastReceiver(); + setUpBroadcastReceiver(); + navigationDrawerFragment = (NavigationDrawerFragment) getSupportFragmentManager().findFragmentById(R.id.navigation_drawer); preferences = getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE); provider = ConfigHelper.getSavedProviderFromSharedPreferences(preferences); - fragmentManager = new FragmentManagerEnhanced(getSupportFragmentManager()); // Set up the drawer. navigationDrawerFragment.setUp( R.id.navigation_drawer, (DrawerLayout) findViewById(R.id.drawer_layout)); + eipStatus = EipStatus.getInstance(); + handleIntentAction(getIntent()); + if(savedInstanceState != null) { + restoreState(savedInstanceState); + } + + } + + @Override + protected void onSaveInstanceState(@NotNull Bundle outState) { + outState.putString(ACTIVITY_STATE, mConfigState.getAction()); + outState.putParcelable(PROVIDER_KEY, provider); + + DialogFragment dialogFragment = (DialogFragment) new FragmentManagerEnhanced(getSupportFragmentManager()).findFragmentByTag(MainActivityErrorDialog.TAG); + outState.putString(REASON_TO_FAIL, reasonToFail); + if (dialogFragment != null) { + dialogFragment.dismiss(); + } + + super.onSaveInstanceState(outState); + } + + private void restoreState(Bundle savedInstance) { + String activityState = savedInstance.getString(ACTIVITY_STATE, ""); + if (activityState.equals(SHOW_DIALOG_STATE)) { + reasonToFail = savedInstance.getString(REASON_TO_FAIL); + if (reasonToFail != null) { + showDownloadFailedDialog(reasonToFail); + } + } + } + + @Override + protected void onResume() { + super.onResume(); + bindOpenVpnService(); + + String action = mConfigState.getAction(); + if(action.equalsIgnoreCase(SHOW_DIALOG_STATE)) { + showDownloadFailedDialog(reasonToFail); + } } @Override @@ -122,8 +233,196 @@ public class MainActivity extends AppCompatActivity { Bundle arguments = new Bundle(); arguments.putParcelable(PROVIDER_KEY, provider); fragment.setArguments(arguments); - fragmentManager.beginTransaction() + new FragmentManagerEnhanced(getSupportFragmentManager()).beginTransaction() .replace(R.id.container, fragment) .commit(); } + + @Override + protected void onPause() { + super.onPause(); + unbindService(openVpnConnection); + } + + @Override + protected void onDestroy() { + LocalBroadcastManager.getInstance(this).unregisterReceiver(mainActivityBroadcastReceiver); + mainActivityBroadcastReceiver = null; + super.onDestroy(); + } + + private void setUpBroadcastReceiver() { + IntentFilter updateIntentFilter = new IntentFilter(BROADCAST_EIP_EVENT); + updateIntentFilter.addAction(BROADCAST_PROVIDER_API_EVENT); + updateIntentFilter.addCategory(CATEGORY_DEFAULT); + LocalBroadcastManager.getInstance(this).registerReceiver(mainActivityBroadcastReceiver, updateIntentFilter); + Log.d(TAG, "broadcast registered"); + } + + @Override + public void onDialogDismissed() { + mConfigState.setAction(DEFAULT_UI_STATE); + reasonToFail = null; + } + + private class MainActivityBroadcastReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + Log.d(TAG, "received Broadcast"); + + String action = intent.getAction(); + if (action == null) { + return; + } + + int resultCode = intent.getIntExtra(BROADCAST_RESULT_CODE, RESULT_CANCELED); + Bundle resultData = intent.getParcelableExtra(BROADCAST_RESULT_KEY); + if (resultData == null) { + resultData = Bundle.EMPTY; + } + + switch (action) { + case BROADCAST_EIP_EVENT: + handleEIPEvent(resultCode, resultData); + break; + case BROADCAST_PROVIDER_API_EVENT: + handleProviderApiEvent(resultCode, resultData); + break; + } + } + } + + private void handleEIPEvent(int resultCode, Bundle resultData) { + String request = resultData.getString(EIP_REQUEST); + + if (request == null) { + return; + } + + switch (request) { + case EIP_ACTION_START: + switch (resultCode) { + case RESULT_OK: + break; + case RESULT_CANCELED: + String error = resultData.getString(ERRORS); + if (LeapSRPSession.loggedIn() || provider.allowsAnonymous()) { + showDownloadFailedDialog(error); + } else { + askUserToLogIn(getString(vpn_certificate_user_message)); + } + break; + } + break; + case EIP_ACTION_STOP: + switch (resultCode) { + case RESULT_OK: + stop(); + break; + case RESULT_CANCELED: + break; + } + break; + } + } + + public void handleProviderApiEvent(int resultCode, Bundle resultData) { + // TODO call DOWNLOAD_EIP_SERVICES ore remove respective cases + switch (resultCode) { + case CORRECTLY_DOWNLOADED_EIP_SERVICE: + provider = resultData.getParcelable(PROVIDER_KEY); + EipCommand.startVPN(this); + break; + case INCORRECTLY_DOWNLOADED_EIP_SERVICE: + // TODO CATCH ME IF YOU CAN - WHAT DO WE WANT TO DO? + break; + + case CORRECTLY_DOWNLOADED_VPN_CERTIFICATE: + provider = resultData.getParcelable(PROVIDER_KEY); + ConfigHelper.storeProviderInPreferences(preferences, provider); + EipCommand.startVPN(this); + break; + case INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE: + if (LeapSRPSession.loggedIn() || provider.allowsAnonymous()) { + showDownloadFailedDialog(getString(downloading_vpn_certificate_failed)); + } else { + askUserToLogIn(getString(vpn_certificate_user_message)); + } + break; + } + } + + /** + * Shows an error dialog + */ + public void showDownloadFailedDialog(String reasonToFail) { + this.reasonToFail = reasonToFail; + mConfigState.setAction(SHOW_DIALOG_STATE); + try { + + FragmentTransaction fragmentTransaction = new FragmentManagerEnhanced( + this.getSupportFragmentManager()).removePreviousFragment( + MainActivityErrorDialog.TAG); + DialogFragment newFragment; + try { + 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); + } catch (IllegalStateException | NullPointerException e) { + e.printStackTrace(); + } + + } + + @Override + public void update(Observable observable, Object data) { + if (observable instanceof EipStatus) { + eipStatus = (EipStatus) observable; + } + } + + private void stop() { + preferences.edit().putBoolean(EIP_RESTART_ON_BOOT, false).apply(); + if (eipStatus.isBlockingVpnEstablished()) { + stopBlockingVpn(); + } + disconnect(); + } + + private void stopBlockingVpn() { + Log.d(TAG, "stop VoidVpn!"); + Intent stopVoidVpnIntent = new Intent(this, VoidVpnService.class); + stopVoidVpnIntent.setAction(EIP_ACTION_STOP_BLOCKING_VPN); + startService(stopVoidVpnIntent); + } + + private void disconnect() { + ProfileManager.setConntectedVpnProfileDisconnected(this); + if (mService != null) { + try { + mService.stopVPN(false); + } catch (RemoteException e) { + VpnStatus.logException(e); + } + } + } + + private void bindOpenVpnService() { + Intent intent = new Intent(this, OpenVPNService.class); + intent.setAction(OpenVPNService.START_SERVICE); + bindService(intent, openVpnConnection, Context.BIND_AUTO_CREATE); + } + + private void askUserToLogIn(String userMessage) { + Intent intent = new Intent(this, LoginActivity.class); + intent.putExtra(PROVIDER_KEY, provider); + if (userMessage != null) { + intent.putExtra(USER_MESSAGE, userMessage); + } + startActivityForResult(intent, REQUEST_CODE_LOG_IN); + } } diff --git a/app/src/main/java/se/leap/bitmaskclient/MainActivityErrorDialog.java b/app/src/main/java/se/leap/bitmaskclient/MainActivityErrorDialog.java new file mode 100644 index 00000000..374c48fa --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/MainActivityErrorDialog.java @@ -0,0 +1,148 @@ +/** + * Copyright (c) 2013 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package se.leap.bitmaskclient; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.DialogFragment; + +import org.json.JSONObject; + +import static se.leap.bitmaskclient.MainActivityErrorDialog.DOWNLOAD_ERRORS.*; +import static se.leap.bitmaskclient.ProviderAPI.DOWNLOAD_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.eip.EIP.ERRORS; +import static se.leap.bitmaskclient.eip.EIP.ERROR_ID; + +/** + * Implements a dialog to show why a download failed. + * + * @author parmegv + */ +public class MainActivityErrorDialog extends DialogFragment { + + public static String TAG = "downloaded_failed_dialog"; + private String reasonToFail; + private DOWNLOAD_ERRORS downloadError = DEFAULT; + + private MainActivityErrorDialogInterface callbackInterface; + private Provider provider; + + public enum DOWNLOAD_ERRORS { + DEFAULT, + ERROR_INVALID_VPN_CERTIFICATE, + } + + /** + * @return a new instance of this DialogFragment. + */ + public static DialogFragment newInstance(Provider provider, String reasonToFail) { + MainActivityErrorDialog dialogFragment = new MainActivityErrorDialog(); + dialogFragment.reasonToFail = reasonToFail; + dialogFragment.provider = provider; + return dialogFragment; + } + + /** + * @return a new instance of this DialogFragment. + */ + public static DialogFragment newInstance(Provider provider, JSONObject errorJson) { + MainActivityErrorDialog dialogFragment = new MainActivityErrorDialog(); + dialogFragment.provider = provider; + try { + if (errorJson.has(ERRORS)) { + dialogFragment.reasonToFail = errorJson.getString(ERRORS); + } else { + //default error msg + dialogFragment.reasonToFail = dialogFragment.getString(R.string.error_io_exception_user_message); + } + + if (errorJson.has(ERROR_ID)) { + dialogFragment.downloadError = valueOf(errorJson.getString(ERROR_ID)); + } + } catch (Exception e) { + e.printStackTrace(); + dialogFragment.reasonToFail = dialogFragment.getString(R.string.error_io_exception_user_message); + } + return dialogFragment; + } + + @Override + @NonNull + public Dialog onCreateDialog(Bundle savedInstanceState) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setMessage(reasonToFail) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.dismiss(); + callbackInterface.onDialogDismissed(); + } + }); + switch (downloadError) { + case ERROR_INVALID_VPN_CERTIFICATE: + builder.setPositiveButton(R.string.update_certificate, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dismiss(); + ProviderAPICommand.execute(getContext(), DOWNLOAD_VPN_CERTIFICATE, provider); + callbackInterface.onDialogDismissed(); + } + }); + break; + default: + builder.setPositiveButton(R.string.retry, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dismiss(); + callbackInterface.onDialogDismissed(); + } + }); + break; + } + + // Create the AlertDialog object and return it + return builder.create(); + } + + @Override + public void dismiss() { + super.dismiss(); + } + + public interface MainActivityErrorDialogInterface { + void onDialogDismissed(); + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + try { + callbackInterface = (MainActivityErrorDialogInterface) context; + } catch (ClassCastException e) { + throw new ClassCastException(context.toString() + + " must implement NoticeDialogListener"); + } + } + + @Override + public void onCancel(DialogInterface dialog) { + super.onCancel(dialog); + callbackInterface.onDialogDismissed(); + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/OkHttpClientGenerator.java b/app/src/main/java/se/leap/bitmaskclient/OkHttpClientGenerator.java index 40b2ea7f..7d1054f1 100644 --- a/app/src/main/java/se/leap/bitmaskclient/OkHttpClientGenerator.java +++ b/app/src/main/java/se/leap/bitmaskclient/OkHttpClientGenerator.java @@ -52,6 +52,7 @@ import static se.leap.bitmaskclient.R.string.error_io_exception_user_message; import static se.leap.bitmaskclient.R.string.error_no_such_algorithm_exception_user_message; import static se.leap.bitmaskclient.R.string.keyChainAccessError; import static se.leap.bitmaskclient.R.string.server_unreachable_message; +import static se.leap.bitmaskclient.R.string.warning_corrupted_provider_cert; /** * Created by cyberta on 08.01.18. @@ -90,7 +91,8 @@ public class OkHttpClientGenerator { return clientBuilder.build(); } catch (IllegalArgumentException e) { e.printStackTrace(); - addErrorMessageToJson(initError, resources.getString(R.string.certificate_error)); + // TODO ca cert is invalid - show better error ?! + addErrorMessageToJson(initError, resources.getString(certificate_error)); } catch (IllegalStateException | KeyManagementException | KeyStoreException e) { e.printStackTrace(); addErrorMessageToJson(initError, String.format(resources.getString(keyChainAccessError), e.getLocalizedMessage())); @@ -99,6 +101,7 @@ public class OkHttpClientGenerator { addErrorMessageToJson(initError, resources.getString(error_no_such_algorithm_exception_user_message)); } catch (CertificateException e) { e.printStackTrace(); + // TODO ca cert is invalid - show better error ?! addErrorMessageToJson(initError, resources.getString(certificate_error)); } catch (UnknownHostException e) { e.printStackTrace(); diff --git a/app/src/main/java/se/leap/bitmaskclient/Provider.java b/app/src/main/java/se/leap/bitmaskclient/Provider.java index b3362409..7104143c 100644 --- a/app/src/main/java/se/leap/bitmaskclient/Provider.java +++ b/app/src/main/java/se/leap/bitmaskclient/Provider.java @@ -112,10 +112,12 @@ public final class Provider implements Parcelable { public boolean isConfigured() { return !mainUrl.isDefault() && - definition.length() > 0 && !apiUrl.isDefault() && - caCert != null && - !caCert.isEmpty(); + hasCaCert() && + hasDefinition() && + hasVpnCertificate() && + hasEIP() && + hasPrivateKey(); } public void setMainUrl(URL url) { @@ -161,7 +163,7 @@ public final class Provider implements Parcelable { return getDefinition().toString(); } - protected String getDomain() { + public String getDomain() { return mainUrl.getDomain(); } @@ -169,7 +171,7 @@ public final class Provider implements Parcelable { return getMainUrl().toString(); } - protected DefaultedURL getMainUrl() { + public DefaultedURL getMainUrl() { return mainUrl; } @@ -400,6 +402,10 @@ public final class Provider implements Parcelable { this.privateKey = privateKey; } + public boolean hasPrivateKey() { + return privateKey != null && privateKey.length() > 0; + } + public String getVpnCertificate() { return vpnCertificate; } diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderAPI.java b/app/src/main/java/se/leap/bitmaskclient/ProviderAPI.java index b3399416..2e153c7a 100644 --- a/app/src/main/java/se/leap/bitmaskclient/ProviderAPI.java +++ b/app/src/main/java/se/leap/bitmaskclient/ProviderAPI.java @@ -44,13 +44,12 @@ public class ProviderAPI extends IntentService implements ProviderApiManagerBase SIGN_UP = "srpRegister", LOG_IN = "srpAuth", LOG_OUT = "logOut", - DOWNLOAD_CERTIFICATE = "downloadUserAuthedCertificate", + DOWNLOAD_VPN_CERTIFICATE = "downloadUserAuthedVPNCertificate", PARAMETERS = "parameters", RECEIVER_KEY = "receiver", ERRORS = "errors", ERRORID = "errorId", - UPDATE_PROGRESSBAR = "update_progressbar", - DOWNLOAD_EIP_SERVICE = "ProviderAPI.DOWNLOAD_EIP_SERVICE", + DOWNLOAD_SERVICE_JSON = "ProviderAPI.DOWNLOAD_SERVICE_JSON", PROVIDER_SET_UP = "ProviderAPI.PROVIDER_SET_UP"; final public static int @@ -60,8 +59,8 @@ public class ProviderAPI extends IntentService implements ProviderApiManagerBase FAILED_SIGNUP = 6, SUCCESSFUL_LOGOUT = 7, LOGOUT_FAILED = 8, - CORRECTLY_DOWNLOADED_CERTIFICATE = 9, - INCORRECTLY_DOWNLOADED_CERTIFICATE = 10, + CORRECTLY_DOWNLOADED_VPN_CERTIFICATE = 9, + INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE = 10, PROVIDER_OK = 11, PROVIDER_NOK = 12, CORRECTLY_DOWNLOADED_EIP_SERVICE = 13, @@ -69,8 +68,6 @@ public class ProviderAPI extends IntentService implements ProviderApiManagerBase ProviderApiManager providerApiManager; - - public ProviderAPI() { super(TAG); } @@ -82,7 +79,6 @@ public class ProviderAPI extends IntentService implements ProviderApiManagerBase return ProviderApiManager.lastDangerOn(); } - @Override public void onCreate() { super.onCreate(); @@ -99,7 +95,6 @@ public class ProviderAPI extends IntentService implements ProviderApiManagerBase providerApiManager.handleIntent(command); } - private ProviderApiManager initApiManager() { SharedPreferences preferences = getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE); OkHttpClientGenerator clientGenerator = new OkHttpClientGenerator(preferences, getResources()); diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java b/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java index 505ee55b..5aff1af1 100644 --- a/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java +++ b/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java @@ -61,18 +61,18 @@ import static se.leap.bitmaskclient.Constants.CREDENTIALS_USERNAME; 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.DownloadFailedDialog.DOWNLOAD_ERRORS.ERROR_CERTIFICATE_PINNING; -import static se.leap.bitmaskclient.DownloadFailedDialog.DOWNLOAD_ERRORS.ERROR_CORRUPTED_PROVIDER_JSON; -import static se.leap.bitmaskclient.DownloadFailedDialog.DOWNLOAD_ERRORS.ERROR_INVALID_CERTIFICATE; -import static se.leap.bitmaskclient.ProviderAPI.CORRECTLY_DOWNLOADED_CERTIFICATE; +import static se.leap.bitmaskclient.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.ERROR_CERTIFICATE_PINNING; +import static se.leap.bitmaskclient.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.ERROR_CORRUPTED_PROVIDER_JSON; +import static se.leap.bitmaskclient.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.ERROR_INVALID_CERTIFICATE; +import static se.leap.bitmaskclient.ProviderAPI.CORRECTLY_DOWNLOADED_VPN_CERTIFICATE; import static se.leap.bitmaskclient.ProviderAPI.CORRECTLY_DOWNLOADED_EIP_SERVICE; -import static se.leap.bitmaskclient.ProviderAPI.DOWNLOAD_CERTIFICATE; -import static se.leap.bitmaskclient.ProviderAPI.DOWNLOAD_EIP_SERVICE; +import static se.leap.bitmaskclient.ProviderAPI.DOWNLOAD_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.ProviderAPI.DOWNLOAD_SERVICE_JSON; import static se.leap.bitmaskclient.ProviderAPI.ERRORID; 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_CERTIFICATE; +import static se.leap.bitmaskclient.ProviderAPI.INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE; import static se.leap.bitmaskclient.ProviderAPI.INCORRECTLY_DOWNLOADED_EIP_SERVICE; import static se.leap.bitmaskclient.ProviderAPI.LOGOUT_FAILED; import static se.leap.bitmaskclient.ProviderAPI.LOG_IN; @@ -89,6 +89,7 @@ import static se.leap.bitmaskclient.ProviderAPI.SUCCESSFUL_LOGOUT; import static se.leap.bitmaskclient.ProviderAPI.SUCCESSFUL_SIGNUP; import static se.leap.bitmaskclient.ProviderAPI.UPDATE_PROVIDER_DETAILS; import static se.leap.bitmaskclient.R.string.certificate_error; +import static se.leap.bitmaskclient.R.string.vpn_certificate_is_invalid; import static se.leap.bitmaskclient.R.string.error_io_exception_user_message; import static se.leap.bitmaskclient.R.string.error_json_exception_user_message; import static se.leap.bitmaskclient.R.string.error_no_such_algorithm_exception_user_message; @@ -184,14 +185,15 @@ public abstract class ProviderApiManagerBase { sendToReceiverOrBroadcast(receiver, LOGOUT_FAILED, Bundle.EMPTY, provider); } break; - case DOWNLOAD_CERTIFICATE: - if (updateVpnCertificate(provider)) { - sendToReceiverOrBroadcast(receiver, CORRECTLY_DOWNLOADED_CERTIFICATE, Bundle.EMPTY, provider); + case DOWNLOAD_VPN_CERTIFICATE: + result = updateVpnCertificate(provider); + if (result.getBoolean(BROADCAST_RESULT_KEY)) { + sendToReceiverOrBroadcast(receiver, CORRECTLY_DOWNLOADED_VPN_CERTIFICATE, result, provider); } else { - sendToReceiverOrBroadcast(receiver, INCORRECTLY_DOWNLOADED_CERTIFICATE, Bundle.EMPTY, provider); + sendToReceiverOrBroadcast(receiver, INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE, result, provider); } break; - case DOWNLOAD_EIP_SERVICE: + case DOWNLOAD_SERVICE_JSON: result = getAndSetEipServiceJson(provider); if (result.getBoolean(BROADCAST_RESULT_KEY)) { sendToReceiverOrBroadcast(receiver, CORRECTLY_DOWNLOADED_EIP_SERVICE, result, provider); @@ -537,7 +539,7 @@ public abstract class ProviderApiManagerBase { } private String requestStringFromServer(@NonNull String url, @NonNull String request_method, String jsonString, @NonNull List> headerArgs, @NonNull OkHttpClient okHttpClient) { - String plainResponseBody = null; + String plainResponseBody; try { @@ -617,7 +619,7 @@ public abstract class ProviderApiManagerBase { * * @return true if certificate was downloaded correctly, false if provider.json is not present in SharedPreferences, or if the certificate url could not be parsed as a URI, or if there was an SSL error. */ - protected abstract boolean updateVpnCertificate(Provider provider); + protected abstract Bundle updateVpnCertificate(Provider provider); protected boolean isValidJson(String jsonString) { @@ -815,15 +817,17 @@ public abstract class ProviderApiManagerBase { return false; } - protected boolean loadCertificate(Provider provider, String cert_string) { - if (cert_string == null) { - return false; + protected Bundle loadCertificate(Provider provider, String certString) { + Bundle result = new Bundle(); + if (certString == null) { + setErrorResult(result, vpn_certificate_is_invalid, null); + return result; } try { // API returns concatenated cert & key. Split them for OpenVPN options String certificateString = null, keyString = null; - String[] certAndKey = cert_string.split("(?<=-\n)"); + String[] certAndKey = certString.split("(?<=-\n)"); for (int i = 0; i < certAndKey.length - 1; i++) { if (certAndKey[i].contains("KEY")) { keyString = certAndKey[i++] + certAndKey[i]; @@ -837,13 +841,14 @@ public abstract class ProviderApiManagerBase { provider.setPrivateKey( "-----BEGIN RSA PRIVATE KEY-----\n" + keyString + "-----END RSA PRIVATE KEY-----"); X509Certificate certificate = ConfigHelper.parseX509CertificateFromString(certificateString); + certificate.checkValidity(); certificateString = Base64.encodeToString(certificate.getEncoded(), Base64.DEFAULT); provider.setVpnCertificate( "-----BEGIN CERTIFICATE-----\n" + certificateString + "-----END CERTIFICATE-----"); - return true; + result.putBoolean(BROADCAST_RESULT_KEY, true); } catch (CertificateException | NullPointerException e) { - // TODO Auto-generated catch block e.printStackTrace(); - return false; + setErrorResult(result, vpn_certificate_is_invalid, null); } + return result; } } diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderCredentialsBaseActivity.java b/app/src/main/java/se/leap/bitmaskclient/ProviderCredentialsBaseActivity.java index 7714e979..ba10ae24 100644 --- a/app/src/main/java/se/leap/bitmaskclient/ProviderCredentialsBaseActivity.java +++ b/app/src/main/java/se/leap/bitmaskclient/ProviderCredentialsBaseActivity.java @@ -41,7 +41,7 @@ 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.PROVIDER_KEY; -import static se.leap.bitmaskclient.ProviderAPI.DOWNLOAD_CERTIFICATE; +import static se.leap.bitmaskclient.ProviderAPI.DOWNLOAD_VPN_CERTIFICATE; import static se.leap.bitmaskclient.ProviderAPI.LOG_IN; import static se.leap.bitmaskclient.ProviderAPI.SIGN_UP; @@ -59,7 +59,7 @@ public abstract class ProviderCredentialsBaseActivity extends ConfigWizardBaseAc final private static String SHOWING_FORM = "SHOWING_FORM"; final private static String PERFORMING_ACTION = "PERFORMING_ACTION"; - final private static String USER_MESSAGE = "USER_MESSAGE"; + final public static String USER_MESSAGE = "USER_MESSAGE"; final private static String USERNAME_ERROR = "USERNAME_ERROR"; final private static String PASSWORD_ERROR = "PASSWORD_ERROR"; final private static String PASSWORD_VERIFICATION_ERROR = "PASSWORD_VERIFICATION_ERROR"; @@ -105,6 +105,12 @@ public abstract class ProviderCredentialsBaseActivity extends ConfigWizardBaseAc if(savedInstanceState != null) { restoreState(savedInstanceState); } + + String userMessageString = getIntent().getStringExtra(USER_MESSAGE); + if (userMessageString != null) { + userMessage.setText(userMessageString); + userMessage.setVisibility(VISIBLE); + } } @Override @@ -204,7 +210,7 @@ public abstract class ProviderCredentialsBaseActivity extends ConfigWizardBaseAc void downloadVpnCertificate(Provider handledProvider) { provider = handledProvider; - ProviderAPICommand.execute(this, DOWNLOAD_CERTIFICATE, provider); + ProviderAPICommand.execute(this, DOWNLOAD_VPN_CERTIFICATE, provider); } protected Bundle bundleUsernameAndPassword(String username, String password) { @@ -381,7 +387,7 @@ public abstract class ProviderCredentialsBaseActivity extends ConfigWizardBaseAc return; } - int resultCode = intent.getIntExtra(BROADCAST_RESULT_CODE, -1); + int resultCode = intent.getIntExtra(BROADCAST_RESULT_CODE, RESULT_CANCELED); Bundle resultData = intent.getParcelableExtra(BROADCAST_RESULT_KEY); Provider handledProvider = resultData.getParcelable(PROVIDER_KEY); @@ -395,11 +401,10 @@ public abstract class ProviderCredentialsBaseActivity extends ConfigWizardBaseAc handleReceivedErrors((Bundle) intent.getParcelableExtra(BROADCAST_RESULT_KEY)); break; - case ProviderAPI.CORRECTLY_DOWNLOADED_CERTIFICATE: + case ProviderAPI.CORRECTLY_DOWNLOADED_VPN_CERTIFICATE: successfullyFinished(handledProvider); - //activity.eip_fragment.updateEipService(); break; - case ProviderAPI.INCORRECTLY_DOWNLOADED_CERTIFICATE: + case ProviderAPI.INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE: // TODO activity.setResult(RESULT_CANCELED); break; } diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderListBaseActivity.java b/app/src/main/java/se/leap/bitmaskclient/ProviderListBaseActivity.java index 41d2d849..3b855601 100644 --- a/app/src/main/java/se/leap/bitmaskclient/ProviderListBaseActivity.java +++ b/app/src/main/java/se/leap/bitmaskclient/ProviderListBaseActivity.java @@ -53,10 +53,10 @@ import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_CODE; import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_KEY; import static se.leap.bitmaskclient.Constants.PROVIDER_KEY; import static se.leap.bitmaskclient.Constants.REQUEST_CODE_CONFIGURE_LEAP; -import static se.leap.bitmaskclient.ProviderAPI.CORRECTLY_DOWNLOADED_CERTIFICATE; -import static se.leap.bitmaskclient.ProviderAPI.DOWNLOAD_CERTIFICATE; +import static se.leap.bitmaskclient.ProviderAPI.CORRECTLY_DOWNLOADED_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.ProviderAPI.DOWNLOAD_VPN_CERTIFICATE; import static se.leap.bitmaskclient.ProviderAPI.ERRORS; -import static se.leap.bitmaskclient.ProviderAPI.INCORRECTLY_DOWNLOADED_CERTIFICATE; +import static se.leap.bitmaskclient.ProviderAPI.INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE; import static se.leap.bitmaskclient.ProviderAPI.PROVIDER_NOK; import static se.leap.bitmaskclient.ProviderAPI.PROVIDER_OK; import static se.leap.bitmaskclient.ProviderAPI.PROVIDER_SET_UP; @@ -74,7 +74,7 @@ import static se.leap.bitmaskclient.ProviderAPI.UPDATE_PROVIDER_DETAILS; */ public abstract class ProviderListBaseActivity extends ConfigWizardBaseActivity - implements NewProviderDialog.NewProviderDialogInterface, DownloadFailedDialog.DownloadFailedDialogInterface, ProviderAPIResultReceiver.Receiver { + implements NewProviderDialog.NewProviderDialogInterface, ProviderSetupFailedDialog.DownloadFailedDialogInterface, ProviderAPIResultReceiver.Receiver { @InjectView(R.id.provider_list) protected ListView providerListView; @@ -121,7 +121,7 @@ public abstract class ProviderListBaseActivity extends ConfigWizardBaseActivity outState.putString(ACTIVITY_STATE, mConfigState.getAction()); outState.putParcelable(PROVIDER_KEY, provider); - DialogFragment dialogFragment = (DialogFragment) fragmentManager.findFragmentByTag(DownloadFailedDialog.TAG); + DialogFragment dialogFragment = (DialogFragment) fragmentManager.findFragmentByTag(ProviderSetupFailedDialog.TAG); if (dialogFragment != null) { outState.putString(REASON_TO_FAIL, reasonToFail); dialogFragment.dismiss(); @@ -313,7 +313,7 @@ public abstract class ProviderListBaseActivity extends ConfigWizardBaseActivity * Asks ProviderApiService to download an anonymous (anon) VPN certificate. */ private void downloadVpnCertificate() { - ProviderAPICommand.execute(this, DOWNLOAD_CERTIFICATE, provider); + ProviderAPICommand.execute(this, DOWNLOAD_VPN_CERTIFICATE, provider); } /** @@ -345,16 +345,16 @@ public abstract class ProviderListBaseActivity extends ConfigWizardBaseActivity */ public void showDownloadFailedDialog() { try { - FragmentTransaction fragmentTransaction = fragmentManager.removePreviousFragment(DownloadFailedDialog.TAG); + FragmentTransaction fragmentTransaction = fragmentManager.removePreviousFragment(ProviderSetupFailedDialog.TAG); DialogFragment newFragment; try { JSONObject errorJson = new JSONObject(reasonToFail); - newFragment = DownloadFailedDialog.newInstance(provider, errorJson); + newFragment = ProviderSetupFailedDialog.newInstance(provider, errorJson); } catch (JSONException e) { e.printStackTrace(); - newFragment = DownloadFailedDialog.newInstance(provider, reasonToFail); + newFragment = ProviderSetupFailedDialog.newInstance(provider, reasonToFail); } - newFragment.show(fragmentTransaction, DownloadFailedDialog.TAG); + newFragment.show(fragmentTransaction, ProviderSetupFailedDialog.TAG); } catch (IllegalStateException e) { e.printStackTrace(); mConfigState.setAction(PENDING_SHOW_FAILED_DIALOG); @@ -405,7 +405,7 @@ public abstract class ProviderListBaseActivity extends ConfigWizardBaseActivity if (mConfigState.getAction() != null && mConfigState.getAction().equalsIgnoreCase(SETTING_UP_PROVIDER)) { - int resultCode = intent.getIntExtra(BROADCAST_RESULT_CODE, -1); + int resultCode = intent.getIntExtra(BROADCAST_RESULT_CODE, RESULT_CANCELED); Log.d(TAG, "Broadcast resultCode: " + Integer.toString(resultCode)); Bundle resultData = intent.getParcelableExtra(BROADCAST_RESULT_KEY); @@ -419,10 +419,10 @@ public abstract class ProviderListBaseActivity extends ConfigWizardBaseActivity case PROVIDER_NOK: handleProviderSetupFailed(resultData); break; - case CORRECTLY_DOWNLOADED_CERTIFICATE: + case CORRECTLY_DOWNLOADED_VPN_CERTIFICATE: handleCorrectlyDownloadedCertificate(handledProvider); break; - case INCORRECTLY_DOWNLOADED_CERTIFICATE: + case INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE: handleIncorrectlyDownloadedCertificate(); break; } diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderSetupFailedDialog.java b/app/src/main/java/se/leap/bitmaskclient/ProviderSetupFailedDialog.java new file mode 100644 index 00000000..9e77452c --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/ProviderSetupFailedDialog.java @@ -0,0 +1,160 @@ +/** + * Copyright (c) 2013 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package se.leap.bitmaskclient; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.DialogFragment; + +import org.json.JSONObject; + +import static se.leap.bitmaskclient.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.DEFAULT; +import static se.leap.bitmaskclient.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.valueOf; +import static se.leap.bitmaskclient.ProviderAPI.ERRORID; +import static se.leap.bitmaskclient.ProviderAPI.ERRORS; + +/** + * Implements a dialog to show why a download failed. + * + * @author parmegv + */ +public class ProviderSetupFailedDialog extends DialogFragment { + + public static String TAG = "downloaded_failed_dialog"; + private String reasonToFail; + private DOWNLOAD_ERRORS downloadError = DEFAULT; + + private Provider provider; + + public enum DOWNLOAD_ERRORS { + DEFAULT, + ERROR_CORRUPTED_PROVIDER_JSON, + ERROR_INVALID_CERTIFICATE, + ERROR_CERTIFICATE_PINNING + } + + /** + * @return a new instance of this DialogFragment. + */ + public static DialogFragment newInstance(Provider provider, String reasonToFail) { + ProviderSetupFailedDialog dialogFragment = new ProviderSetupFailedDialog(); + dialogFragment.reasonToFail = reasonToFail; + dialogFragment.provider = provider; + return dialogFragment; + } + + /** + * @return a new instance of this DialogFragment. + */ + public static DialogFragment newInstance(Provider provider, JSONObject errorJson) { + ProviderSetupFailedDialog dialogFragment = new ProviderSetupFailedDialog(); + dialogFragment.provider = provider; + try { + if (errorJson.has(ERRORS)) { + dialogFragment.reasonToFail = errorJson.getString(ERRORS); + } else { + //default error msg + dialogFragment.reasonToFail = dialogFragment.getString(R.string.error_io_exception_user_message); + } + + if (errorJson.has(ERRORID)) { + dialogFragment.downloadError = valueOf(errorJson.getString(ERRORID)); + } + } catch (Exception e) { + e.printStackTrace(); + dialogFragment.reasonToFail = dialogFragment.getString(R.string.error_io_exception_user_message); + } + return dialogFragment; + } + + @Override + @NonNull + public Dialog onCreateDialog(Bundle savedInstanceState) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setMessage(reasonToFail) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + interfaceWithConfigurationWizard.cancelSettingUpProvider(); + dialog.dismiss(); + } + }); + switch (downloadError) { + case ERROR_CORRUPTED_PROVIDER_JSON: + builder.setPositiveButton(R.string.update_provider_details, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dismiss(); + interfaceWithConfigurationWizard.updateProviderDetails(); + } + }); + break; + case ERROR_CERTIFICATE_PINNING: + case ERROR_INVALID_CERTIFICATE: + builder.setPositiveButton(R.string.update_certificate, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dismiss(); + interfaceWithConfigurationWizard.updateProviderDetails(); + } + }); + break; + default: + builder.setPositiveButton(R.string.retry, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dismiss(); + interfaceWithConfigurationWizard.retrySetUpProvider(provider); + } + }); + break; + } + + // Create the AlertDialog object and return it + return builder.create(); + } + + public interface DownloadFailedDialogInterface { + void retrySetUpProvider(@NonNull Provider provider); + + void cancelSettingUpProvider(); + + void updateProviderDetails(); + } + + DownloadFailedDialogInterface interfaceWithConfigurationWizard; + + @Override + public void onAttach(Context context) { + super.onAttach(context); + try { + interfaceWithConfigurationWizard = (DownloadFailedDialogInterface) context; + } catch (ClassCastException e) { + throw new ClassCastException(context.toString() + + " must implement NoticeDialogListener"); + } + } + + @Override + public void onCancel(DialogInterface dialog) { + interfaceWithConfigurationWizard.cancelSettingUpProvider(); + dialog.dismiss(); + } + +} 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 46528b85..88047f55 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java @@ -16,7 +16,6 @@ */ package se.leap.bitmaskclient.eip; -import android.app.Activity; import android.app.IntentService; import android.content.Intent; import android.content.SharedPreferences; @@ -33,6 +32,9 @@ import java.lang.ref.WeakReference; import de.blinkt.openvpn.LaunchVPN; import se.leap.bitmaskclient.OnBootReceiver; +import static android.app.Activity.RESULT_CANCELED; +import static android.app.Activity.RESULT_OK; +import static android.content.Intent.CATEGORY_DEFAULT; import static se.leap.bitmaskclient.Constants.BROADCAST_EIP_EVENT; import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_CODE; import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_KEY; @@ -41,13 +43,14 @@ import static se.leap.bitmaskclient.Constants.EIP_ACTION_IS_RUNNING; 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_ACTION_STOP; -import static se.leap.bitmaskclient.Constants.EIP_ACTION_UPDATE; import static se.leap.bitmaskclient.Constants.EIP_RECEIVER; import static se.leap.bitmaskclient.Constants.EIP_REQUEST; import static se.leap.bitmaskclient.Constants.EIP_RESTART_ON_BOOT; import static se.leap.bitmaskclient.Constants.PROVIDER_EIP_DEFINITION; import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE; import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; +import static se.leap.bitmaskclient.MainActivityErrorDialog.DOWNLOAD_ERRORS.ERROR_INVALID_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.R.string.vpn_certificate_is_invalid; /** * EIP is the abstract base class for interacting with and managing the Encrypted @@ -61,16 +64,14 @@ import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; */ public final class EIP extends IntentService { - public final static String TAG = EIP.class.getSimpleName(); - public final static String SERVICE_API_PATH = "config/eip-service.json"; + public final static String TAG = EIP.class.getSimpleName(), + SERVICE_API_PATH = "config/eip-service.json", + ERRORS = "errors", + ERROR_ID = "errorID"; private WeakReference mReceiverRef = new WeakReference<>(null); private SharedPreferences preferences; - private JSONObject eipDefinition; - private GatewaysManager gatewaysManager = new GatewaysManager(); - private Gateway gateway; - public EIP() { super(TAG); } @@ -79,9 +80,6 @@ public final class EIP extends IntentService { public void onCreate() { super.onCreate(); preferences = getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE); - eipDefinition = eipDefinitionFromPreferences(); - if (gatewaysManager.isEmpty()) - gatewaysFromPreferences(); } @Override @@ -108,11 +106,8 @@ public final class EIP extends IntentService { case EIP_ACTION_IS_RUNNING: isRunning(); break; - case EIP_ACTION_UPDATE: - updateEIPService(); - break; case EIP_ACTION_CHECK_CERT_VALIDITY: - checkCertValidity(); + checkVPNCertificateValidity(); break; } } @@ -123,21 +118,29 @@ public final class EIP extends IntentService { * It also sets up early routes. */ private void startEIP() { + if (!EipStatus.getInstance().isBlockingVpnEstablished()) { + earlyRoutes(); + } + + Bundle result = new Bundle(); + if (!preferences.getBoolean(EIP_RESTART_ON_BOOT, false)){ preferences.edit().putBoolean(EIP_RESTART_ON_BOOT, true).commit(); } - if (gatewaysManager.isEmpty()) - updateEIPService(); - if (!EipStatus.getInstance().isBlockingVpnEstablished()) { - earlyRoutes(); + + GatewaysManager gatewaysManager = gatewaysFromPreferences(); + if (!isVPNCertificateValid()){ + setErrorResult(result, vpn_certificate_is_invalid, ERROR_INVALID_VPN_CERTIFICATE.toString()); + tellToReceiverOrBroadcast(EIP_ACTION_START, RESULT_CANCELED, result); + return; } - gateway = gatewaysManager.select(); + Gateway gateway = gatewaysManager.select(); if (gateway != null && gateway.getProfile() != null) { - launchActiveGateway(); - tellToReceiverOrBroadcast(EIP_ACTION_START, Activity.RESULT_OK); + launchActiveGateway(gateway); + tellToReceiverOrBroadcast(EIP_ACTION_START, RESULT_OK); } else - tellToReceiverOrBroadcast(EIP_ACTION_START, Activity.RESULT_CANCELED); + tellToReceiverOrBroadcast(EIP_ACTION_START, RESULT_CANCELED); } /** @@ -147,14 +150,12 @@ public final class EIP extends IntentService { private void startEIPAlwaysOnVpn() { Log.d(TAG, "startEIPAlwaysOnVpn vpn"); - if (gatewaysManager.isEmpty()) - updateEIPService(); - - gateway = gatewaysManager.select(); + GatewaysManager gatewaysManager = gatewaysFromPreferences(); + Gateway gateway = gatewaysManager.select(); if (gateway != null && gateway.getProfile() != null) { Log.d(TAG, "startEIPAlwaysOnVpn eip launch avtive gateway vpn"); - launchActiveGateway(); + launchActiveGateway(gateway); } else { Log.d(TAG, "startEIPAlwaysOnVpn no active profile available!"); } @@ -170,7 +171,7 @@ public final class EIP extends IntentService { startActivity(voidVpnLauncher); } - private void launchActiveGateway() { + private void launchActiveGateway(Gateway gateway) { Intent intent = new Intent(this, LaunchVPN.class); intent.setAction(Intent.ACTION_MAIN); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); @@ -180,10 +181,11 @@ public final class EIP extends IntentService { } private void stopEIP() { + // TODO try to do anything! stop eip from here if possible... EipStatus eipStatus = EipStatus.getInstance(); - int resultCode = Activity.RESULT_CANCELED; + int resultCode = RESULT_CANCELED; if (eipStatus.isConnected() || eipStatus.isConnecting()) - resultCode = Activity.RESULT_OK; + resultCode = RESULT_OK; tellToReceiverOrBroadcast(EIP_ACTION_STOP, resultCode); } @@ -196,22 +198,11 @@ public final class EIP extends IntentService { private void isRunning() { EipStatus eipStatus = EipStatus.getInstance(); int resultCode = (eipStatus.isConnected()) ? - Activity.RESULT_OK : - Activity.RESULT_CANCELED; + RESULT_OK : + RESULT_CANCELED; tellToReceiverOrBroadcast(EIP_ACTION_IS_RUNNING, resultCode); } - /** - * Loads eip-service.json from SharedPreferences, delete previous vpn profiles and add new gateways. - * TODO Implement API call to refresh eip-service.json from the provider - */ - private void updateEIPService() { - eipDefinition = eipDefinitionFromPreferences(); - if (eipDefinition.length() > 0) - updateGateways(); - tellToReceiverOrBroadcast(EIP_ACTION_UPDATE, Activity.RESULT_OK); - } - private JSONObject eipDefinitionFromPreferences() { JSONObject result = new JSONObject(); try { @@ -226,34 +217,25 @@ public final class EIP extends IntentService { return result; } - private void updateGateways() { - gatewaysManager.clearGatewaysAndProfiles(); - gatewaysManager.fromEipServiceJson(eipDefinition); - gatewaysToPreferences(); + private GatewaysManager gatewaysFromPreferences() { + GatewaysManager gatewaysManager = new GatewaysManager(this, preferences); + gatewaysManager.fromEipServiceJson(eipDefinitionFromPreferences()); + return gatewaysManager; } - private void gatewaysFromPreferences() { - String gatewaysString = preferences.getString(Gateway.TAG, ""); - gatewaysManager = new GatewaysManager(this, preferences); - gatewaysManager.addFromString(gatewaysString); - preferences.edit().remove(Gateway.TAG).apply(); - } - - private void gatewaysToPreferences() { - String gateways_string = gatewaysManager.toString(); - preferences.edit().putString(Gateway.TAG, gateways_string).commit(); + private void checkVPNCertificateValidity() { + int resultCode = isVPNCertificateValid() ? + RESULT_OK : + RESULT_CANCELED; + tellToReceiverOrBroadcast(EIP_ACTION_CHECK_CERT_VALIDITY, resultCode); } - private void checkCertValidity() { + private boolean isVPNCertificateValid() { VpnCertificateValidator validator = new VpnCertificateValidator(preferences.getString(PROVIDER_VPN_CERTIFICATE, "")); - int resultCode = validator.isValid() ? - Activity.RESULT_OK : - Activity.RESULT_CANCELED; - tellToReceiverOrBroadcast(EIP_ACTION_CHECK_CERT_VALIDITY, resultCode); + return validator.isValid(); } - private void tellToReceiverOrBroadcast(String action, int resultCode) { - Bundle resultData = new Bundle(); + private void tellToReceiverOrBroadcast(String action, int resultCode, Bundle resultData) { resultData.putString(EIP_REQUEST, action); if (mReceiverRef.get() != null) { mReceiverRef.get().send(resultCode, resultData); @@ -262,13 +244,33 @@ public final class EIP extends IntentService { } } + private void tellToReceiverOrBroadcast(String action, int resultCode) { + tellToReceiverOrBroadcast(action, resultCode, new Bundle()); + } + private void broadcastEvent(int resultCode , Bundle resultData) { Intent intentUpdate = new Intent(BROADCAST_EIP_EVENT); - intentUpdate.addCategory(Intent.CATEGORY_DEFAULT); + intentUpdate.addCategory(CATEGORY_DEFAULT); intentUpdate.putExtra(BROADCAST_RESULT_CODE, resultCode); intentUpdate.putExtra(BROADCAST_RESULT_KEY, resultData); Log.d(TAG, "sending broadcast"); LocalBroadcastManager.getInstance(this).sendBroadcast(intentUpdate); } + Bundle setErrorResult(Bundle result, int errorMessageId, String errorId) { + JSONObject errorJson = new JSONObject(); + addErrorMessageToJson(errorJson, getResources().getString(errorMessageId), errorId); + result.putString(ERRORS, errorJson.toString()); + result.putBoolean(BROADCAST_RESULT_KEY, false); + return result; + } + + private void addErrorMessageToJson(JSONObject jsonObject, String errorMessage, String errorId) { + try { + jsonObject.put(ERRORS, errorMessage); + jsonObject.put(ERROR_ID, errorId); + } catch (JSONException e) { + e.printStackTrace(); + } + } } diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/EipCommand.java b/app/src/main/java/se/leap/bitmaskclient/eip/EipCommand.java index 1c778ec7..aa06b462 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EipCommand.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EipCommand.java @@ -11,7 +11,6 @@ import org.jetbrains.annotations.Nullable; import static se.leap.bitmaskclient.Constants.EIP_ACTION_CHECK_CERT_VALIDITY; import static se.leap.bitmaskclient.Constants.EIP_ACTION_START; import static se.leap.bitmaskclient.Constants.EIP_ACTION_STOP; -import static se.leap.bitmaskclient.Constants.EIP_ACTION_UPDATE; import static se.leap.bitmaskclient.Constants.EIP_RECEIVER; /** @@ -40,14 +39,6 @@ public class EipCommand { context.startService(vpnIntent); } - public static void updateEipService(@NonNull Context context, ResultReceiver resultReceiver) { - execute(context, EIP_ACTION_UPDATE, resultReceiver); - } - - public static void updateEipService(@NonNull Context context) { - execute(context, EIP_ACTION_UPDATE); - } - public static void startVPN(@NonNull Context context) { execute(context, EIP_ACTION_START); } diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/VpnCertificateValidator.java b/app/src/main/java/se/leap/bitmaskclient/eip/VpnCertificateValidator.java index 197a080b..03dd9d05 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VpnCertificateValidator.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VpnCertificateValidator.java @@ -48,12 +48,16 @@ public class VpnCertificateValidator { return false; } - X509Certificate certificate_x509 = ConfigHelper.parseX509CertificateFromString(certificate); - return isValid(certificate_x509); + X509Certificate x509Certificate = ConfigHelper.parseX509CertificateFromString(certificate); + return isValid(x509Certificate); } private boolean isValid(X509Certificate certificate) { + if (certificate == null) { + return false; + } + Calendar offsetDate = calculateOffsetCertificateValidity(certificate); try { certificate.checkValidity(offsetDate.getTime()); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 515e1f37..c3e91617 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -100,8 +100,12 @@ Blocking traffic Update provider details Update certificate + Updating provider configuration failed. + Updating provider configuration failed. Please log in to try again. Stored provider details are corrupted. You can either update Bitmask (recommended) or update the provider details using a commercial CA certificate. Stored provider certificate is invalid. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate. Stored provider certificate is expired. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate. - + Downloading the VPN certificate failed. Try again or choose another provider. + VPN certificate is invalid. Try to download a new one. + The VPN certificate is invalid. Please log in to do download a new one. diff --git a/app/src/production/java/se/leap/bitmaskclient/ProviderApiManager.java b/app/src/production/java/se/leap/bitmaskclient/ProviderApiManager.java index 5317118b..a6f0436d 100644 --- a/app/src/production/java/se/leap/bitmaskclient/ProviderApiManager.java +++ b/app/src/production/java/se/leap/bitmaskclient/ProviderApiManager.java @@ -38,6 +38,8 @@ import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE; import static se.leap.bitmaskclient.DownloadFailedDialog.DOWNLOAD_ERRORS.ERROR_CERTIFICATE_PINNING; import static se.leap.bitmaskclient.DownloadFailedDialog.DOWNLOAD_ERRORS.ERROR_CORRUPTED_PROVIDER_JSON; import static se.leap.bitmaskclient.ProviderAPI.ERRORS; +import static se.leap.bitmaskclient.R.string.downloading_vpn_certificate_failed; +import static se.leap.bitmaskclient.R.string.error_io_exception_user_message; import static se.leap.bitmaskclient.R.string.malformed_url; import static se.leap.bitmaskclient.R.string.warning_corrupted_provider_cert; import static se.leap.bitmaskclient.R.string.warning_corrupted_provider_details; @@ -172,27 +174,32 @@ public class ProviderApiManager extends ProviderApiManagerBase { * @return true if certificate was downloaded correctly, false if provider.json is not present in SharedPreferences, or if the certificate url could not be parsed as a URI, or if there was an SSL error. */ @Override - protected boolean updateVpnCertificate(Provider provider) { + protected Bundle updateVpnCertificate(Provider provider) { + Bundle result = new Bundle(); try { JSONObject providerJson = provider.getDefinition(); String providerMainUrl = providerJson.getString(Provider.API_URL); URL newCertStringUrl = new URL(providerMainUrl + "/" + providerJson.getString(Provider.API_VERSION) + "/" + PROVIDER_VPN_CERTIFICATE); String certString = downloadWithProviderCA(provider.getCaCert(), newCertStringUrl.toString()); - - if (ConfigHelper.checkErroneousDownload(certString)) - return false; - else - return loadCertificate(provider, certString); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - return false; - } catch (JSONException e) { - // TODO Auto-generated catch block + if (ConfigHelper.checkErroneousDownload(certString)) { + if (certString == null || certString.isEmpty()) { + // probably 204 + setErrorResult(result, error_io_exception_user_message, null); + } else { + String reasonToFail = pickErrorMessage(certString); + result.putString(ERRORS, reasonToFail); + result.putBoolean(BROADCAST_RESULT_KEY, false); + return result; + } + } + return loadCertificate(provider, certString); + } catch (IOException | JSONException e) { + // TODO try to get Provider Json + setErrorResult(result, downloading_vpn_certificate_failed, null); e.printStackTrace(); - return false; } + return result; } private Bundle downloadCACert(Provider provider) { 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 4842d170..0121f7aa 100644 --- a/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java +++ b/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java @@ -269,7 +269,7 @@ public class ProviderApiManagerTest { Bundle expectedResult = mockBundle(); expectedResult.putBoolean(BROADCAST_RESULT_KEY, false); - expectedResult.putString(ERRORS, "{\"errorId\":\"ERROR_INVALID_CERTIFICATE\",\"errors\":\"Stored provider certificate is expired. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.\"}"); + expectedResult.putString(ERRORS, "{\"errorId\":\"ERROR_INVALID_VPN_CERTIFICATE\",\"errors\":\"Stored provider certificate is expired. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.\"}"); expectedResult.putParcelable(PROVIDER_KEY, provider); Intent providerApiCommand = mockIntent(); @@ -292,7 +292,7 @@ public class ProviderApiManagerTest { Bundle expectedResult = mockBundle(); expectedResult.putBoolean(BROADCAST_RESULT_KEY, false); - expectedResult.putString(ERRORS, "{\"errorId\":\"ERROR_INVALID_CERTIFICATE\",\"errors\":\"Stored provider certificate is expired. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.\"}"); + expectedResult.putString(ERRORS, "{\"errorId\":\"ERROR_INVALID_VPN_CERTIFICATE\",\"errors\":\"Stored provider certificate is expired. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.\"}"); expectedResult.putParcelable(PROVIDER_KEY, provider); Intent providerApiCommand = mockIntent(); @@ -315,7 +315,7 @@ public class ProviderApiManagerTest { providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback()); Bundle expectedResult = mockBundle(); expectedResult.putBoolean(BROADCAST_RESULT_KEY, false); - expectedResult.putString(ERRORS, "{\"errorId\":\"ERROR_INVALID_CERTIFICATE\",\"errors\":\"Stored provider certificate is invalid. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.\"}"); + expectedResult.putString(ERRORS, "{\"errorId\":\"ERROR_INVALID_VPN_CERTIFICATE\",\"errors\":\"Stored provider certificate is invalid. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.\"}"); expectedResult.putParcelable(PROVIDER_KEY, provider); Intent providerApiCommand = mockIntent(); @@ -340,7 +340,7 @@ public class ProviderApiManagerTest { Bundle expectedResult = mockBundle(); expectedResult.putBoolean(BROADCAST_RESULT_KEY, false); - expectedResult.putString(ERRORS, "{\"errorId\":\"ERROR_INVALID_CERTIFICATE\",\"errors\":\"Stored provider certificate is invalid. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.\"}"); + expectedResult.putString(ERRORS, "{\"errorId\":\"ERROR_INVALID_VPN_CERTIFICATE\",\"errors\":\"Stored provider certificate is invalid. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.\"}"); expectedResult.putParcelable(PROVIDER_KEY, provider); Intent providerApiCommand = mockIntent(); -- cgit v1.2.3