From 8301b4bc5b24561b77d3381ea2e8ff8c72368669 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Tue, 17 May 2022 15:54:22 +0200 Subject: use snowflake if necessary to update invalid vpn cert. Show cert update message in UI --- .../bitmaskclient/base/fragments/EipFragment.java | 59 ++++++++++-------- .../leap/bitmaskclient/eip/EipSetupObserver.java | 10 ++- .../java/se/leap/bitmaskclient/eip/EipStatus.java | 10 +++ .../providersetup/ProviderApiManager.java | 46 +++++++------- .../bitmaskclient/eip/ProviderApiManagerTest.java | 72 +++++++++++++++++++++- .../NoErrorBackendResponseAPIv4.java | 3 + .../TorFallbackBackendResponse.java | 8 +++ .../leap/bitmaskclient/testutils/MockHelper.java | 71 +++++++++++++++------ app/src/test/resources/v4/riseup.net.cert | 54 ++++++++++++++++ 9 files changed, 265 insertions(+), 68 deletions(-) create mode 100644 app/src/test/resources/v4/riseup.net.cert 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..e654b18c 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,27 @@ */ package se.leap.bitmaskclient.base.fragments; +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 android.app.Activity; import android.content.ComponentName; import android.content.Context; @@ -27,18 +48,15 @@ import android.graphics.ColorMatrixColorFilter; import android.os.Bundle; import android.os.IBinder; import android.os.Vibrator; -import android.text.TextUtils; 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; @@ -75,29 +93,6 @@ import se.leap.bitmaskclient.providersetup.activities.CustomProviderSetupActivit 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; - public class EipFragment extends Fragment implements Observer { public final static String TAG = EipFragment.class.getSimpleName(); @@ -416,7 +411,16 @@ 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(false); + showConnectionTransitionLayout(true); + locationButton.setText(getString(R.string.eip_status_start_pending)); + locationButton.setLocationLoad(UNKNOWN); + locationButton.showBridgeIndicator(false); + locationButton.showRecommendedIndicator(false); + mainDescription.setText(null); + 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 +578,7 @@ public class EipFragment extends Fragment implements Observer { } private void updateInvalidVpnCertificate() { + EipStatus.getInstance().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..c71a028d 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java @@ -201,6 +201,10 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta ProviderObservable.getInstance().updateProvider(provider); PreferenceHelper.storeProviderInPreferences(preferences, provider); EipCommand.startVPN(context, false); + EipStatus.getInstance().setUpdatingVpnCert(false); + if (TorStatusObservable.getStatus() != OFF) { + TorServiceCommand.stopTorServiceAsync(context); + } break; case CORRECTLY_DOWNLOADED_GEOIP_JSON: provider = resultData.getParcelable(PROVIDER_KEY); @@ -211,8 +215,12 @@ 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.getStatus() != OFF) { + TorServiceCommand.stopTorServiceAsync(context); + } + case PROVIDER_NOK: case INCORRECTLY_DOWNLOADED_EIP_SERVICE: case INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE: if (TorStatusObservable.getStatus() != OFF) { 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/production/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java b/app/src/production/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java index d1de62a0..63b24ea2 100644 --- a/app/src/production/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java +++ b/app/src/production/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java @@ -199,29 +199,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.getStatus() != OFF && TorStatusObservable.getProxyPort() != -1) { + 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 +345,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..b7a4fa7c 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,8 +62,11 @@ 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; @@ -76,6 +80,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 +103,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; @@ -789,6 +794,71 @@ public class ProviderApiManagerTest { assertEquals(-1, TorStatusObservable.getProxyPort()); } + @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_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 -- cgit v1.2.3 From 5de151dc62caa499157224620ed05bfda5f6d612 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Wed, 18 May 2022 11:13:24 +0200 Subject: avoid possible NPE in ClientTransportPlugin --- .../main/java/se/leap/bitmaskclient/tor/ClientTransportPlugin.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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() { -- cgit v1.2.3 From 483fce1c76b991d7ae1a5c2434876cb2fc90a98f Mon Sep 17 00:00:00 2001 From: cyBerta Date: Thu, 19 May 2022 12:22:51 +0200 Subject: implement method to determine if tor is currently running and not yet about to be shutdown --- app/src/main/java/se/leap/bitmaskclient/tor/TorStatusObservable.java | 5 +++++ 1 file changed, 5 insertions(+) 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..54868549 100644 --- a/app/src/main/java/se/leap/bitmaskclient/tor/TorStatusObservable.java +++ b/app/src/main/java/se/leap/bitmaskclient/tor/TorStatusObservable.java @@ -287,4 +287,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; + } } -- cgit v1.2.3 From 8b0a78e9b6691436d6facbb224d8bab7972ab775 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Thu, 19 May 2022 12:24:36 +0200 Subject: reset http proxy port immediately after tor has been cancelled and is about to be shutdown --- app/src/main/java/se/leap/bitmaskclient/tor/TorStatusObservable.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 54868549..239bd528 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); @@ -280,6 +279,8 @@ public class TorStatusObservable extends Observable { public static void markCancelled() { if (!getInstance().cancelled) { getInstance().cancelled = true; + getInstance().port = -1; + getInstance().setChanged(); getInstance().notifyObservers(); } } -- cgit v1.2.3 From 378dc870c6dca3c5186a311fbc9c90e560653a2d Mon Sep 17 00:00:00 2001 From: cyBerta Date: Thu, 19 May 2022 12:26:52 +0200 Subject: only wait until Tor has been completely shutdown successfully on startTorService command if the service was previously cancelled --- app/src/main/java/se/leap/bitmaskclient/tor/TorServiceCommand.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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..0f721013 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(); } -- cgit v1.2.3 From 43637659860bc5cf6506c1520a2d8d83f35d64be Mon Sep 17 00:00:00 2001 From: cyBerta Date: Thu, 19 May 2022 12:29:58 +0200 Subject: early return if tor is ment to be shutdown but is not running --- app/src/main/java/se/leap/bitmaskclient/tor/TorServiceCommand.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 0f721013..b99abb3d 100644 --- a/app/src/main/java/se/leap/bitmaskclient/tor/TorServiceCommand.java +++ b/app/src/main/java/se/leap/bitmaskclient/tor/TorServiceCommand.java @@ -81,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(); @@ -102,6 +103,9 @@ public class TorServiceCommand { } public static void stopTorServiceAsync(Context context) { + if (!TorStatusObservable.isRunning()) { + return; + } TorStatusObservable.markCancelled(); new Thread(() -> stopTorService(context)).start(); } -- cgit v1.2.3 From 37689ed545a9f6cce543c431e9de1948dafaf4cf Mon Sep 17 00:00:00 2001 From: cyBerta Date: Thu, 19 May 2022 12:39:40 +0200 Subject: handle tor startup errors and use better checks in EipSetupObserver if tor should be shutdown --- .../leap/bitmaskclient/eip/EipSetupObserver.java | 84 ++++++++------ .../bitmaskclient/providersetup/ProviderAPI.java | 3 +- .../providersetup/ProviderApiManagerBase.java | 121 ++++++++++++--------- 3 files changed, 120 insertions(+), 88 deletions(-) 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 c71a028d..71b3f5af 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. */ @@ -202,7 +203,7 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta PreferenceHelper.storeProviderInPreferences(preferences, provider); EipCommand.startVPN(context, false); EipStatus.getInstance().setUpdatingVpnCert(false); - if (TorStatusObservable.getStatus() != OFF) { + if (TorStatusObservable.isRunning()) { TorServiceCommand.stopTorServiceAsync(context); } break; @@ -217,13 +218,14 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta break; case INCORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE: EipStatus.getInstance().setUpdatingVpnCert(false); - if (TorStatusObservable.getStatus() != OFF) { + if (TorStatusObservable.isRunning()) { TorServiceCommand.stopTorServiceAsync(context); } + break; case PROVIDER_NOK: case INCORRECTLY_DOWNLOADED_EIP_SERVICE: case INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE: - if (TorStatusObservable.getStatus() != OFF) { + if (TorStatusObservable.isRunning()) { TorServiceCommand.stopTorServiceAsync(context); } Log.d(TAG, "PROVIDER NOK - FETCH FAILED"); @@ -231,6 +233,18 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta 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 initalAction = jsonObject.getString(ProviderAPI.INITIAL_ACTION); + if (UPDATE_INVALID_VPN_CERTIFICATE.equals(initalAction)) { + EipStatus.getInstance().setUpdatingVpnCert(false); + } + } catch (Exception e) { + //ignore + } + break; default: break; } @@ -382,7 +396,7 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta observedProfileFromVpnStatus = null; this.changingGateway.set(changingGateway); this.reconnectTry.set(0); - if (TorStatusObservable.getStatus() != OFF) { + if (TorStatusObservable.isRunning()) { TorServiceCommand.stopTorServiceAsync(context); } } 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..4f1a2d5c 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. @@ -205,6 +206,9 @@ public abstract class ProviderApiManagerBase { } } 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); } -- cgit v1.2.3 From 122b0f9dcde45d4e1afe4d6cfaea6f0926141ea6 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Thu, 19 May 2022 12:49:14 +0200 Subject: ignore snowflake preferences for API communication if a VPN tunnel is already established --- .../se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 4f1a2d5c..ecddb9c7 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java @@ -201,7 +201,7 @@ public abstract class ProviderApiManagerBase { } try { - if (PreferenceHelper.hasSnowflakePrefs(preferences)) { + if (PreferenceHelper.hasSnowflakePrefs(preferences) && !VpnStatus.isVPNActive()) { startTorProxy(); } } catch (InterruptedException | IllegalStateException e) { -- cgit v1.2.3 From 217fc3e6fb4deaf7e1d194e941d7d0b159ce16ab Mon Sep 17 00:00:00 2001 From: cyBerta Date: Thu, 19 May 2022 12:53:22 +0200 Subject: allow to cancel connection attempt during vpn certificate updates, if tor is running, it will be shut down --- .../bitmaskclient/base/fragments/EipFragment.java | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) 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 e654b18c..fb27bcea 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 @@ -92,6 +92,9 @@ 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 se.leap.bitmaskclient.tor.TorServiceCommand; +import se.leap.bitmaskclient.tor.TorStatusObservable; +import se.leap.bitmaskclient.tor.TorStatusObservable.TorStatus; public class EipFragment extends Fragment implements Observer { @@ -272,7 +275,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(); @@ -308,7 +311,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(); @@ -359,7 +362,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) { @@ -412,7 +422,7 @@ 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.isUpdatingVpnCert()) { - setMainButtonEnabled(false); + setMainButtonEnabled(true); showConnectionTransitionLayout(true); locationButton.setText(getString(R.string.eip_status_start_pending)); locationButton.setLocationLoad(UNKNOWN); @@ -578,7 +588,7 @@ public class EipFragment extends Fragment implements Observer { } private void updateInvalidVpnCertificate() { - EipStatus.getInstance().setUpdatingVpnCert(true); + eipStatus.setUpdatingVpnCert(true); ProviderAPICommand.execute(getContext(), UPDATE_INVALID_VPN_CERTIFICATE, provider); } -- cgit v1.2.3 From 6569f4c99565ace16d8a58bc5feb522c693f2194 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Thu, 19 May 2022 12:54:47 +0200 Subject: reduce log noise in error handling --- app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java | 1 - 1 file changed, 1 deletion(-) 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..e9a5032c 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java @@ -327,7 +327,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); -- cgit v1.2.3 From fccac14d1164a9e70cc76738e21a438999eb883a Mon Sep 17 00:00:00 2001 From: cyBerta Date: Thu, 19 May 2022 13:19:30 +0200 Subject: clarify that EipSetupObserver uses the application context --- .../leap/bitmaskclient/eip/EipSetupObserver.java | 38 +++++++++++----------- 1 file changed, 19 insertions(+), 19 deletions(-) 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 71b3f5af..b1bf8cb5 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java @@ -87,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(); @@ -98,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); @@ -106,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); } @@ -175,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); } @@ -194,17 +194,17 @@ 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(context); + TorServiceCommand.stopTorServiceAsync(appContext); } break; case CORRECTLY_DOWNLOADED_GEOIP_JSON: @@ -219,14 +219,14 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta case INCORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE: EipStatus.getInstance().setUpdatingVpnCert(false); if (TorStatusObservable.isRunning()) { - TorServiceCommand.stopTorServiceAsync(context); + TorServiceCommand.stopTorServiceAsync(appContext); } break; case PROVIDER_NOK: case INCORRECTLY_DOWNLOADED_EIP_SERVICE: case INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE: if (TorStatusObservable.isRunning()) { - TorServiceCommand.stopTorServiceAsync(context); + TorServiceCommand.stopTorServiceAsync(appContext); } Log.d(TAG, "PROVIDER NOK - FETCH FAILED"); break; @@ -257,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); } } @@ -284,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(); } } @@ -367,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)) { @@ -380,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) { @@ -397,7 +397,7 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta this.changingGateway.set(changingGateway); this.reconnectTry.set(0); if (TorStatusObservable.isRunning()) { - TorServiceCommand.stopTorServiceAsync(context); + TorServiceCommand.stopTorServiceAsync(appContext); } } @@ -418,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(); -- cgit v1.2.3 From e7395b411c9e50067c59dcadfc8d922855bef96d Mon Sep 17 00:00:00 2001 From: cyBerta Date: Thu, 19 May 2022 13:21:23 +0200 Subject: use better check if tor is running on vpn certificate update error handling --- .../java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 63b24ea2..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; @@ -204,7 +203,7 @@ public class ProviderApiManager extends ProviderApiManagerBase { VpnStatus.logDebug("[API] VPN CERT: " + certString); } if (ConfigHelper.checkErroneousDownload(certString)) { - if (TorStatusObservable.getStatus() != OFF && TorStatusObservable.getProxyPort() != -1) { + if (TorStatusObservable.isRunning()) { setErrorResult(result, downloading_vpn_certificate_failed, null); } else if (certString == null || certString.isEmpty() ){ // probably 204 -- cgit v1.2.3 From 0bf1147fe69cd6e8a8583eff71824b4af9bcd6bf Mon Sep 17 00:00:00 2001 From: cyBerta Date: Thu, 19 May 2022 13:23:50 +0200 Subject: set tor-android commit to patch_branch, which includes latest upstream master and one patch commit --- tor-android | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tor-android b/tor-android index 2f914bda..6a9b209b 160000 --- a/tor-android +++ b/tor-android @@ -1 +1 @@ -Subproject commit 2f914bda7e368776be20ea3fa081713e7abc98b4 +Subproject commit 6a9b209bc04fc43eae8a23bf6c845e9f7310bba0 -- cgit v1.2.3 From 464b9e44cd491c75479b13448b001ed2a64dcf9c Mon Sep 17 00:00:00 2001 From: cyBerta Date: Thu, 19 May 2022 16:26:06 +0200 Subject: show in EipFragment bridge status message if bridges are used to update the VPN certificate --- .../bitmaskclient/base/fragments/EipFragment.java | 38 +++++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) 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 fb27bcea..c777e727 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 @@ -17,7 +17,6 @@ package se.leap.bitmaskclient.base.fragments; 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; @@ -48,6 +47,10 @@ 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; @@ -94,7 +97,6 @@ import se.leap.bitmaskclient.providersetup.activities.LoginActivity; import se.leap.bitmaskclient.providersetup.models.LeapSRPSession; import se.leap.bitmaskclient.tor.TorServiceCommand; import se.leap.bitmaskclient.tor.TorStatusObservable; -import se.leap.bitmaskclient.tor.TorStatusObservable.TorStatus; public class EipFragment extends Fragment implements Observer { @@ -121,6 +123,7 @@ public class EipFragment extends Fragment implements Observer { private Unbinder unbinder; private EipStatus eipStatus; + private TorStatusObservable torStatusObservable; private GatewaysManager gatewaysManager; @@ -170,6 +173,7 @@ public class EipFragment extends Fragment implements Observer { super.onCreate(savedInstanceState); openVpnConnection = new EipFragmentServiceConnection(); eipStatus = EipStatus.getInstance(); + torStatusObservable = TorStatusObservable.getInstance(); Activity activity = getActivity(); if (activity != null) { preferences = getActivity().getSharedPreferences(SHARED_PREFERENCES, Context.MODE_PRIVATE); @@ -185,6 +189,7 @@ public class EipFragment extends Fragment implements Observer { @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { eipStatus.addObserver(this); + torStatusObservable.addObserver(this); View view = inflater.inflate(R.layout.f_eip, container, false); unbinder = ButterKnife.bind(this, view); @@ -262,6 +267,7 @@ public class EipFragment extends Fragment implements Observer { public void onDestroyView() { super.onDestroyView(); eipStatus.deleteObserver(this); + torStatusObservable.deleteObserver(this); unbinder.unbind(); } @@ -402,14 +408,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"); } } @@ -429,7 +441,15 @@ public class EipFragment extends Fragment implements Observer { locationButton.showBridgeIndicator(false); locationButton.showRecommendedIndicator(false); mainDescription.setText(null); - subDescription.setText(getString(R.string.updating_certificate_message)); + String torStatus = TorStatusObservable.getStringForCurrentStatus(getContext()); + subDescription.setText(torStatus); + 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); -- cgit v1.2.3 From 7923e2bf9de7cec0a7cd2e428655a93c33f7c5aa Mon Sep 17 00:00:00 2001 From: cyBerta Date: Thu, 19 May 2022 16:26:51 +0200 Subject: avoid possible NPE in TorStatusObservable --- app/src/main/java/se/leap/bitmaskclient/tor/TorStatusObservable.java | 4 ++++ 1 file changed, 4 insertions(+) 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 239bd528..7eee1a9d 100644 --- a/app/src/main/java/se/leap/bitmaskclient/tor/TorStatusObservable.java +++ b/app/src/main/java/se/leap/bitmaskclient/tor/TorStatusObservable.java @@ -263,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); -- cgit v1.2.3 From fb38b7b60b888cc9a4caed5ce0a271b1f7d487ea Mon Sep 17 00:00:00 2001 From: cyBerta Date: Thu, 19 May 2022 16:28:05 +0200 Subject: add providerObservable as member variable to EipFragment, ensure the Fragment has always the latest provider object --- .../main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java | 4 ++++ 1 file changed, 4 insertions(+) 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 c777e727..deac214d 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 @@ -123,6 +123,7 @@ public class EipFragment extends Fragment implements Observer { private Unbinder unbinder; private EipStatus eipStatus; + private ProviderObservable providerObservable; private TorStatusObservable torStatusObservable; private GatewaysManager gatewaysManager; @@ -173,6 +174,7 @@ 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) { @@ -190,6 +192,7 @@ public class EipFragment extends Fragment implements Observer { 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); @@ -267,6 +270,7 @@ public class EipFragment extends Fragment implements Observer { public void onDestroyView() { super.onDestroyView(); eipStatus.deleteObserver(this); + providerObservable.deleteObserver(this); torStatusObservable.deleteObserver(this); unbinder.unbind(); } -- cgit v1.2.3 From c36a41c7072f799d019c2ed0abed030ee3db1bc9 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Fri, 20 May 2022 12:42:09 +0200 Subject: add test for the case the tor thread is interrupted --- .../bitmaskclient/eip/ProviderApiManagerTest.java | 28 ++++++++++++++++++++++ 1 file changed, 28 insertions(+) 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 b7a4fa7c..cb1e1f73 100644 --- a/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java +++ b/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java @@ -71,6 +71,7 @@ import static se.leap.bitmaskclient.providersetup.ProviderAPI.MISSING_NETWORK_CO 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; @@ -859,6 +860,33 @@ public class ProviderApiManagerTest { 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(); -- cgit v1.2.3 From 2ba5e0faa0e576703fa80b36a480e93a0a7a26b6 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Fri, 20 May 2022 13:02:23 +0200 Subject: fix typo and parse string from error result json object more gracefully --- app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 b1bf8cb5..9d67340e 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java @@ -237,8 +237,8 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta case TOR_EXCEPTION: try { JSONObject jsonObject = new JSONObject(resultData.getString(ProviderAPI.ERRORS)); - String initalAction = jsonObject.getString(ProviderAPI.INITIAL_ACTION); - if (UPDATE_INVALID_VPN_CERTIFICATE.equals(initalAction)) { + String initialAction = jsonObject.optString(ProviderAPI.INITIAL_ACTION); + if (UPDATE_INVALID_VPN_CERTIFICATE.equals(initialAction)) { EipStatus.getInstance().setUpdatingVpnCert(false); } } catch (Exception e) { -- cgit v1.2.3 From 840fc607b987825f2b362ae6838d42ef4fda5e7b Mon Sep 17 00:00:00 2001 From: cyBerta Date: Fri, 20 May 2022 13:03:25 +0200 Subject: show error message if tor failed to start during the VPN certificate update --- .../java/se/leap/bitmaskclient/base/MainActivity.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) 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 e9a5032c..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; } } -- cgit v1.2.3 From 0ebc7e3a9e84f598a0221fe64f51d0e7906ac377 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Fri, 20 May 2022 13:08:03 +0200 Subject: remove unnecessary method call during EipFragment layouting --- app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java | 1 - 1 file changed, 1 deletion(-) 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 deac214d..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 @@ -446,7 +446,6 @@ public class EipFragment extends Fragment implements Observer { locationButton.showRecommendedIndicator(false); mainDescription.setText(null); String torStatus = TorStatusObservable.getStringForCurrentStatus(getContext()); - subDescription.setText(torStatus); if (!TextUtils.isEmpty(torStatus)) { Spannable spannable = new SpannableString(torStatus); spannable.setSpan(new RelativeSizeSpan(0.75f), 0, spannable.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); -- cgit v1.2.3