summaryrefslogtreecommitdiff
path: root/app/src/main/java/se/leap/bitmaskclient/base
diff options
context:
space:
mode:
authorcyberta <cyberta@riseup.net>2024-01-25 23:41:42 +0000
committercyberta <cyberta@riseup.net>2024-01-25 23:41:42 +0000
commit5bf3f807a4804c18b7dc88e07e4e34ecf0791713 (patch)
treea092fd4ab72d1a31daa3cc442331cbb05d034ef0 /app/src/main/java/se/leap/bitmaskclient/base
parentce8106f60d83ee2a788f1920437a0bbd48d6b15f (diff)
parente84289ab4380ae61cc9f2a86da9a16d1aae45cbd (diff)
Merge branch 'post_release_work' into 'master'
post release tweaks and fixes Closes #9150 and #8983 See merge request leap/bitmask_android!264
Diffstat (limited to 'app/src/main/java/se/leap/bitmaskclient/base')
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/BitmaskTileService.java13
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java12
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java36
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/fragments/GatewaySelectionFragment.java10
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java12
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/fragments/ObfuscationProxyDialog.java12
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/fragments/SettingsFragment.java2
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/fragments/TetheringDialog.java13
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java1
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java4
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/models/ProviderObservable.java24
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/utils/BuildConfigHelper.java98
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/utils/CertificateHelper.java64
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/utils/ConfigHelper.java136
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/utils/FileHelper.java41
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/utils/HandlerProvider.java38
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/utils/InputStreamHelper.java24
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java7
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/utils/RSAHelper.java72
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/utils/TimezoneHelper.java47
20 files changed, 482 insertions, 184 deletions
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/BitmaskTileService.java b/app/src/main/java/se/leap/bitmaskclient/base/BitmaskTileService.java
index 370a7af6..d85e0a75 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/BitmaskTileService.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/BitmaskTileService.java
@@ -8,6 +8,8 @@ import android.os.Build;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
import java.util.Observable;
import java.util.Observer;
@@ -19,7 +21,7 @@ import se.leap.bitmaskclient.base.models.ProviderObservable;
@TargetApi(Build.VERSION_CODES.N)
-public class BitmaskTileService extends TileService implements Observer {
+public class BitmaskTileService extends TileService implements PropertyChangeListener {
@SuppressLint("Override")
@TargetApi(Build.VERSION_CODES.N)
@@ -59,7 +61,7 @@ public class BitmaskTileService extends TileService implements Observer {
public void onStartListening() {
super.onStartListening();
EipStatus.getInstance().addObserver(this);
- update(EipStatus.getInstance(), null);
+ propertyChange(new PropertyChangeEvent(EipStatus.getInstance(), EipStatus.PROPERTY_CHANGE, null, EipStatus.getInstance()));
}
@Override
@@ -69,16 +71,15 @@ public class BitmaskTileService extends TileService implements Observer {
}
@Override
- public void update(Observable o, Object arg) {
+ public void propertyChange(PropertyChangeEvent evt) {
Tile t = getQsTile();
// Tile t should never be null according to https://developer.android.com/reference/kotlin/android/service/quicksettings/TileService.
// Hovever we've got crash reports.
if (t == null) {
return;
}
-
- if (o instanceof EipStatus) {
- EipStatus status = (EipStatus) o;
+ if (EipStatus.PROPERTY_CHANGE.equals(evt.getPropertyName())) {
+ EipStatus status = (EipStatus) evt.getNewValue();
Icon icon;
String title;
if (status.isConnecting() || status.isReconnecting()) {
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 5da238d4..3f541d8d 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java
@@ -59,8 +59,8 @@ import androidx.fragment.app.FragmentTransaction;
import org.json.JSONException;
import org.json.JSONObject;
-import java.util.Observable;
-import java.util.Observer;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
import se.leap.bitmaskclient.BuildConfig;
import se.leap.bitmaskclient.R;
@@ -80,7 +80,7 @@ import se.leap.bitmaskclient.eip.EipSetupListener;
import se.leap.bitmaskclient.eip.EipSetupObserver;
import se.leap.bitmaskclient.providersetup.ProviderAPI;
-public class MainActivity extends AppCompatActivity implements EipSetupListener, Observer {
+public class MainActivity extends AppCompatActivity implements EipSetupListener, PropertyChangeListener {
public final static String TAG = MainActivity.class.getSimpleName();
@@ -354,9 +354,9 @@ public class MainActivity extends AppCompatActivity implements EipSetupListener,
}
@Override
- public void update(Observable o, Object arg) {
- if (o instanceof ProviderObservable) {
- this.provider = ((ProviderObservable) o).getCurrentProvider();
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (ProviderObservable.PROPERTY_CHANGE.equals(evt.getPropertyName())) {
+ this.provider = (Provider) evt.getNewValue();
}
}
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 f4e09e62..fb93796e 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
@@ -56,8 +56,8 @@ import androidx.fragment.app.FragmentTransaction;
import androidx.vectordrawable.graphics.drawable.Animatable2Compat.AnimationCallback;
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat;
-import java.util.Observable;
-import java.util.Observer;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
import de.blinkt.openvpn.core.ConnectionStatus;
import de.blinkt.openvpn.core.VpnStatus;
@@ -80,7 +80,7 @@ import se.leap.bitmaskclient.providersetup.activities.SetupActivity;
import se.leap.bitmaskclient.tor.TorServiceCommand;
import se.leap.bitmaskclient.tor.TorStatusObservable;
-public class EipFragment extends Fragment implements Observer {
+public class EipFragment extends Fragment implements PropertyChangeListener {
public final static String TAG = EipFragment.class.getSimpleName();
@@ -375,17 +375,27 @@ public class EipFragment extends Fragment implements Observer {
}
+
@Override
- public void update(Observable observable, Object data) {
- if (observable instanceof EipStatus) {
- previousEipLevel = eipStatus.getEipLevel();
- eipStatus = (EipStatus) observable;
- handleNewStateOnMain();
-
- } else if (observable instanceof ProviderObservable) {
- provider = ((ProviderObservable) observable).getCurrentProvider();
- } else if (observable instanceof TorStatusObservable && EipStatus.getInstance().isUpdatingVpnCert()) {
- handleNewStateOnMain();
+ public void propertyChange(PropertyChangeEvent evt) {
+ switch (evt.getPropertyName()) {
+ case ProviderObservable.PROPERTY_CHANGE: {
+ provider = ((Provider) evt.getNewValue());
+ break;
+ }
+ case TorStatusObservable.PROPERTY_CHANGE: {
+ if (EipStatus.getInstance().isUpdatingVpnCert()) {
+ handleNewStateOnMain();
+ }
+ break;
+ }
+ case EipStatus.PROPERTY_CHANGE: {
+ previousEipLevel = eipStatus.getEipLevel();
+ eipStatus = (EipStatus) evt.getNewValue();
+ handleNewStateOnMain();
+ break;
+ }
+ default: {}
}
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/GatewaySelectionFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/GatewaySelectionFragment.java
index 99b1ac39..bb5a06c4 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/GatewaySelectionFragment.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/GatewaySelectionFragment.java
@@ -44,6 +44,8 @@ import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.Observable;
@@ -64,7 +66,7 @@ interface LocationListSelectionListener {
void onLocationManuallySelected(Location location);
}
-public class GatewaySelectionFragment extends Fragment implements Observer, LocationListSelectionListener, SharedPreferences.OnSharedPreferenceChangeListener {
+public class GatewaySelectionFragment extends Fragment implements PropertyChangeListener, LocationListSelectionListener, SharedPreferences.OnSharedPreferenceChangeListener {
private static final String TAG = GatewaySelectionFragment.class.getSimpleName();
@@ -197,9 +199,9 @@ public class GatewaySelectionFragment extends Fragment implements Observer, Loca
}
@Override
- public void update(Observable o, Object arg) {
- if (o instanceof EipStatus) {
- eipStatus = (EipStatus) o;
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (EipStatus.PROPERTY_CHANGE.equals(evt.getPropertyName())) {
+ eipStatus = (EipStatus) evt.getNewValue();
Activity activity = getActivity();
if (activity != null) {
activity.runOnUiThread(this::updateRecommendedLocation);
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java
index 60c21c40..16aea065 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java
@@ -24,7 +24,7 @@ import static se.leap.bitmaskclient.base.models.Constants.ENABLE_DONATION;
import static se.leap.bitmaskclient.base.models.Constants.PREFERRED_CITY;
import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY;
import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_SWITCH_PROVIDER;
-import static se.leap.bitmaskclient.base.utils.ConfigHelper.isDefaultBitmask;
+import static se.leap.bitmaskclient.base.utils.BuildConfigHelper.isDefaultBitmask;
import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getPreferredCity;
import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getSaveBattery;
import static se.leap.bitmaskclient.base.utils.PreferenceHelper.saveBattery;
@@ -53,8 +53,8 @@ import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment;
-import java.util.Observable;
-import java.util.Observer;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
import se.leap.bitmaskclient.BuildConfig;
import se.leap.bitmaskclient.R;
@@ -74,7 +74,7 @@ import se.leap.bitmaskclient.tethering.TetheringObservable;
* See the <a href="https://developer.android.com/design/patterns/navigation-drawer.html#Interaction">
* design guidelines</a> for a complete explanation of the behaviors implemented here.
*/
-public class NavigationDrawerFragment extends Fragment implements SharedPreferences.OnSharedPreferenceChangeListener, Observer {
+public class NavigationDrawerFragment extends Fragment implements SharedPreferences.OnSharedPreferenceChangeListener, PropertyChangeListener {
/**
* Per the design guidelines, you should show the drawer on launch until the user manually
@@ -444,8 +444,8 @@ public class NavigationDrawerFragment extends Fragment implements SharedPreferen
}
@Override
- public void update(Observable o, Object arg) {
- if (o instanceof TetheringObservable || o instanceof EipStatus) {
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (EipStatus.PROPERTY_CHANGE.equals(evt.getPropertyName()) || TetheringObservable.PROPERTY_CHANGE.equals(evt.getPropertyName())) {
try {
getActivity().runOnUiThread(() ->
enableSaveBatteryEntry(!TetheringObservable.getInstance().getTetheringState().isVpnTetheringRunning()));
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/ObfuscationProxyDialog.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/ObfuscationProxyDialog.java
index 948d764f..7d12ca70 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/ObfuscationProxyDialog.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/ObfuscationProxyDialog.java
@@ -15,7 +15,7 @@ import androidx.appcompat.app.AppCompatDialogFragment;
import androidx.appcompat.widget.AppCompatButton;
import androidx.appcompat.widget.AppCompatEditText;
-import se.leap.bitmaskclient.base.utils.ConfigHelper.ObfsVpnHelper;
+import se.leap.bitmaskclient.base.utils.BuildConfigHelper;
import se.leap.bitmaskclient.base.utils.PreferenceHelper;
import se.leap.bitmaskclient.base.views.IconSwitchEntry;
import se.leap.bitmaskclient.databinding.DObfuscationProxyBinding;
@@ -68,12 +68,12 @@ public class ObfuscationProxyDialog extends AppCompatDialogFragment {
dismiss();
});
- useDefaultsButton.setVisibility(ObfsVpnHelper.hasObfuscationPinningDefaults() ? VISIBLE : GONE);
+ useDefaultsButton.setVisibility(BuildConfigHelper.hasObfuscationPinningDefaults() ? VISIBLE : GONE);
useDefaultsButton.setOnClickListener(v -> {
- ipField.setText(ObfsVpnHelper.obfsvpnIP());
- portField.setText(ObfsVpnHelper.obfsvpnPort());
- certificateField.setText(ObfsVpnHelper.obfsvpnCert());
- kcpSwitch.setChecked(ObfsVpnHelper.useKcp());
+ ipField.setText(BuildConfigHelper.obfsvpnIP());
+ portField.setText(BuildConfigHelper.obfsvpnPort());
+ certificateField.setText(BuildConfigHelper.obfsvpnCert());
+ kcpSwitch.setChecked(BuildConfigHelper.useKcp());
});
cancelButton.setOnClickListener(v -> {
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/SettingsFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/SettingsFragment.java
index c8c994a5..d7b62de2 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/SettingsFragment.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/SettingsFragment.java
@@ -8,7 +8,7 @@ import static se.leap.bitmaskclient.base.models.Constants.PREFER_UDP;
import static se.leap.bitmaskclient.base.models.Constants.USE_BRIDGES;
import static se.leap.bitmaskclient.base.models.Constants.USE_IPv6_FIREWALL;
import static se.leap.bitmaskclient.base.models.Constants.USE_OBFUSCATION_PINNING;
-import static se.leap.bitmaskclient.base.utils.ConfigHelper.ObfsVpnHelper.useObfsVpn;
+import static se.leap.bitmaskclient.base.utils.BuildConfigHelper.useObfsVpn;
import static se.leap.bitmaskclient.base.utils.ConfigHelper.isCalyxOSWithTetheringSupport;
import static se.leap.bitmaskclient.base.utils.PreferenceHelper.allowExperimentalTransports;
import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getExcludedApps;
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/TetheringDialog.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/TetheringDialog.java
index 588daa3f..05744bc9 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/TetheringDialog.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/TetheringDialog.java
@@ -22,8 +22,8 @@ import androidx.appcompat.app.AppCompatDialogFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
-import java.util.Observable;
-import java.util.Observer;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -52,7 +52,7 @@ import se.leap.bitmaskclient.tethering.TetheringObservable;
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-public class TetheringDialog extends AppCompatDialogFragment implements Observer {
+public class TetheringDialog extends AppCompatDialogFragment implements PropertyChangeListener {
public final static String TAG = TetheringDialog.class.getName();
@@ -241,9 +241,9 @@ public class TetheringDialog extends AppCompatDialogFragment implements Observer
}
@Override
- public void update(Observable o, Object arg) {
- if (o instanceof TetheringObservable) {
- TetheringObservable observable = (TetheringObservable) o;
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (TetheringObservable.PROPERTY_CHANGE.equals(evt.getPropertyName())) {
+ TetheringObservable observable = (TetheringObservable) evt.getNewValue();
Log.d(TAG, "TetheringObservable is updated");
dataset[0].enabled = observable.isWifiTetheringEnabled();
dataset[1].enabled = observable.isUsbTetheringEnabled();
@@ -251,5 +251,4 @@ public class TetheringDialog extends AppCompatDialogFragment implements Observer
adapter.notifyDataSetChanged();
}
}
-
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java b/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java
index 70bf3943..18590f0b 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java
@@ -52,6 +52,7 @@ public interface Constants {
String OBFUSCATION_PINNING_CERT = "obfuscation_pinning_cert";
String OBFUSCATION_PINNING_KCP = "obfuscation_pinning_udp";
String OBFUSCATION_PINNING_LOCATION = "obfuscation_pinning_location";
+ String USE_SYSTEM_PROXY = "usesystemproxy";
//////////////////////////////////////////////
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java b/app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java
index 14c78cc3..cb9bd520 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java
@@ -28,8 +28,8 @@ import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_ALLOWED_REGIS
import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_ALLOW_ANONYMOUS;
import static se.leap.bitmaskclient.base.models.Constants.TRANSPORT;
import static se.leap.bitmaskclient.base.models.Constants.TYPE;
-import static se.leap.bitmaskclient.base.utils.ConfigHelper.ObfsVpnHelper.useObfsVpn;
-import static se.leap.bitmaskclient.base.utils.ConfigHelper.RSAHelper.parseRsaKeyFromString;
+import static se.leap.bitmaskclient.base.utils.BuildConfigHelper.useObfsVpn;
+import static se.leap.bitmaskclient.base.utils.RSAHelper.parseRsaKeyFromString;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORS;
import android.os.Parcel;
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/models/ProviderObservable.java b/app/src/main/java/se/leap/bitmaskclient/base/models/ProviderObservable.java
index 3e1e1fcc..6e28ac3e 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/models/ProviderObservable.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/models/ProviderObservable.java
@@ -2,13 +2,17 @@ package se.leap.bitmaskclient.base.models;
import androidx.annotation.NonNull;
-import java.util.Observable;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
/**
* Created by cyberta on 05.12.18.
*/
-public class ProviderObservable extends Observable {
+public class ProviderObservable {
private static ProviderObservable instance;
+ private final PropertyChangeSupport changeSupport;
+ public static final String PROPERTY_CHANGE = "ProviderObservable";
+
private Provider currentProvider;
private Provider providerForDns;
@@ -19,11 +23,23 @@ public class ProviderObservable extends Observable {
return instance;
}
+ private ProviderObservable() {
+ changeSupport = new PropertyChangeSupport(this);
+ currentProvider = new Provider();
+ }
+
+ public void addObserver(PropertyChangeListener propertyChangeListener) {
+ changeSupport.addPropertyChangeListener(propertyChangeListener);
+ }
+
+ public void deleteObserver(PropertyChangeListener propertyChangeListener) {
+ changeSupport.removePropertyChangeListener(propertyChangeListener);
+ }
+
public synchronized void updateProvider(@NonNull Provider provider) {
instance.currentProvider = provider;
instance.providerForDns = null;
- instance.setChanged();
- instance.notifyObservers();
+ instance.changeSupport.firePropertyChange(PROPERTY_CHANGE, null, provider);
}
public Provider getCurrentProvider() {
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/BuildConfigHelper.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/BuildConfigHelper.java
new file mode 100644
index 00000000..e1f65b5e
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/BuildConfigHelper.java
@@ -0,0 +1,98 @@
+package se.leap.bitmaskclient.base.utils;
+
+import static se.leap.bitmaskclient.base.models.Constants.DEFAULT_BITMASK;
+
+import androidx.annotation.VisibleForTesting;
+
+import de.blinkt.openvpn.core.NativeUtils;
+import se.leap.bitmaskclient.BuildConfig;
+
+// ObfsVpnHelper class allows us to mock BuildConfig fields related to the pre-shipped circumvention settings
+public class BuildConfigHelper {
+
+ public interface BuildConfigHelperInterface {
+ boolean useObfsVpn();
+ boolean hasObfuscationPinningDefaults();
+ String obfsvpnIP();
+ String obfsvpnPort();
+ String obfsvpnCert();
+ boolean useKcp();
+ boolean isDefaultBitmask();
+ }
+
+ public static class DefaultBuildConfigHelper implements BuildConfigHelperInterface {
+ @Override
+ public boolean useObfsVpn() {
+ return BuildConfig.use_obfsvpn;
+ }
+
+ @Override
+ public boolean hasObfuscationPinningDefaults() {
+ return BuildConfig.obfsvpn_ip != null &&
+ BuildConfig.obfsvpn_port != null &&
+ BuildConfig.obfsvpn_cert != null &&
+ !BuildConfig.obfsvpn_ip.isEmpty() &&
+ !BuildConfig.obfsvpn_port.isEmpty() &&
+ !BuildConfig.obfsvpn_cert.isEmpty();
+ }
+
+ @Override
+ public String obfsvpnIP() {
+ return BuildConfig.obfsvpn_ip;
+ }
+
+ @Override
+ public String obfsvpnPort() {
+ return BuildConfig.obfsvpn_port;
+ }
+
+ @Override
+ public String obfsvpnCert() {
+ return BuildConfig.obfsvpn_cert;
+ }
+
+ @Override
+ public boolean useKcp() {
+ return BuildConfig.obfsvpn_use_kcp;
+ }
+
+ @Override
+ public boolean isDefaultBitmask() {
+ return BuildConfig.FLAVOR_branding.equals(DEFAULT_BITMASK);
+ }
+ }
+
+ private static BuildConfigHelperInterface instance = new DefaultBuildConfigHelper();
+
+ @VisibleForTesting
+ public BuildConfigHelper(BuildConfigHelperInterface helperInterface) {
+ if (!NativeUtils.isUnitTest()) {
+ throw new IllegalStateException("ObfsVpnHelper injected with ObfsVpnHelperInterface outside of an unit test");
+ }
+ instance = helperInterface;
+ }
+
+ public static boolean useObfsVpn() {
+ return instance.useObfsVpn();
+ }
+
+ public static boolean hasObfuscationPinningDefaults() {
+ return instance.hasObfuscationPinningDefaults();
+ }
+ public static String obfsvpnIP() {
+ return instance.obfsvpnIP();
+ }
+ public static String obfsvpnPort() {
+ return instance.obfsvpnPort();
+ }
+ public static String obfsvpnCert() {
+ return instance.obfsvpnCert();
+ }
+ public static boolean useKcp() {
+ return instance.useKcp();
+ }
+
+ public static boolean isDefaultBitmask() {
+ return instance.isDefaultBitmask();
+ }
+}
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/CertificateHelper.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/CertificateHelper.java
new file mode 100644
index 00000000..11202734
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/CertificateHelper.java
@@ -0,0 +1,64 @@
+package se.leap.bitmaskclient.base.utils;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+
+import de.blinkt.openvpn.core.NativeUtils;
+
+public class CertificateHelper {
+
+ public interface CertificateHelperInterface {
+ String getFingerprintFromCertificate(X509Certificate certificate, String encoding) throws NoSuchAlgorithmException, CertificateEncodingException;
+
+ }
+
+ public static class DefaultCertificateHelper implements CertificateHelperInterface {
+
+ public String byteArrayToHex(byte[] input) {
+ int readBytes = input.length;
+ StringBuffer hexData = new StringBuffer();
+ int onebyte;
+ for (int i = 0; i < readBytes; i++) {
+ onebyte = ((0x000000ff & input[i]) | 0xffffff00);
+ hexData.append(Integer.toHexString(onebyte).substring(6));
+ }
+ return hexData.toString();
+ }
+
+ /**
+ * Calculates the hexadecimal representation of a sha256/sha1 fingerprint of a certificate
+ *
+ * @param certificate
+ * @param encoding
+ * @return
+ * @throws NoSuchAlgorithmException
+ * @throws CertificateEncodingException
+ */
+ @Override
+ public String getFingerprintFromCertificate(X509Certificate certificate, String encoding) throws NoSuchAlgorithmException, CertificateEncodingException {
+ byte[] byteArray = MessageDigest.getInstance(encoding).digest(certificate.getEncoded());
+ return byteArrayToHex(byteArray);
+ }
+ }
+
+ private static CertificateHelperInterface instance = new DefaultCertificateHelper();
+
+ @VisibleForTesting
+ public CertificateHelper(CertificateHelperInterface helperInterface) {
+ if (!NativeUtils.isUnitTest()) {
+ throw new IllegalStateException("CertificateHelper injected with CertificateHelperInterface outside of an unit test");
+ }
+ instance = helperInterface;
+ }
+
+ @NonNull
+ public static String getFingerprintFromCertificate(X509Certificate certificate, String encoding) throws NoSuchAlgorithmException, CertificateEncodingException {
+ return instance.getFingerprintFromCertificate(certificate, encoding);
+ }
+
+}
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/ConfigHelper.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/ConfigHelper.java
index 9289738a..cd5d1fca 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/utils/ConfigHelper.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/ConfigHelper.java
@@ -16,8 +16,6 @@
*/
package se.leap.bitmaskclient.base.utils;
-import static se.leap.bitmaskclient.base.models.Constants.DEFAULT_BITMASK;
-
import android.app.PendingIntent;
import android.content.Context;
import android.content.res.Resources;
@@ -36,22 +34,14 @@ import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
-import java.security.KeyFactory;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
-import java.security.interfaces.RSAPrivateKey;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
-import java.util.Calendar;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import de.blinkt.openvpn.core.NativeUtils;
import okhttp3.internal.publicsuffix.PublicSuffixDatabase;
import se.leap.bitmaskclient.BuildConfig;
import se.leap.bitmaskclient.R;
@@ -69,8 +59,6 @@ public class ConfigHelper {
final public static String NG_1024 =
"eeaf0ab9adb38dd69c33f80afa8fc5e86072618775ff3c0b9ea2314c9c256576d674df7496ea81d3383b4813d692c6e0e0d5d8e250b98be48e495c1d6089dad15dc7d7b46154d6b6ce8ef4ad69b15d4982559b297bcf1885c529f566660e57ec68edbc3c05726cc02fd4cbf4976eaa9afd5138fe8376435b9fc61d2fc0eb06e3";
final public static BigInteger G = new BigInteger("2");
- final public static Pattern IPv4_PATTERN = Pattern.compile("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$");
- final public static Pattern PEM_CERTIFICATE_PATTERN = Pattern.compile("((-----BEGIN CERTIFICATE-----)([A-Za-z0-9+/=\\n]+)(-----END CERTIFICATE-----)+)");
public static boolean checkErroneousDownload(String downloadedString) {
try {
@@ -109,8 +97,8 @@ public class ConfigHelper {
CertificateFactory cf;
try {
cf = CertificateFactory.getInstance("X.509");
-
- Matcher matcher = PEM_CERTIFICATE_PATTERN.matcher(certificateString);
+ Pattern pattern = Pattern.compile("((-----BEGIN CERTIFICATE-----)([A-Za-z0-9+/=\\n]+)(-----END CERTIFICATE-----)+)");
+ Matcher matcher = pattern.matcher(certificateString);
while (matcher.find()) {
String certificate = matcher.group(3);
if (certificate == null) continue;
@@ -131,65 +119,6 @@ public class ConfigHelper {
return null;
}
- public static class RSAHelper {
- public static RSAPrivateKey parseRsaKeyFromString(String rsaKeyString) {
- RSAPrivateKey key;
- try {
- KeyFactory kf;
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
- kf = KeyFactory.getInstance("RSA", "BC");
- } else {
- kf = KeyFactory.getInstance("RSA");
- }
- rsaKeyString = rsaKeyString.replaceFirst("-----BEGIN RSA PRIVATE KEY-----", "").replaceFirst("-----END RSA PRIVATE KEY-----", "");
- PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.decode(rsaKeyString));
- key = (RSAPrivateKey) kf.generatePrivate(keySpec);
- } catch (InvalidKeySpecException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- return null;
- } catch (NoSuchAlgorithmException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- return null;
- } catch (NullPointerException e) {
- e.printStackTrace();
- return null;
- } catch (NoSuchProviderException e) {
- e.printStackTrace();
- return null;
- }
-
- return key;
- }
- }
-
- private static String byteArrayToHex(byte[] input) {
- int readBytes = input.length;
- StringBuffer hexData = new StringBuffer();
- int onebyte;
- for (int i = 0; i < readBytes; i++) {
- onebyte = ((0x000000ff & input[i]) | 0xffffff00);
- hexData.append(Integer.toHexString(onebyte).substring(6));
- }
- return hexData.toString();
- }
-
- /**
- * Calculates the hexadecimal representation of a sha256/sha1 fingerprint of a certificate
- *
- * @param certificate
- * @param encoding
- * @return
- * @throws NoSuchAlgorithmException
- * @throws CertificateEncodingException
- */
- @NonNull
- public static String getFingerprintFromCertificate(X509Certificate certificate, String encoding) throws NoSuchAlgorithmException, CertificateEncodingException /*, UnsupportedEncodingException*/ {
- byte[] byteArray = MessageDigest.getInstance(encoding).digest(certificate.getEncoded());
- return byteArrayToHex(byteArray);
- }
-
public static void ensureNotOnMainThread(@NonNull Context context) throws IllegalStateException{
Looper looper = Looper.myLooper();
if (looper != null && looper == context.getMainLooper()) {
@@ -198,35 +127,18 @@ public class ConfigHelper {
}
}
- public static boolean isDefaultBitmask() {
- return BuildConfig.FLAVOR_branding.equals(DEFAULT_BITMASK);
- }
-
public static boolean preferAnonymousUsage() {
return BuildConfig.priotize_anonymous_usage;
}
- public static int getCurrentTimezone() {
- return Calendar.getInstance().get(Calendar.ZONE_OFFSET) / 3600000;
- }
-
- public static int timezoneDistance(int local_timezone, int remoteTimezone) {
- // Distance along the numberline of Prime Meridian centric, assumes UTC-11 through UTC+12
- int dist = Math.abs(local_timezone - remoteTimezone);
- // Farther than 12 timezones and it's shorter around the "back"
- if (dist > 12)
- dist = 12 - (dist - 12); // Well i'll be. Absolute values make equations do funny things.
- return dist;
- }
-
/**
*
* @param remoteTimezone
* @return a value between 0.1 and 1.0
*/
public static double getConnectionQualityFromTimezoneDistance(int remoteTimezone) {
- int localTimeZone = ConfigHelper.getCurrentTimezone();
- int distance = ConfigHelper.timezoneDistance(localTimeZone, remoteTimezone);
+ int localTimeZone = TimezoneHelper.getCurrentTimezone();
+ int distance = TimezoneHelper.timezoneDistance(localTimeZone, remoteTimezone);
return Math.max(distance / 12.0, 0.1);
}
@@ -274,7 +186,7 @@ public class ConfigHelper {
if (ipv4 == null) {
return false;
}
- Matcher matcher = IPv4_PATTERN.matcher(ipv4);
+ Matcher matcher = Pattern.compile("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$").matcher(ipv4);
return matcher.matches();
}
@@ -287,35 +199,6 @@ public class ConfigHelper {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R;
}
- // ObfsVpnHelper class allows us to mock BuildConfig.use_obfsvpn while
- // not mocking the whole ConfigHelper class
- public static class ObfsVpnHelper {
- public static boolean useObfsVpn() {
- return BuildConfig.use_obfsvpn;
- }
-
- public static boolean hasObfuscationPinningDefaults() {
- return BuildConfig.obfsvpn_ip != null &&
- BuildConfig.obfsvpn_port != null &&
- BuildConfig.obfsvpn_cert != null &&
- !BuildConfig.obfsvpn_ip.isEmpty() &&
- !BuildConfig.obfsvpn_port.isEmpty() &&
- !BuildConfig.obfsvpn_cert.isEmpty();
- }
- public static String obfsvpnIP() {
- return BuildConfig.obfsvpn_ip;
- }
- public static String obfsvpnPort() {
- return BuildConfig.obfsvpn_port;
- }
- public static String obfsvpnCert() {
- return BuildConfig.obfsvpn_cert;
- }
- public static boolean useKcp() {
- return BuildConfig.obfsvpn_use_kcp;
- }
- }
-
public static int getPendingIntentFlags() {
int flags = PendingIntent.FLAG_CANCEL_CURRENT;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
@@ -323,4 +206,11 @@ public class ConfigHelper {
}
return flags;
}
+
+ public static int getTorTimeout() {
+ if (NativeUtils.isUnitTest()) {
+ return 1;
+ }
+ return 180;
+ }
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/FileHelper.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/FileHelper.java
index eb1c255c..f1d86876 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/utils/FileHelper.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/FileHelper.java
@@ -2,6 +2,8 @@ package se.leap.bitmaskclient.base.utils;
import android.content.Context;
+import androidx.annotation.VisibleForTesting;
+
import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
@@ -9,19 +11,50 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import de.blinkt.openvpn.core.NativeUtils;
+
/**
* Created by cyberta on 18.03.18.
*/
public class FileHelper {
+
+ public interface FileHelperInterface {
+ File createFile(File dir, String fileName);
+ void persistFile(File file, String content) throws IOException;
+ }
+
+ public static class DefaultFileHelper implements FileHelperInterface {
+ @Override
+ public File createFile(File dir, String fileName) {
+ return new File(dir, fileName);
+ }
+
+ @Override
+ public void persistFile(File file, String content) throws IOException {
+ FileWriter writer = new FileWriter(file);
+ writer.write(content);
+ writer.close();
+ }
+ }
+
+ private static FileHelperInterface instance = new DefaultFileHelper();
+
+ @VisibleForTesting
+ public FileHelper(FileHelperInterface helperInterface) {
+ if (!NativeUtils.isUnitTest()) {
+ throw new IllegalStateException("FileHelper injected with FileHelperInterface outside of an unit test");
+ }
+
+ instance = helperInterface;
+ }
+
public static File createFile(File dir, String fileName) {
- return new File(dir, fileName);
+ return instance.createFile(dir, fileName);
}
public static void persistFile(File file, String content) throws IOException {
- FileWriter writer = new FileWriter(file);
- writer.write(content);
- writer.close();
+ instance.persistFile(file, content);
}
public static String readPublicKey(Context context) {
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/HandlerProvider.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/HandlerProvider.java
new file mode 100644
index 00000000..d9198ab7
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/HandlerProvider.java
@@ -0,0 +1,38 @@
+package se.leap.bitmaskclient.base.utils;
+
+import android.os.Handler;
+import android.os.Looper;
+
+public class HandlerProvider {
+
+
+ public interface HandlerInterface {
+ void postDelayed(Runnable r, long delay);
+ }
+
+
+ private static HandlerInterface instance;
+
+ public HandlerProvider(HandlerInterface handlerInterface) {
+ instance = handlerInterface;
+ }
+ public static HandlerInterface get() {
+ if (instance == null) {
+ instance = new DefaultHandler();
+ }
+ return instance;
+ }
+
+ public static class DefaultHandler implements HandlerInterface {
+ Handler handler;
+
+ public DefaultHandler() {
+ this.handler = new Handler(Looper.getMainLooper());
+ }
+ @Override
+ public void postDelayed(Runnable r, long delay) {
+ this.handler.postDelayed(r, delay);
+ }
+ }
+}
+
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/InputStreamHelper.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/InputStreamHelper.java
index 8e6273a7..6dfe0861 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/utils/InputStreamHelper.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/InputStreamHelper.java
@@ -8,14 +8,36 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
+import de.blinkt.openvpn.core.NativeUtils;
+
/**
* Created by cyberta on 18.03.18.
*/
public class InputStreamHelper {
+ public interface InputStreamHelperInterface {
+ InputStream getInputStreamFrom(String filePath) throws FileNotFoundException;
+
+ }
+
+ private static InputStreamHelperInterface instance = new DefaultInputStreamHelper();
+
+ private static class DefaultInputStreamHelper implements InputStreamHelperInterface {
+ @Override
+ public InputStream getInputStreamFrom(String filePath) throws FileNotFoundException {
+ return new FileInputStream(filePath);
+ }
+ }
+
+ public InputStreamHelper(InputStreamHelperInterface helperInterface) {
+ if (!NativeUtils.isUnitTest()) {
+ throw new IllegalStateException("InputStreamHelper injected with InputStreamHelperInterface outside of an unit test");
+ }
+ instance = helperInterface;
+ }
//allows us to mock FileInputStream
public static InputStream getInputStreamFrom(String filePath) throws FileNotFoundException {
- return new FileInputStream(filePath);
+ return instance.getInputStreamFrom(filePath);
}
public static String loadInputStreamAsString(InputStream is) {
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java
index b35a04cd..2420a797 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java
@@ -40,6 +40,7 @@ import static se.leap.bitmaskclient.base.models.Constants.USE_BRIDGES;
import static se.leap.bitmaskclient.base.models.Constants.USE_IPv6_FIREWALL;
import static se.leap.bitmaskclient.base.models.Constants.USE_OBFUSCATION_PINNING;
import static se.leap.bitmaskclient.base.models.Constants.USE_SNOWFLAKE;
+import static se.leap.bitmaskclient.base.models.Constants.USE_SYSTEM_PROXY;
import android.content.Context;
import android.content.SharedPreferences;
@@ -460,12 +461,16 @@ public class PreferenceHelper {
return getBoolean(ALLOW_EXPERIMENTAL_TRANSPORTS, false);
}
+ public static boolean useSystemProxy() {
+ return getBoolean(USE_SYSTEM_PROXY, true);
+ }
+
public static void setUseObfuscationPinning(Boolean pinning) {
putBoolean(USE_OBFUSCATION_PINNING, pinning);
}
public static boolean useObfuscationPinning() {
- return ConfigHelper.ObfsVpnHelper.useObfsVpn() &&
+ return BuildConfigHelper.useObfsVpn() &&
getUseBridges() &&
getBoolean(USE_OBFUSCATION_PINNING, false) &&
!TextUtils.isEmpty(getObfuscationPinningIP()) &&
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/RSAHelper.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/RSAHelper.java
new file mode 100644
index 00000000..2872139a
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/RSAHelper.java
@@ -0,0 +1,72 @@
+package se.leap.bitmaskclient.base.utils;
+
+import android.os.Build;
+
+import androidx.annotation.VisibleForTesting;
+
+import org.spongycastle.util.encoders.Base64;
+
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+
+import de.blinkt.openvpn.core.NativeUtils;
+
+public class RSAHelper {
+
+ public interface RSAHelperInterface {
+ RSAPrivateKey parseRsaKeyFromString(String rsaKeyString);
+ }
+
+ public static class DefaultRSAHelper implements RSAHelperInterface {
+
+ @Override
+ public RSAPrivateKey parseRsaKeyFromString(String rsaKeyString) {
+ RSAPrivateKey key;
+ try {
+ KeyFactory kf;
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
+ kf = KeyFactory.getInstance("RSA", "BC");
+ } else {
+ kf = KeyFactory.getInstance("RSA");
+ }
+ rsaKeyString = rsaKeyString.replaceFirst("-----BEGIN RSA PRIVATE KEY-----", "").replaceFirst("-----END RSA PRIVATE KEY-----", "");
+ PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.decode(rsaKeyString));
+ key = (RSAPrivateKey) kf.generatePrivate(keySpec);
+ } catch (InvalidKeySpecException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ return null;
+ } catch (NoSuchAlgorithmException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ return null;
+ } catch (NullPointerException e) {
+ e.printStackTrace();
+ return null;
+ } catch (NoSuchProviderException e) {
+ e.printStackTrace();
+ return null;
+ }
+
+ return key;
+ }
+ }
+
+ private static RSAHelperInterface instance = new DefaultRSAHelper();
+
+ @VisibleForTesting
+ public RSAHelper(RSAHelperInterface helperInterface) {
+ if (!NativeUtils.isUnitTest()) {
+ throw new IllegalStateException("RSAHelper injected with RSAHelperInterface outside of an unit test");
+ }
+ instance = helperInterface;
+ }
+
+ public static RSAPrivateKey parseRsaKeyFromString(String rsaKeyString) {
+ return instance.parseRsaKeyFromString(rsaKeyString);
+ }
+}
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/TimezoneHelper.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/TimezoneHelper.java
new file mode 100644
index 00000000..63b12fd3
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/TimezoneHelper.java
@@ -0,0 +1,47 @@
+package se.leap.bitmaskclient.base.utils;
+
+import androidx.annotation.VisibleForTesting;
+
+import java.util.Calendar;
+
+import de.blinkt.openvpn.core.NativeUtils;
+
+public class TimezoneHelper {
+
+ public interface TimezoneInterface {
+ int getCurrentTimezone();
+ }
+
+ private static TimezoneInterface instance = new DefaultTimezoneHelper();
+
+ @VisibleForTesting
+ public TimezoneHelper(TimezoneInterface timezoneInterface) {
+ if (!NativeUtils.isUnitTest()) {
+ throw new IllegalStateException("TimezoneHelper injected with timezoneInterface outside of an unit test");
+ }
+ instance = timezoneInterface;
+ }
+
+ public static TimezoneInterface get() {
+ return instance;
+ }
+
+ public static int timezoneDistance(int localTimezone, int remoteTimezone) { // Distance along the numberline of Prime Meridian centric, assumes UTC-11 through UTC+12
+ int dist = Math.abs(localTimezone - remoteTimezone);
+ // Farther than 12 timezones and it's shorter around the "back"
+ if (dist > 12)
+ dist = 12 - (dist - 12); // Well i'll be. Absolute values make equations do funny things.
+ return dist;
+ }
+
+ public static int getCurrentTimezone() {
+ return get().getCurrentTimezone();
+ }
+
+ private static class DefaultTimezoneHelper implements TimezoneInterface {
+ @Override
+ public int getCurrentTimezone() {
+ return Calendar.getInstance().get(Calendar.ZONE_OFFSET) / 3600000;
+ }
+ }
+}